Map: exarch / shell eval
shell_eval.rs runs one tool call as a ral top-level turn against the
persistent `Shell`. run_shell is a pure request
supplier: it builds a transport-level Source Turn, dispatches it through
ral_core::transport::dispatch_to_report against the agent’s
IdentityTransport, and renders the terminal Report that comes back — the
transport is the canonical turn vocabulary
(enquiry-channel;
a turn, end to end;
run-turn-is-host-api). Evaluation can
be entered only through a framed turn door — the reduction primitive behind it
is crate-private (run-turn-host-loop) —
so core owns all the turn machinery (compile, frame install, capture, the wall),
and run_shell owns only the turn it builds and the outcome it formats:
- source +
script_name: "<tool>". Core’scompile_turnrunscompile_and_typecheckseeded from the live session (shell.session_schemes(), the one name→scheme seed — session-scheme-continuity); the prelude’s schemes ride scope[0], installed when the annotated prelude was evaluated at boot. The check is strict — any type error is fatal — over the single mode-inference engine every evaluated path shares (unconditional-mode-pass). Parse/type errors come back asReport::Static { diagnostics }, whichrun_shellformats toOutcome::Static; on success the annotated comp runs (unify-turn-evaluation); caps— the sessionCapabilities, pushed for the eval’s dynamic extent. This is the sandbox: the boundary is the pushed grant frame plus the top-level contract, not a source-levelgrant { … }the model could escape. External commands route through the same OS sandbox as ral (capabilities). The post-runMobileinstalls onto the shell, solet,cd, and env persist across tool calls (the in-module tests pin this);io: TurnIo::Capture— core mints the stdout/stderr buffers and returns them inReport::Ran’scaptured: the full, model-visible and logged text. Nothing echoes live; the rail surfaces cards instead, and the digest caps shape only the model’s history view;terminal: RequestedTerminalAccess::Denied— a tool turn holds no terminal lease, so the foreground handoff is uninvocable: a bare pipeline cannottcsetpgrp, and the SIGTTIN crash of an agent stealing the controlling terminal becomes a state the types refuse to represent. Paired withstdin: TurnStdin::Empty, the turn reads no terminal at all — an explicit empty source, not a side effect of foreground denial;turn_limit— the per-tool wall, theraltool’stimeout_secs(60s default). Core arms it on the turn’s foreground scope before compiling, so it bounds the whole turn; onlyCancelCause::Deadlinereportstimed_out, whichrun_shellturns into the timeout-124 message (retry with a highertimeout_secs, orspawnand overlap), while Esc stays an interrupt. A grant body evaluates locally — no sandbox-IPC round trip to interrupt — so cancellation reaches any spawned child through the ordinary process-group / cancel-scope path;deferred_lease— the idle-observation lease for workers the turn detaches at the durable root:DETACHED_WORKER_CEILING(1 h) as the idle bound — a worker is reaped once unobserved that long, renewed by anypoll/await/racenaming its handle — underDETACHED_WORKER_BACKSTOP(24 h), the absolute age no polling extends. Not load-bearing for turn exit;worker_cap—LIVE_WORKER_CAP(64), the admission bound core enforces at the spawn door: the 65th spawn is refused (the error namesawait/cancel) while 64 workers of any class still run; settled entries lingering under retention hold no seat. Its sibling constantSETTLED_WORKER_RETENTION(256 ral calls, matching the binding lease’s scratch expiry) is not on the request at all — it parameterises the agent’s per-calladvance_worker_epochsweep; Surface delivery is not aTurnfield:dispatch_to_reporttakes the live and deferred-batch sink closures directly (below, both routed throughfleet::desk::SurfaceApplier), plus an enquiry handler answering through the per-callExarchDeskwhen one is installed (builtins); a desk-less dispatch gets an honestEnquiryError.
BINDING_IDLE_CALLS (256, beside DETACHED_WORKER_CEILING) is the other
lease constant this module owns but does not put on the request: it is not
per-turn policy, it is per-shell policy, armed once — Agent::assemble
and Agent::replace_shell call Shell::arm_binding_lease with it at the
two places an agent’s shell is installed
(agent; agent-binding-reaping).
Reusing the settled-worker-retention figure is deliberate: one ral-call
clock, read by both ledgers for their own idle policy. LARGE_BINDING_BYTES
(1 MiB) rides the same BindingLease on the same two arm calls — a
residency threshold, not a lifetime one, so the install chokepoint checks it
independently of idle age or baseline status.
Completion is dispatch_to_report returning its Report. A detached spawned worker — a
server, a watch — holds bounded deferred surface storage in core, never a clone
of the bus `Emitter`, so it cannot keep the tool turn
from ending (run-turn-host-loop). Frame
teardown — restoring the prior TurnState, its byte sinks, its surface, and its
terminal access — is core’s TurnGuard, which self-heals on a caught worker
panic as well as on the normal return
(panic-recovery); exarch installs no
per-call IoGuard of its own. The dynamic-context half of the contract lives in
agent.
Surface decoding. decode_surface is the single decoder both delivery
regimes share: the live foreground sink run_shell hands dispatch_to_report
emits now, and the deferred sink (deferred_sink, installed on the transport
before each dispatch) mints identical events when a detached worker’s batch
flushes. accepted_surface wraps it with the protected-pin guard; each decoded
Kind reaches the bus through the call’s Emitter.
Five shapes ride the one surface channel, tried pin-first:
- a
`pin/`unpinwrapper decodes toKind::Pin/Kind::Unpin, but the host-ownedserviceskey is protected (protected-commitment-pins): ordinarysurfacewrites or clears there are rejected with a diagnostic before they reach the pin mirror or viewport; accepted pins are mirrored asPinDigests so the nudge layer can name pinned state without parsing rendered text; - an
io-keyedMapcore emits at a redirect / exec door decodes throughvalue_to_io/io_cardinto aKind::Io { event, card }, carrying the raw effect record beside its rendering (io-surface); - a
`noticecore’s ready-boundary housekeeping pushes (a worker reap, an idle-binding prune, a large-binding warning) decodes toKind::Notice { notice, card }(enquiry-channel); - any other value tries
value_to_cardand becomes aKind::Card; the closed mark set and thevalue_to_card/render_carddecode-and-bind path live in cards; - the
`donecompletion event a detached worker flushes at the end of its batch decodes toKind::Done { outcome, card }; - a value that is none of these is dropped, the same graceful degradation
value_to_cardgives an unknown mark.
The producer is a direct surface call at each kit site, with no cross-language
sentinel constant. Same-thread children inherit the sink; detached workers do
not — core buffers their surface calls and replays them once on await, so a
bus Emitter clone can never outlive the tool turn. Across the OS-sandbox
boundary the events are buffered in the confined child and replayed through the
parent’s sink (carried on the IPC response), so they
are batched rather than live under the sandbox.
shell_eval/builtins.rs registers exarch’s resident host atoms — view-text, the
grep-files search, and the hash-addressed edit-hash/edit-replace, whose
file I/O happens in Rust, below the redirect frame
(io-surface) — and sources the small embedded
data/agent.ral helper library (view-text-around, the tasks kit) into the
shell at boot (builtins). The one host_surface()
value declaring these sets rides core’s boot_shell at construction and is
also the builtin surface a wire engine child’s Frame::Attach names, so a
remote shell is dressed with the same atoms.
A tool command that fails under an active OS sandbox carries a kernel-denial
diagnostic — the blocked syscall, the exact path to grant, the symlink caveat —
appended to the error’s hint. That harvesting now lives in core
(core::sandbox::diag), driven by the command and pipeline runners over the
failing call’s wall window and rendered identically by both the ral-sh REPL and
exarch (capabilities).