The Worker Agent Reported Success. Where Should the Check Live?
A worker agent's "done" is a sentence, not evidence — and in effectful work the gap between the two is where silent failures live. Three places to put the verification, and the one most teams pick that buys nothing.
A backfill agent finishes its shift and hands the supervisor a tidy line: reprocessed 14 partitions, all green. The supervisor marks the subtask complete, releases the lock, and moves to the next one. Three days later an analyst notices two partitions are still stale — the worker's final query returned an empty result set, it read "nothing returned" as "nothing left to do," and reported success in good faith.
Nothing in that chain was a hallucination. The worker made a plausible inference from a real tool response and summarized it accurately. The failure is architectural: the only evidence the supervisor had was the worker's own account of itself.
So the design question isn't "how do we make workers more honest." It's narrower and more useful: where in the topology does the check live? There are three answers, they cost different amounts, and most multi-agent systems quietly pick the cheapest one by default.
🔍 The claim is not the outcome
In a supervisor–worker setup, a completed subtask produces two separate artifacts that are easy to conflate:
- The claim — the worker's natural-language report of what it did.
- The outcome — the actual state of the world after its tool calls.
A supervisor reading only the claim is doing something closer to reference-checking than auditing. That's tolerable when the worker's job is generative (draft this, summarize that) and the supervisor can judge the artifact directly. It breaks down the moment the worker's job is effectful — writing rows, moving files, calling a paid API, closing a ticket — because the artifact the supervisor sees is a sentence, not the effect.
The heuristic: if the subtask changed state somewhere the supervisor can't see, the claim is not evidence.
Placement 1 — the worker checks itself
The worker runs a verification step before it reports. It re-queries row counts, diffs its own output against the input manifest, re-reads the file it just wrote.
✅ Cheapest. No extra agent, no extra hop, and the worker already holds all the context needed to know what "done" means for this partition.
❌ Correlated failure. The worker that misread an empty result set as completion will very likely misread its own verification query the same way. Self-checking catches slips — a tool that returned a 500, a loop that exited early — but not misconceptions. If the worker's model of the task is wrong, its check inherits the same wrong model.
Use this as a first line, never as the only one. It's the smoke detector, not the inspection.
Placement 2 — the supervisor re-checks
The supervisor doesn't just read the report; it independently calls a tool to confirm. The worker says fourteen partitions are current, and the supervisor runs its own freshness query against the warehouse.
✅ Breaks correlation. Different context, different prompt framing, often a different tool. A supervisor asking "which partitions are stale?" is a genuinely different question from the worker asking "did my backfill finish?"
❌ Real cost, and it scales with fan-out. Every re-check is tokens, latency, and one more chance for the supervisor to misjudge. Verify ten workers this way and the supervisor's context fills with verification transcripts, which is exactly how supervisors start losing the thread on the coordination job they exist to do.
There's a subtler trap: if the supervisor's check reuses the worker's summary as its framing — "confirm that 14 partitions were reprocessed" — it has re-imported the worker's assumption and bought nothing. The supervisor's check has to be phrased from the goal, not from the report.
Placement 3 — a deterministic gate outside both
No agent verifies. Code does. The subtask isn't complete until a plain function returns true.
def backfill_complete(run_id, expected_partitions):
fresh = warehouse.query(
"select partition from fct_orders "
"where updated_at > %s", run_started_at
)
missing = set(expected_partitions) - {r.partition for r in fresh}
return len(missing) == 0, missing
ok, missing = backfill_complete(run_id, manifest.partitions)
if not ok:
return Retry(reason="partitions still stale", targets=missing)
✅ No judgment, no drift, no token cost, and it fails the same way every time — which means you can write a test for it.
❌ It only works where "done" is expressible as an assertion. Rewriting a runbook, triaging an ambiguous alert, deciding a schema change is safe: those don't reduce to a boolean, and forcing them to produces a gate that passes garbage.
How to choose
Work down this list and stop at the first match:
- Can "done" be written as an assertion over observable state? → Deterministic gate. Always. Don't spend a model call on something
assertcan do. - Is the subtask effectful but the success condition fuzzy (the ticket is adequately resolved, the doc is accurate)? → Supervisor re-check, framed from the original goal, never from the worker's summary.
- Is the subtask cheap, reversible, and easy to redo? → Worker self-check only. Verification should not cost more than the work.
- Is it effectful, fuzzy, and expensive to undo? → Deterministic gate on the parts that reduce to assertions, plus supervisor re-check on the remainder. This is the only case that earns two layers.
⚠️ One placement most teams reach for and shouldn't: a dedicated "verifier agent" that takes the worker's transcript as input. It looks like independent verification and is mostly Placement 1 with an extra API call — same evidence, same blind spot, new bill.
The rewire
The backfill agent gets a manifest of expected partitions before it starts, and a backfill_complete gate after. The worker still self-checks, because catching a 500 early is free. The supervisor stops reading claims and starts reading gate results — its context now holds fourteen booleans instead of fourteen paragraphs, and it recovers the coordination capacity it was spending on prose.
The empty result set still happens. The worker still misreads it. The difference is that the run doesn't close on that misreading, and the missing partitions come back as a Retry with two names attached instead of as an analyst's message three days later.
Trust between agents isn't a property you prompt for. It's a property of where you put the check.