Expand description
spawn — the model’s only route to a command and to a subagent.
ADR-0016. Two doors existed for do something I cannot do by thinking —
mentra’s shell builtin and its task intrinsic — and they were governed
as if they were unrelated: two side-effect levels, two names at basis’s
ApprovalGate, and therefore two rule
namespaces for what an operator thinks of as one question. This is that one
question, with one name on it.
§The shape the model sees
One string. A leading ! means run this; anything else is a task for a
subagent. A prompt that genuinely starts with ! is written !!. The
prefix is surface: it is read exactly once, at the boundary, and everything
after that dispatches on the typed mode — which is why the sugar costs no
type confusion between the approver, the rule store and the audit trail.
A command may also say where: !@<target> <command> names an executor to
run it on (ADR-0021) — though nothing currently registers one; see
docs/targets.md. One more fact on the same act rather than a second
tool, because a second tool would be a second name at the gate and a
second rule namespace — the two doors ADR-0016 closed, rebuilt by hand.
The prefix is read by the same parser, in the same pass, and the target it
leaves behind is a typed field like the mode beside it.
§What the approver sees
ToolExecutor::authorization_preview is overridden rather than left at
its default, which merely restates the static descriptor. A command reports
ToolSideEffectLevel::Process and a delegation reports
ToolSideEffectLevel::LocalState; both are consequential, so
is_consequential never waves either
through under the reads-are-never-asked rule. The preview’s
parser owns {mode, body, target}; after the tool context resolves the
working directory, structured_input carries {mode, body, cwd, target} — the thing an
approver renders, and the thing mentra globs a remembered rule’s pattern against
(RuleStore::matching_rule). A RuleKey { tool_name: "spawn", pattern } is
therefore a command allowlist expressible as data. A delegation whose child
policy (ChildSpec, D4) overrode something adds a child key describing
what the child will be — roster, model, whether the system prompt is
replaced — and only then, so the four-key shape every existing rule was
written against is untouched on a runtime with no policy.
Order matters and is mentra’s, not basis’s: the orchestrator builds this
preview and puts it to the authorizer before it calls execute_mut
(ToolRuntime::execute_registered_tool), so nothing here runs ahead of the
answer. A preview that returns Err is a refusal in its own right, reaching
the model as Tool execution denied: … without the approver — or the rule
store — ever being consulted. That is where the depth guard lives, because
a structural floor is not a thing an allow-rule should be able to lift.
§What this is not
Approval is policy, not confinement (ADR-0013). An approved command runs
with the full authority of this process, and --no-shell remains the only
thing that shuts commands off entirely: it sets
RuntimePolicy::allow_shell_commands(false), and mentra’s policy refuses on
the same path spawn calls — after this tool has been authorized, before
anything executes.
Structs§
- Child
Context - What
spawnknows about a delegation when it consults the child policy. - Child
Spec - Which of a child’s three inherited facts a policy overrides.
- Reach
Policy - What a host allows
reachto be, and what it means when the model says nothing (ADR-0037 §3). - Spawn
Input - Every fact the spawn input parser owns: mode, normalized body, and optional target — where a command runs, or which remote agent a task is for. The working directory is not model input; the tool context resolves it later when building the authorization preview.
- Spawn
Tool - basis’s own tool, and the first tool basis registers on a runtime it builds.
Enums§
Constants§
- DEFAULT_
DELEGATION_ DEPTH - How many levels of delegation
spawnwill start, absent aRuntimeBuilder::with_delegation_depthoverride. - SPAWN
- The name the model calls, an operator writes in a rule, and a
.basis/hooks.jsonentry matches on. - SPAWN_
INPUT_ FIELD - The field the model fills in. One string, because the tool takes one thing.
- SPAWN_
REACH_ FIELD - The one optional field beside it: which agents
@<name>may reach (ADR-0037 §3). Typed, never read out of the string.
Functions§
- classify_
spawn_ input - Classifies the lexical spawn mode without validating that the body is executable.
- parse_
spawn_ input - Reads a tool call’s JSON input into a typed call, or says why it could not.