No Agent Output Ships Unverified: The Handle-and-Check Rule
An agent that reports “file created and deployed” is telling you a story about work, not showing you the work. The house rule for multi-agent pipelines is short: no output ships until the handler has opened a checkable handle for it. Everything below is about making that rule mechanical instead of aspirational.
Why the completion message is the weakest part of the pipeline
An agent’s final message is generated after the action, in the same channel as its reasoning, tuned to sound finished. It is the one part of the run that is never checked by the thing that produced it. When four agents hand work to each other, that optimism compounds: agent A says it wrote the file, agent B trusts the summary and builds on it, agent C publishes a page referencing content that may be a paragraph of intent.
The fix is not a better prompt asking agents to be honest. Prompts are advisory and fail silently at exactly the moment you need them. The fix is a contract: the agent must return a handle, and the receiver must resolve that handle independently. If the handle does not resolve, the task is not done, regardless of how confident the message reads. This is the same discipline as giving each layer of a stack one job, covered in how to wire an AI dev stack so each layer has one job.
Define “handler” as whoever receives the handoff: a downstream agent, a script, or a human reviewer. The handler owns the check. The producer never grades its own paper.
What counts as a handle, by artifact type
A handle is anything that can be resolved by a different process than the one that made it. A path, a URL, a row ID, a commit SHA, a job ID. What counts as verification changes with the artifact, and it always means reading the artifact, not the description of it.
| Artifact | Handle | Verification that counts | Does not count |
|---|---|---|---|
| Repo file | Absolute path | test -s, tracked by git, expected headings present | “I wrote the file” |
| Published page | URL | HTTP 200 plus a string from the body | “Deployed successfully” |
| Database row | Table + primary key | SELECT returns the expected column values | “Inserted the record” |
| Deploy / job | Build or run ID | Provider API reports the terminal state | “Should be live now” |
| Pull request | PR number or URL | Open, has a diff, base branch matches | A branch name only |
| Outbound email | Message ID | ID resolves in the provider’s API | “Sent the email” |
| Generated asset | Path + hash | Size above a floor, hash recorded | A base64 blob in chat |
The pattern: handle resolves, content matches what the task asked for, and the evidence is stored next to the run. Record the hash or status code you saw. When something breaks three days later, the difference between “verified” and “reported” is the only thing that tells you where the fault started.
Put the contract in the task, put the check in the code
Ask for the handle when you dispatch the task, not when the answer comes back. A YAML contract travels with the job and gives the handler something to parse. This is the same reason scripts win over visual builders once a workflow has real branching: the check has to be expressible, as argued in visual workflow builders vs scripts.
id: release-notes-2026-05
agent: writer-2
must_return:
- name: body
kind: file
pattern: "notes/v*/release-*.md"
min_bytes: 400
- name: published
kind: url
require_status: 200
acceptance:
- exists
- non_empty
- tracked_by_git
- contains_heading
on_fail: requeue_once_then_escalate
# Handler-side gate. Any failure exits non-zero, so it cannot read as success.
set -euo pipefail
handle="notes/v3/release-2026-05.md"
[ -s "$handle" ] # exists, not zero bytes
git ls-files --error-unmatch "$handle" # tracked, not a stray /tmp artifact
grep -q '^## ' "$handle" # has structure, not a stub
[ "$(wc -c < "$handle")" -gt 400 ] # above the size floor from the contract
code=$(curl -fsS -o /tmp/page.html -w '%{http_code}' "$PUBLISHED_URL")
[ "$code" = "200" ] && grep -q "Release 2026.05" /tmp/page.html
sha256sum "$handle" >> .run/handles.log
For pipelines with mixed artifact types, one small dispatcher keeps the check honest:
import hashlib, os, subprocess, urllib.request
def verify(kind, handle, min_bytes=1):
"""Resolve the handle directly. Never trust the agent's summary."""
if kind == "file":
if not os.path.isfile(handle):
return False, "missing"
size = os.path.getsize(handle)
if size < min_bytes:
return False, f"too small: {size}B"
digest = hashlib.sha256(open(handle, "rb").read()).hexdigest()[:12]
return True, f"sha256:{digest} size:{size}"
if kind == "git_path":
r = subprocess.run(["git", "cat-file", "-e", f"HEAD:{handle}"],
capture_output=True)
return r.returncode == 0, "git_object"
if kind == "url":
try:
with urllib.request.urlopen(handle, timeout=10) as resp:
body = resp.read()
return 200 <= resp.status < 300, f"status:{resp.status} bytes:{len(body)}"
except Exception as exc:
return False, f"unreachable: {exc}"
return False, f"unknown kind: {kind}"
The dispatcher returns a reason string, not a boolean. “too small: 12B” tells the next agent what to redo. “false” tells it nothing and invites a guess.
The one question that catches simulated work
When a report looks suspicious but you cannot say why, ask this:
Does this check read the artifact, or does it read your description of the artifact?
A run that passes only because the summary says it passed is not verified. It is a rumor with formatting. The question has a second use: it catches the case where an agent built a plausible simulation of the work. A directory listing it printed, a URL it described but never fetched, a database row it constructed in its own scratch context. Real handles resolve outside the agent’s context, for a process that shares nothing with it.
Common tells of simulated work:
- The handle only resolves inside the agent’s sandbox, or lives under a temp path that survives one turn.
- The path has a plausible name but the file is a stub or a copy of the prompt.
- The URL returns 200 because it is the agent’s own log page, not the published artifact.
- The ID changes on every mention, which means it was composed rather than received.
- Success is described but the exit code, hash, or provider status is absent.
A single rule of thumb: if you deleted the agent’s final message, would the evidence still be there? If the work only exists in prose, it does not exist.
Failure modes and the two-step recovery
Verification failures are cheap and should be common early. Handle them in two steps.
First, requeue once with the contract attached and the failed reason quoted back. Most misses are a wrong path or a truncated write, and one retry fixes them. Second, if the retry fails, downgrade the artifact to draft and route it to a human queue. Do not let a third attempt run: repeated failures on the same contract usually mean the task is underspecified, not that the agent is unlucky.
Order the checks by cost. Existence first, then type and size, then content, then provenance such as git tracking or a recorded hash. A missing file should never trigger an HTTP request. And log every check with the run ID and the handle, because the run history is the only thing that distinguishes a pipeline you trust from one you hope works. If you are still assembling that pipeline layer by layer, the AI automation guide covers where verification sits in the overall stack.
One more failure worth naming: a valid handle can point at the wrong artifact. The path exists, the file is real, and it is last week’s notes. Content checks catch this only if they assert something specific. “Has a heading” is weak. “Contains the release date from the task input” is not.
What to do next
- Add a
must_returnblock with a handle to every task definition you dispatch today. If a prompt currently asks for “a summary of what you did,” change it to “the path, URL or ID, plus one line of what it contains.” - Write the gate script for your three most common artifact types. File, URL, database row. Run it manually against the last five agent reports you accepted and see which ones fail. Fix nothing yet, just count.
- Rewrite one acceptance check so it reads the artifact instead of the claim. Replace “agent confirmed sent” with a provider API lookup on the message ID.
- Put the question on the team wall: does this check read the artifact, or the description of it?
- If you are tracking several agents by hand across a shared drive, a dashboard with task routing and run history saves the bookkeeping. Cowork Pro is a one-time $59 download at https://slashmaster6.gumroad.com/l/xfhfps that does exactly that. It does not replace the check, but it gives the check a place to live.
Get Cowork Pro
Cowork Pro โ $59, one-time payment, instant download. See the full breakdown on the review page.
Related reading
- The Ultimate Guide to AI Automation 2026
- How I Built a 10-Product Digital Business
- Building an AI Content Factory
๐ Want to try before you buy?
Both samples are 100% free. Enter your email to unlock the download links and get our 3-day AI toolkit drip in your inbox.
No spam. Unsubscribe anytime. We respect your privacy.
Or browse the full catalogue in the Slashman Tools storefront.
Published by slashman413 โ writing practical, evergreen guides on money, productivity, developer tooling and the web. More about this site โ
Frequently Asked Questions
What if an agent genuinely cannot produce a handle for the work?
Then the work is not done. Treat the output as a draft, send it back with the contract attached, and do not let it move downstream until a handle resolves from outside the agent’s context.
Does verification make the pipeline too slow?
The checks are cheap: a file stat, an HTTP status code, one row lookup. The expensive failure is a downstream agent spending a full run building on an artifact that never existed.
Can the agent just verify its own output?
It can self-check as a first pass, but the handler must run the check independently. A self-report reproduces the same optimism that produced the false completion message in the first place.
๐ Recommended Tools
๐ Related Articles
- Every Product We Sell on Gumroad: The Complete CatalogEvery live Slashman Tools product on Gumroad in one place โ AI prompts, agent workflows, dashboards, courses โฆ
- What Makes a Feishu Template Worth Paying ForA breakdown of Feishu template parts: data model, views, permission scopes, automation flows โ and which ones โฆ
- Buying a Template vs Building Your Own: Variables That DecideA practical decision guide for buying a template versus building your own workspace: data model stability, โฆ
- n8n vs Custom Multi-Agent Workflows: When to Stop Dragging Nodes and Write Code (2026)n8n or a custom code multi-agent workflow? An honest 2026 comparison of versioning, testing, cost, LLM โฆ
๐ฌ Free Weekly AI Product Guides
New tools, templates and automation walkthroughs โ plus hands-on updates on the Slashman Tools catalogue. One email a week, zero fluff.
Free forever ยท No spam ยท Unsubscribe anytime ยท Sent instantly