pub struct Work {
pub calls: u32,
pub failed: u32,
pub refused: u32,
pub verify_like: u32,
pub shell_calls: u32,
pub last: Option<Outcome>,
pub in_flight: u32,
pub denied: u32,
pub run: u64,
}Expand description
The run’s work so far, as of one tool call.
Cumulative, and only ever read as a difference between two points — the
turn a step went in_progress and the turn it was marked done. That is why
it is a handful of integers rather than a list: the span is arithmetic, and
keeping the calls themselves would make this a second copy of the trace.
Stamped on ToolCtx by the loop, which does not
know which tool cares — the taint and call_id precedent.
Fields§
§calls: u32Every attempt, refusals included. A step that tried three times and was refused three times is blocked, not empty, and counting only what ran would report it as having done nothing.
failed: u32§refused: u32§verify_like: u32Successful calls that look like they verified something — a shell
call whose command matches a small test-runner-shaped keyword list.
See [looks_like_verification]. Folded here rather than kept as a
raw trace for the same reason calls/failed/refused are: the span
is arithmetic, and a tool asking “was there a check in this span”
needs a count, not the calls themselves.
shell_calls: u32Every successful shell call, matched or not — the denominator
verify_like needs to mean anything, so it has to count the same
population verify_like draws from (Outcome::Ok only). A refused
or failed shell call never ran, and counting it here would reopen
exactly the false positive this field exists to close: on a
read-only run (shell denied) or a surface where shell is not
registered at all, that is precisely the shape a step’s one attempt
takes. looks_like_verification can only recognise a check shaped as
shell, so verify_like == 0 is ambiguous on its own: it is true
both when a step’s checks used some other tool (an MCP test runner,
cargo check-via-a-non-shell wrapper) and on any surface where
shell is not even registered (a mail-only trigger, a
tools:-narrowed skill, a read-only run) — cases where nothing could
have set the counter regardless of what actually happened. See
escalation_candidate’s UnverifiedClaim branch, which reads this
alongside verify_like for exactly that reason.
last: Option<Outcome>How the most recent attempt ended. None before the run makes one.
in_flight: u32Calls approved in this turn whose results are not back yet — the siblings of the call reading this.
mecha executes a turn’s calls concurrently, so a model that does the work and ticks the box in one batch has that work invisible to the fold below. Without this the commonest efficient shape in the corpus would report as the null step, which is the false positive that would teach people to ignore the reading.
denied: u32Calls settled this turn without becoming approved work, whose target step is unknown to the harness. Named for the commonest case (the approver, a hook, the interlock) but not only that: an unknown or withheld tool name and a failed staging attempt settle the same way, without ever being denied by anyone.
Any of these is settled the instant it happens — unlike in_flight
it is already in the trace — but the batch it happened in is exactly
the shape in_flight exists for: a model ticking a step and
reaching for the next one’s tool in the same turn. trace.push for
one of these runs ahead of the calls it approved, so Work::of folds
it in as the raw trace’s last entry regardless of which call it sat
beside — blaming this step for an outcome that belongs to the
next one. Carried alongside in_flight for the same reason: a batch
holding either supports no finding at all.
run: u64Which run these counters belong to.
The trace is per run and a conversation is many runs. In chat and
the TUI one submission is one run, so the counters restart at zero
while the plan carries on — and a step started before the user last
spoke would difference against a larger number, saturate to zero and
report as the null step. That is the loudest reading this module has,
firing on the commonest shape there is, which is how a check gets
switched off. So a mark from another run is unmeasurable rather than
empty, and Work::since says so by returning nothing.
Only inequality is ever read, which is what makes a process-local counter enough: two runs in one process must differ, and nothing compares this across processes or across a restart.
Implementations§
Source§impl Work
impl Work
Sourcepub fn of(trace: &[ToolCallTrace]) -> Self
pub fn of(trace: &[ToolCallTrace]) -> Self
Fold the run’s trace so far.
Called once per turn, not once per call: the numbers are the same for every call in a batch, and the walk is over every call the run has made.
pub fn with_in_flight(self, n: u32) -> Self
pub fn with_denied(self, n: u32) -> Self
pub fn in_run(self, run: u64) -> Self
Sourcepub fn since(
&self,
start: Work,
bookkeeping: u32,
last: Option<Outcome>,
) -> Option<Span>
pub fn since( &self, start: Work, bookkeeping: u32, last: Option<Outcome>, ) -> Option<Span>
Work done since start, less bookkeeping calls the caller knows were
its own.
The adjustment exists because the only caller is a tool that appears in its own count. A model that revises its plan three times mid-step would otherwise show three calls of “work” for a step where nothing happened — the null step masked by the bookkeeping that announced it. The argument for putting the subtraction here rather than in the loop is the loop’s own invariant: it stamps run state without learning which tool reads it, so “the plan tool is not work” is a judgement only the plan tool can make, and this is where the arithmetic it needs lives.
last is the caller’s to supply, and self.last is the wrong
answer. self.last is the raw trace’s most recent entry, which is
this same bookkeeping tool’s own call whenever one lands last — a
successful revision masks an earlier failure (EndedOnFailure never
fires), and a rejected one, which never reaches this method’s caller
at all, reads as the step’s own failure (EndedOnFailure fires on
work that landed). bookkeeping is a count and cannot say which
position it occupied, so only a caller tracking its own calls as they
happen — [crate::tool::todo::Tracked] does, incrementally — can name
the outcome that actually belongs to the span.
None when start was taken in another run — see Work::run. An
unmeasurable span supports no finding, which is doctor’s dash one
mechanism over: could-not-look and nothing-happened are opposite
answers.