pub struct ToolCtx {Show 15 fields
pub workspace: PathBuf,
pub shell_timeout: Duration,
pub security: SecurityConfig,
pub output_budget_bytes: usize,
pub spill_dir: Option<PathBuf>,
pub events: Option<UnboundedSender<AgentEvent>>,
pub cancel: Option<CancellationToken>,
pub phase: Phase,
pub withheld: Arc<[String]>,
pub call_id: Option<String>,
pub taint: Option<Taint>,
pub context: Option<Forecast>,
pub work: Option<Work>,
pub compact_requested: Option<Arc<AtomicBool>>,
pub step_escalation: Option<Arc<Mutex<Option<StepEscalation>>>>,
}Expand description
What a tool is allowed to touch.
Fields§
§workspace: PathBufFilesystem tools refuse paths outside this root.
shell_timeout: Duration§security: SecurityConfig§output_budget_bytes: usizeThe byte budget one turn’s tool results share, divided equally across the calls in the batch so one runaway tool cannot starve its siblings (mecha executes a turn’s calls concurrently, so they land together). The old per-tool cap was 200 KB — ~50k tokens, 1.5× the whole local context window, which is not a cap so much as a promise to overflow.
spill_dir: Option<PathBuf>Where an oversized result is saved in full before its transcript copy
is cut. None disables spilling — the cut then names what was lost
instead of where to find it. Per-context on purpose: two eval cases
sharing one spill directory could read each other’s output through it.
events: Option<UnboundedSender<AgentEvent>>The run’s event channel, so a tool that contains a run — a subagent — can surface its progress instead of going dark until it returns.
Display-only, and treat it that way: any tool (including a third-party
MCP server’s) can send fabricated events down this channel, so nothing
that matters may key off it. Conversation state, taint, and run
completion all come from the loop and the caller’s join handle, never
from events. Stamped by Agent::run_in per run; None everywhere
nobody is watching (batch, eval).
cancel: Option<CancellationToken>The run’s cancellation token. A tool that contains a run passes it on,
so cancelling the parent actually cancels the child instead of politely
waiting out its entire run. Stamped by Agent::run_in, like events.
phase: PhaseThe run’s phase. A tool that contains a run passes it on, so delegation
is not the way to get a write executed from a planning run. Stamped by
Agent::run_in, like events.
withheld: Arc<[String]>Tools the run this call belongs to may not dispatch, carried here so a
tool that contains a run — a subagent — inherits the withholding
instead of becoming the way around it. Same reasoning as phase
directly above: delegating from a narrowed run must not widen it.
call_id: Option<String>The tool_use id of the call this context was built for. Stamped per
dispatch (only when events is watched), so a tool that contains a
run can tag its forwarded events with the call that spawned it — two
subagents running in parallel are otherwise indistinguishable to a
renderer.
taint: Option<Taint>The conversation’s taint as of this turn, stamped per dispatch when a
mailbox is attached. The conservative pre-gate value — it includes
what the batch can return, so a read and a message_send in one
turn cannot stamp a clean label on the outgoing message. None means
nobody stamped it, and a consumer must fail closed (treat it as fully
tainted): a subagent’s context, or any run wired outside the loop,
must never pass as a clean sender by omission.
context: Option<Forecast>What the next request is predicted to cost, as of this turn.
Run-scoped state a tool may read, like taint and call_id — and like
them, the loop stamps it without knowing which tool cares. Only todo
reads it today, because a plan is the one place a headroom number
changes a decision; §4.3’s rule is that most state belongs to the
harness and never reaches the model at all.
Never the system prompt. Render order is tools → system → messages with the cache breakpoint on the last system block, so a per-turn value there would re-pay the entire prefix, tools included, on every request. A tool result is where a changing reading is affordable.
work: Option<Work>What this run has actually done, as of this call — the substrate step
appraisal differences (docs/GOAL-SYSTEM-DESIGN.md §5.5).
Stamped like context directly above and read by the same one tool,
for a reason that generalises past todo: the loop owns the trace and
a tool cannot see it, but only the tool holding a plan knows which
span a number belongs to. So the loop supplies the counters and the
tool supplies the boundaries.
None means nobody stamped it — a subagent’s context, a tool called
outside the loop, a test — and a consumer must make no claim rather
than read it as a run that did nothing. Zero work and no measurement
are the opposite findings doctor’s dash exists to keep apart.
compact_requested: Option<Arc<AtomicBool>>Set by the compact tool; read and cleared by the loop between turns.
Shared rather than returned, on cancel’s precedent one field up: a
tool cannot rewrite the transcript — it has no access to it — so what
it can do is ask, and the loop is what acts. None where nothing
registered the tool.
step_escalation: Option<Arc<Mutex<Option<StepEscalation>>>>Set by the todo tool when a just-completed step is an escalation
candidate (docs/GOAL-SYSTEM-DESIGN.md §5.5); read and cleared by the
loop between turns, which makes the one quarantined call and folds a
nudge into the turn if it says to.
compact_requested’s exact shape, for the exact same reason: todo
cannot rewrite the transcript or reach a provider, so what it can do
is ask. None — not merely an empty slot — is what “this run has the
feature off” means; presence is the enablement, like
compact_requested’s own absence-is-the-off-switch.
Implementations§
Source§impl ToolCtx
impl ToolCtx
Sourcepub fn with_workspace(&self, workspace: impl Into<PathBuf>) -> Self
pub fn with_workspace(&self, workspace: impl Into<PathBuf>) -> Self
The same policy pointed at a different root. Used to give one run — an eval case, a batch item — its own isolated copy of a workspace without rebuilding the agent around it. The spill directory is re-derived too: a re-rooted context is a new isolation domain, and inheriting the old one would let its runs read each other’s spilled output.