Map: exarch / builtins
exarch’s resident host atoms and the thin ral helpers over them — the
search, line-witness, and edit surface the model reaches through the ral
tool. The Rust atoms register above ral-core and core never inspects them
(builtins-registry;
repl-builtins-stay-in-repl). The
why of witnessed editing is
hash-addressed-editing.
The shared identity is the witness: the letter h followed by six hex of a
Blake3 digest (trailing whitespace stripped), computed over the smallest
symmetric window of neighbouring lines — at least ±MIN_RADIUS (5), grown until
it names the line uniquely, falling back to the absolute index past
MAX_RADIUS — with the target’s offset and the radius folded in
(window_hashes, an adaptive-context witness). The hashing is private Rust:
the model never constructs a witness, only copies one from view-hash into
edit-hash, and both derive identical witnesses from identical content. The h
prefix keeps the witness un-lexable as an integer, so a hash never elaborates to
Val::Int and silently fails to compare against the recomputed String
(witness-hash-h-prefix).
Rust atoms — shell_eval/builtins.rs
EXARCH_BUILTINS is the largest set on builtins::host_surface() — the one
HostSurface value declaring exarch’s builtins beyond core’s, alongside the
harness verbs and core’s host-selected SERVICE_BUILTIN. Core’s boot_shell
takes the surface and installs it at construction (a half-dressed production
shell is unrepresentable), and the wire engine boots the same dressing
through its EngineInstaller boot recipe
(bootstrap::engine_boot_shell), named on the wire by INSTALLER_TAG in
Frame::Attach.
The bulk-I/O atoms read in Rust, below the redirect frame, so each is one
logical operation with one surface (io-surface).
view-text <path> <start> <end>→[{line, text}]. The read primitive: the half-open line range[start, end), each row carrying its 1-based line number and its text, verbatim — which is whatedit-replacematches on. Surfaces oneObserved::Read { path }observation.view-hash <path> <start> <end>→[{line, hash, text}]. The same range with each row’s witness, the handleedit-hashchecks. Reads and hashes the whole file, since the witness depends on file-wide uniqueness; both readers share one range door and differ only in the column.grep-files <pattern>→[{ file, line, text }]. An ignore-aware Rust regex walk of the cwd (search_tree, binary detection quits at NUL, each file gated bycheck_fs_read, the walk polling the cancel check per entry via the one sanctionedcancellabledoor). Surfaces exactly oneObserved::Grep { scope, pattern }observation for the whole search (io-surface).edit-hash <path> <edits>→Unit. The edit verb:editsis a list of[hash: …, line: …]records. Read the file once, resolve each hash to the one line whose witness matches (zero matches, several matches, a stale witness, or two records on one line all fail before any write), splice every named line in a single pass over the original rows (a real newline in the replacement splits the line, an empty string deletes it), and write back through core’s atomic write door (Shell::atomic_write). Resolving against one snapshot makes the batch atomic and non-interfering. The Rust read raises no read card and the atomic write observes nothing; the builtin surfaces one whole-file diff card of the original against the final text (cards), and nothing at all if the two agree. A stderr note names the replaced lines and warns on suspicious\n-style escapes (the replacement text is verbatim).edit-replace <path> <from> <to>→Unit. The default taught edit: replace the one literal occurrence offrom, erroring (file untouched) on zero or several matches; same silent read, same atomic write, same diff card. Counting is overlap-aware (core’soccurrence_starts), so a needle that overlaps itself cannot pass for unique. It speaks its own diagnostics rather than relabelling another builtin’s: several matches name the lines, and a miss names the mangling it can prove — a literal\n-style escape infrom, or a line matching apart from its indentation.explore-dir <n>→[String]. List directory entries to depthn, ignore-aware, skipping the root and any denied path.skill-list/skill <name>— Agent Skills with progressive disclosure: list the available skills (fresh scan each call, filtered by the grant), then load one skill’s fullSKILL.mdbody on demand; the scan and frontmatter parse live inshell_eval/skill.rs.fff <query>→[String]. Frecency-ranked fuzzy filename search over the working tree (fff_index, thefff-searchcrate); the per-directory index is cached process-globally, so forked children sharing the cwd reuse it.
Reads resolve through checked_read_path / check_fs_read; the edit writes go
through core’s atomic door under the run’s pushed grant
frame (surface-reads-writes-execs).
Legibility by lease class — service, service-handle
There is no model-facing listing over the worker registry at all —
workers was retired: a listing carrying live Value::Handles cannot cross
the engine protocol (SerialValue’s decoder rejects them), and returning the
registry as a language value was mislayered in the first place — enumeration,
reaping, and caps belong to the host and the lease layer, never this door.
Legibility now
splits by class instead:
- An ordinary
spawn-born worker (class: Worker) gets no listing at all. Its idle-observation lease already bounds a forgotten spawn’s harm to at most an hour of one seat out of the cap, so a rail card at birth and a reap card at death are the whole story (shell-eval). - A
service-born worker (class: Durable) is bound only by legibility, and that bound is now the same one an ordinary worker gets, plus one aggregate: the birth trail card (worker #id cmd durable, shell-eval) shown at the moment of theservicecall, and/resources’workers.running[durable]count. The register once carried a protectedservicespin the host reconciled — one row per live service, unwritable by the program — but that mechanism is deleted outright, on no rationale beyond the operator’s own: protected pins should not exist (names-and-schedule-labels’s 2026-08-27 amendment). There is no per-service listing left; a durable worker’s id must be read off its birth card or kept from theHandletheservicecall returned.
service <desc> <thunk> → Handle. The durable-birth verb: an ordinary
buffered spawn registered under the durable class, which arms no lease chain
— no idle reap, no 24 h backstop. desc is a mandatory, non-empty,
single-line String — the whole legibility bound a durable birth declares,
so it cannot be absent — and lands verbatim (trimmed) as the registry
entry’s cmd, which is what the services pin renders. Cancellable through
its handle, dead with /clear or the process. Length is declared at birth,
never promoted into after the fact. The atom itself lives in core
(SERVICE_BUILTIN, the watch mechanism with the hosts swapped —
map: core builtins); exarch is the host that installs
it, because only under exarch’s lease frame does a durable birth distinguish
anything.
service-handle <id> → Handle. The one narrow door back to a never-bound
service’s handle: looked up among this shell’s LeaseClass::Durable entries
only, by the id its birth trail card named. An id naming an ephemeral
spawn/watch worker is refused exactly like an unknown one — an ephemeral
worker’s rediscovery path is the binding lease, not enumeration by id. A bare
top-level service-handle N result cannot cross the engine protocol (a Handle is
not ground) — it exists to be composed with an eliminator in the same run:
await (service-handle 3), cancel (service-handle 3).
Carried only on builtins::host_surface(), alongside the search/edit
atoms above: a bare REPL shell, whose boot never carries EXARCH_BUILTINS (nor
SERVICE_BUILTIN), has neither service nor service-handle — its own job
control is jobs.
ral helpers — agent.ral
Sourced into the shell at boot:
view-text-around path line peek/view-hash-around path line peek— the two thin helpers over the atoms: the2*peek + 1lines centred online, clamped at the top of the file.pin-set <key> <card>/pin-clear <key>— the model-facing write pair, thin wrappers oversurface ``pin/unpin ``, completing thepin-*` family the two enquiries below start (register-is-read-write).- the tasks kit —
mk-task/add-task/transitionand friends, a pure-ral task list that reads its own state back throughpin-readand writes the rendered rollup forward throughpin-set/pin-clear(sync-tasks) rather than threading a bound list through every mutator (cards). set-goal/clear-goal—pin-set/pin-clearunder thegoalregister key, kept visible by the nudge reminder.
Harness verbs — context, spawn, schedule, reply
Every verb below is a BuiltinEntry in
exarch/src/shell_eval/builtins/harness.rs
(HARNESS_BUILTINS, carried on host_surface() beside the atoms above — one
surface for the boot install and the prompt’s builtin_index alike), landed by
agent-tool-to-exarch-builtin
over the rail engine-protocol built. A
verb’s body validates its arguments engine-side and calls
shell.enquire(class); exarch/src/fleet/desk.rs’s ExarchDesk decodes the class
label and answers from shared handles (HostServices) captured at
install — never &mut Agent — installed per ral call in Agent::run_shell
and swapped back to an absent desk immediately after. A closed label set the
retiring JSON tools validated as a schema enum is an open row checked at the
door instead of a closed variant type: an unknown label errors before any
enquiry crosses, naming the legal set, rather than a static row-unification
error with no room for a didactic message.
The desk answers six classes: three families and three singletons.
`agents, `schedules and `context each carry a tag naming what
to do (family_tag), and an unrecognised tag is as loud one level down as an
unrecognised class is at the top (unknown_tag) — never a silent default.
The three singletons — `pin-read, `pin-list, `transcript —
take a bare payload read positionally (payload_list and the scalar
accessors); a family tag’s own record crosses by field name, through
FOValue::try_from(&Value) and out through Fields, `fold’s included. The desk’s decode is not a
duplicate of the builtin’s door but the trust boundary: the door checks
engine-side so a bad value reaches the model with the parser’s own message, and
the desk checks again because a guest can send whatever it likes — which is why
CronSchedule::parse runs on both sides deliberately.
Context stewardship
The context verbs address the model view by the exchange and digest reaches
reported by `survey; they do not expose the forensic event ledger as a
queryable store.
-
context <tag>→∀ρ. <survey | drop [Int] | fold [through: Int, digest: Str] | ρ> → F [spans: [[exchange: Int, kind: Str, prompt: Str, bytes: Int, steps: Int, live: Bool]], total-bytes: Int, total-steps: Int]. One verb per addressable state: the tag selects the transition, and every tag answers the survey afterwards. That is not a shared prefix collapsed but the rule the registries already follow, and it fits the view better than either, because an edit changes what is addressable — the swallowed exchanges stop being nameable and the digest answers to its reach — so the edit is also the resurvey the next edit must be written against.`surveydescribes the finite view, one span per exchange, import, or digest, a digest named by the last exchange it reaches. It changes nothing.`drop <exchanges>sheds whole closed exchanges. The live, unknown, folded, duplicate, or empty selection is refused with an explanation; a user-shaped rewind is the same closed-range operation.`fold [through, digest]replaces the visible prefix through a closed exchange with the supplied digest. The reach may extend the current digest but cannot cross the live exchange.
Each edit records a
ContextEditedmodel event at the desk immediately. There is no byte-delta receipt: the decision-relevant number istotal-bytesnow against the budget, and a bad fold is diagnosed better by the digest’s ownbytessitting beside the spans it replaced — that says where the weight is, not merely that it moved (context-is-a-projection). -
transcript <exchanges>→F [Str]. Reads named closed exchanges back as material: one role-marked, step-delimited string per span, ordered by the view rather than by the argument, each opening with the=== … ===header that is its address. It may name a digest by its reach, but not an exchange swallowed by that digest. It is not a tag ofcontext, because it is the one harness verb whose answer is the size of the thing it describes: the survey spends a few hundred bytes to describe a 200 KB view, and this returns the 200 KB. A distinct name is the cheapest safety mechanism available on a model-facing surface, and the only one that acts before the call rather than after. The list is what makes the doc’s own advice sayable — a slice is$t[0], a count islength $t— where a concatenatedStrleft the header load-bearing as a boundary the reader had to re-parse. -
agents <tag>→∀α. F α. One verb for the fleet, over an open row of six tags —`list,`start,`message,`cancel,`reply <value>,`read <name>— each taking one argument. Every tag but`readanswers with the roster afterwards,[[name: Str, state: <busy|waiting-on-agents|replied|waiting>, idle-s: Int, elapsed-s: Int, log-dir: Str]], rather than a receipt of its own;`readanswers[name: Str, reply: α], the value a replied child deposited, which is why the family’s answer type is a bareα(thepin-readprecedent) rather than the roster it once was (reply-parks).`replyis the sole return path of a returning agent — first-orderness checked at the door, refused on every non-returning agent with the desk’s own didactic text, last write wins within a run, deposited once the enclosingralbatch drains. The outer row is open so an unknown tag reaches a door that enumerates the six; each known tag’s payload keeps its exact type, so the closed record inside`startstill makes a missing or misspelled field a static error naming it, while thetype/grantrows inside that record stay open for the same reason one level down.`start [prompt: …, name: …, type: …, grant: …, search: …]is the one spawn: launch-only and always asynchronous, a one-line notice arriving through the inbox when the child replies, and the child’s row in the answer carrying thenameandlog-dirthe old receipt did.nameis the child’s identity — the tab-bar contract (check_name, infleet.rsbeside the rule it belongs with), unique among live agents or the call is refused; this door refuses a malformed one early and in the model’s own words, andFleet::enrolrefuses it again for peers that never came through a door (agent);typeis`amnemon(blank context) or`mnemon(imports the parent’s model-visible context before the fresh final prompt);grantis one of the five spawnable base names (confined,read-only,edit-only,reasonable,dangerous);searchis aBooladmitting the provider’s own hosted web search, clamped at the desk to at most the caller’s own bit, which the trunk takes from the IT network policy’ssearchverdict (agent). Fuel bounds delegation depth, not fan-out — refused only once the caller’s ownfuelreaches zero.`message [to: …, text: …]and`cancel <name>are descendant-only, resolved by name and enforced at the desk; a scope violation raises. Where a removed schedule is simply gone from its answer, a cancelled agent is not:Agent::cancel_treeonly sets the cooperative token (and stamps the eval reach), so a successful cancel answers with a roster that still lists the target — a request, not a transaction, and the one place the rule needs a sentence of its own. -
schedules <tag>→F [[label: Str, trigger: Str, next-s: Int, fires: Int]]. The same shape over`list,`add,`remove.`add [trigger: …, label: …, prompt: …]takes a closed record — a record literal infers an exact row, so a missing or surplus field is a static error naming it, which also sidesteps ral’s grammar footgun where a nullary tag would otherwise absorb the following positional atom;triggeris`cron '<expr>'or`after '<dur>'over an open row,labela requiredStrand the schedule’s identity. The receipt’snext-sis not lost but recomputed: the new schedule’s row in the answer carries it, which is still how a mis-meant cron is caught at arm time.`remove <label>really does answer by absence, sinceScheduleRegistry::unscheduleremoves the entry under the lock — the half of the rule the fleet cannot honour.next-ssaturates toi64::MAXfor a cron with no next occurrence. Every tag is gated on the--allow-schedulegrant, refused with a didactic text that names the tag the model actually typed — never a spelling it was never taught.Both verbs issue their transition and then the list, so a raise does not imply nothing happened — the act may have landed and the re-read failed. The audit is unchanged:
`listcommits noDeskActin either family, so each transition is still one act. -
pin-read <key>→∀α. F α. Enquiry over the caller’s own pin register: the card pinned atkey, canonically re-encoded (cards) so a kit can destructure it whether or not the bytes it wrote match what comes back;()on a miss or an absent register. Typed on thefrom-jsonprecedent — trusted, not checked — because the register is schemaless by design (register-is-read-write). -
pin-list→F [String]. Silent; the keys currently occupied on the caller’s register, inBTreeMaporder — a key names a slot forpin-read, not its content.
Receipts and listings are ral records the model can bind, filter, and fan out
over, rather than stringly-typed JSON it re-parses — the composability the
retired tool form lacked. Acting verbs render as acts — the
Display::HarnessCall/Forensic::HarnessResult rail pair
(harness-calls-are-acts; a spawn
additionally derives a child tab);
listings stay silent, since their value is the returned record.
tools is what remains a tool.
Where to look
exarch/src/shell_eval/builtins.rs(+builtins/fff_index.rs) — the Rust atoms, their type schemes, andEXARCH_BUILTINS.exarch/src/shell_eval/builtins/harness.rs— the harness verbs above,HARNESS_BUILTINS.exarch/src/shell_eval/skill.rs— the skill scan behindskill-list/skill.exarch/src/fleet/desk.rs—HostServices,ExarchDesk, and the handler for each enquiry class.exarch/data/agent.ral— the helper library; seeded byboot_shell(exarch hub).- The model-facing tool that carries every one of these calls is `ral`.
- There is no model-facing network builtin: a guest reaches the network itself, policed host-side — egress, agent, synod.