Map: exarch / tools

ral is exarch’s tool. Every other harness affordance — spawning, messaging, cancelling, scheduling, replying, and reading a reply — is a ral builtin, not a provider-advertised Tool, reached by writing ral inside ral itself, per the agent-tool-to-exarch-builtin migration; see builtins for the verbs and engine-protocol for the desk they speak through.

Two files hold the seam that makes this parametric. provider/tools.rs is the wire half: a ToolSpec is a static record — name, description, schema fn — (RAL, THINKING; RAL_TOOL names the one every agent is offered), and Toolset is a Copy slice of them: Toolset::offered(thinking) is ral alone or ral plus thinking; Toolset::default() is empty (--chat). agent/tools.rs is the dispatch half: dispatch matches a name to its module. The fleet’s Launch carries one Toolset, and both provider.complete (which puts Toolset::wire() on the request) and Avatar::invoke (which asks Toolset::contains before it dispatches) read it, so what was advertised and what is recognised cannot disagree; an unadvertised name earns unknown tool and a Forensic::Error. Every fork and desk spawn inherits its parent’s set verbatim. Malformed input is answered through the shared required_str/input_error (agent/tools.rs), in the same words for every tool; a missing field is answered with the fields the call did carry, so the model can see what it sent.

  • ral (agent/tools/ral.rs) — the one call that crosses the provider boundary: evaluate ral source against the session shell, synchronously, through Avatar::ral and `run_shell`. Its input is a required command (the ral source) and a required one-line description (shown on the rail; the full command opens in the collapsible tool-call block; oversize descriptions are truncated, never rejected). An optional timeout_secs bounds the call, defaulting to CALL_TIMEOUT_SECS (60s) — a default, not a cap: raise it for a command known to run long, rather than spawn-then-wait, and keep spawn for work the model can overlap. A call the wall cuts short keeps what landed before it, so its stderr carries the engine’s span, the remedy, an audit of what already stands, and the spawned workers still running past the wall, each named #id (title) so await !{worker N} reaches it whether or not its binding landed (shell-eval). Every accepted or malformed call emits a Display::ToolCall followed by a Display::Verdict addressed by the call’s BlockId, so a verdict never has to search backward for its tool row. Verdict::of(content, failed) is the result’s line count and failed — rejected, raised, nonzero, or severed — which the view fold settles onto the call, and the TUI draws a failed call’s script in solid red; the result text itself is recorded once, in Protocol::ToolResults. Malformed JSON uses <invalid input> as the call label but still gets a paired diagnostic verdict; if the call row itself cannot be appended, the seam reports a transient fault and cannot invent a verdict target.
  • thinking (agent/tools/thinking.rs) — offered only under the hidden --thinking-tool flag: a relay whose one field, thought, is recorded as a single Display::Thinking — the same lane the provider’s own reasoning commits on, so a thought wears the ∴ rail, the drained ink, and the /thinking dial (newline-terminated, since the view fold grows one thinking block by every thinking record that follows) — and answered with the bare relayed. Nothing else happens — no call row, no model-view twin — so a model may narrate between calls without ending its turn. A record failure is a seam fault, not a tool error: the model still gets its acknowledgement.
  • agent/spawn.rs — not a tool module but the spawn spine every launch shares: spawn_async, AsyncSpawn, SpawnedChild. The attended roots (main, a resumed one, a /new or /branch root hatched by spawn_root) and the desk’s exarch-agents `start handler build on it — either arm of it, in-process or across a wire — so a root and a harness-spawned child run one mechanism (agents, agent).

The harness verbs are answered by the ExarchDesk (exarch/src/agent/desk.rs), installed per ral call and reached through shell.enquire(...) from the builtin’s body; acting verbs emit Display::HarnessCall/Forensic::HarnessResult and, on the arm where the act genuinely landed, file it in the call’s act ledger for the audit every raise owes the model (shell-eval). They are rendered as acts — verb, subject, payload rows that never fold into an observation run (harness-calls-are-acts; spawns additionally derive a child tab) — while listings stay silent since their value is the returned record, the whole read-only `exarch-transcript family among them. There is no Gate/tools_for axis any more — a fresh model never even sees a verb the desk would certainly refuse: agents is dropped from the per-agent builtin index when the agent neither spawns nor returns (its `reply needs only a returning role, so a fuelless returning leaf keeps the verb), and the self-wakeup family when the agent lacks the schedule grant (agent/index.rs’s BuiltinIndex, resolved once against the boot shell), while authority itself is still enforced only at the desk, never by omission.