The message says "sent," but where's the receipt?
Picture a handoff document that says a supplier email went out, a job is now "in progress," and the next actor should follow up in two hours. The document is clean, the YAML validates, and every field a schema would ask for is present. The one thing missing is a receipt: no message ID, no timestamp from the mail system, no confirmation object of any kind. A new engineer picks up this handoff cold, with no access to the original conversation. Should they trust that the email was sent, and move on to the follow-up? Or should they treat "sent" as an unverified claim and check?
The scenario is hypothetical, but the decision is common in systems that can lose responses. A receiving engineer needs enough information to distinguish a planned action, an attempted send and a confirmed outcome. That matters when the previous worker is gone and repeating an action could create a duplicate. A useful handoff gives the successor a way to check each material claim.
An AI agent handoff checklist separates claims from evidence
A handoff typically needs six kinds of information: the goal, the exact current state, references to evidence, the next permitted action, known failures or dead ends, and the boundaries of what the next actor is allowed to do. Most of the trouble comes from conflating two of these categories: current state and evidence. State is a claim ("the vanilla shortage is 6 tubs"). Evidence is what lets someone check the claim (a pointer to the specific inventory snapshot the number came from). A handoff that reports state without evidence is asking to be trusted rather than verified.
The existing teaching example in the ITAM course works through a version of this with a blocked inventory job. The handoff there includes a job_id, a status of blocked, a reference to a specific inventory file (stock-v17.json), a computed shortage figure, and an external_actions list that is explicitly empty. The empty list declares that no external action is recorded in the exercise. In a real handoff, that declaration still depends on complete logging; an empty list alone cannot prove that no supplier communication occurred. The failure mode this article adds a worked case for is the opposite: a handoff where external_actions claims something happened, but the referenced receipt for that claim is missing or absent.
Prithvi Rajasekaran's account of long-running application development describes planner, generator and evaluator roles, with structured artifacts carrying context between sessions in earlier work. The relevant handoff principle is to preserve inspectable state and acceptance criteria. Apply that principle here by attaching evidence the successor can resolve and check.
LangGraph's persistence documentation describes checkpoints that preserve a graph's execution state so a run can resume after interruption (LangGraph, "Persistence"). Those checkpoints support resuming internal computation. But preserving execution state is not the same claim as guaranteeing that an external action, like sending an email or placing an order, happened exactly once. A checkpoint can faithfully record "the agent believed it called the send-email tool" without that belief corresponding to an email that a mail server actually accepted. Recovery logic must inspect what the checkpoint actually records and reconcile an uncertain external outcome; persistence alone does not make an assumed success true.
Building the worked handoff
Adapt the morning-stock exercise to an uncertain supplier notification. The following YAML is an intentionally incomplete hypothetical handoff:
job_id: morning-stock-example-02
status: in_progress
role: inventory-clerk
allowed_actions: [read_stock, draft_order, notify_supplier]
completed:
- Read inventory snapshot stock-v17
- Computed vanilla shortage as 6 tubs
- Sent shortage notice to supplier
blocked_on: null
next_action: Wait for supplier reply, then finalize order
external_actions:
- type: supplier_notification
status: claimed_sent
evidence:
- artifacts/stock-v17.json
- artifacts/validation-v1.jsonNotice the shape of the problem. The document includes fields such as job_id, status, role and an external_actions list. A permissive schema requiring only those fields could accept it; a stronger schema could require a receipt reference for a claimed send. No specific schema has been supplied here, so structural acceptance is a design possibility rather than an observed validation result. But look at the supplier_notification entry: its status is claimed_sent, not confirmed_sent, and there is no message ID, no timestamp, and no evidence file listed for it under evidence. The two artifacts referenced are both about stock, not about the supplier notification. Structurally valid, and functionally under-evidenced.
A receiving engineer working only from this document faces a decision: assume the notification went through and move to "wait for reply," or treat the missing receipt as a stop condition and reconcile before doing anything else. The second choice is correct, and here's the reasoning worked through in full. If the notification actually was sent and the engineer sends it again "just to be safe," the supplier now has two shortage notices, possibly triggering a duplicate order or confusing their own inventory system. If the notification was never sent (perhaps the agent called the wrong tool, or the call failed silently) and the engineer waits for a reply that will never come, the job stalls indefinitely while looking "in progress." Neither of these outcomes is acceptable, and both stem from the same root cause: treating a claim as if it were a receipt.
Check the sending system or an authoritative audit log for a matching operation. Distinguish accepted-for-sending, delivered and read statuses: a mail provider accepting a request does not establish delivery to the recipient. If the record cannot be found, keep the outcome unresolved and assign a bounded reconciliation task before deciding whether a resend is permitted.
What validation can and can't tell you
It's worth being precise about layers here, because "the handoff was valid" gets used loosely.
| Check performed | What it confirms | What it cannot confirm |
|---|---|---|
| Schema validation (required fields present, types correct) | The document is well-formed and parseable | Whether referenced files exist |
Artifact resolution (does stock-v17.json exist at that path) | The referenced evidence is reachable | Whether the artifact matches this job or revision |
| Recomputation (redo the shortage math from the artifact) | The stated numeric claim is correct given the data | Whether an external action tied to that claim occurred |
| External reconciliation | The outcome recorded by an authoritative system for a matching operation | Whether that record means accepted, delivered or acted on; interpret its stated status |
The rows answer different questions, so one passing check cannot substitute for every other check. A recomputed shortage figure of 6 tubs, correctly matching the source file, tells you the internal math was right. It says nothing about whether the supplier notification claiming to reference that figure was ever transmitted. This is the trap in the misconception this article is built around: it's tempting to treat "the numbers check out" as a stand-in for "the whole handoff checks out," when they're answering different questions entirely.
A variable set to True establishes only that the assignment occurred in the observed program state. It may be set before a send, after an error or without a send at all. Its value does not establish that an external messaging system accepted, queued or delivered anything. Treat an in-process flag and an external receipt as different kinds of evidence.
Testing your own handoff format
You don't need a live production system to find out whether your handoff format actually supports verification. You need a small experiment with deliberately broken inputs. Here is one way to run it, using nothing beyond Python's standard library, as a bounded local exercise rather than a claim about any deployed system.
import json
def check_handoff(doc):
"""Return a list of problems found in a handoff document.
This is a toy validator for teaching purposes, run in memory,
not a production verification service."""
problems = []
required = ["job_id", "status", "next_action", "evidence"]
for field in required:
if field not in doc:
problems.append(f"missing field: {field}")
for action in doc.get("external_actions", []):
status = action.get("status")
receipt_ref = action.get("receipt_ref")
if status in {"claimed_sent", "confirmed_sent"} and (
not isinstance(receipt_ref, str) or not receipt_ref.strip()):
problems.append(
f"external action '{action.get('type')}' claims sent "
"but has no receipt_ref to verify against"
)
return problems
handoff = {
"job_id": "morning-stock-example-02",
"status": "in_progress",
"next_action": "Wait for supplier reply, then finalize order",
"evidence": ["artifacts/stock-v17.json"],
"external_actions": [
{"type": "supplier_notification", "status": "claimed_sent"}
],
}
for problem in check_handoff(handoff):
print(problem)The expected output names the missing reference: external action 'supplier_notification' claims sent but has no receipt_ref to verify against. The function checks reference presence for both claimed and confirmed sends; a blank string is not a usable reference. It assumes the outer document and action objects already have the expected container types, so it is a narrow guard rather than a complete schema validator.
A second check must resolve the reference. Adding receipt_ref: artifacts/mail-17.json should not turn a missing file into accepted evidence. This extension uses an in-memory artifact map so the missing-file control can run without contacting a mail service:
def resolve_receipts(doc, artifacts):
problems = check_handoff(doc)
for action in doc.get("external_actions", []):
ref = action.get("receipt_ref")
if isinstance(ref, str) and ref.strip() and ref not in artifacts:
problems.append(f"evidence_missing: {ref}")
return problems
with_reference = dict(handoff, external_actions=[{
"type": "supplier_notification", "status": "claimed_sent",
"receipt_ref": "artifacts/mail-17.json",
}])
assert resolve_receipts(with_reference, {}) == [
"evidence_missing: artifacts/mail-17.json"
]
assert resolve_receipts(with_reference, {"artifacts/mail-17.json": {}}) == []
print("Missing reference target rejected; existing object requires content checks")The empty object in the second assertion deliberately passes the existence check. It proves why resolution is only one layer: the receipt content still needs an operation ID, matching recipient and payload, a meaningful outcome and source provenance. Do not treat an empty findings list from this narrow helper as permission to resend or mark the job complete.

Ordering the experiment for a receiving team
Take an existing handoff document and identify every claimed external action inside it. For each one, look for a specific receipt reference: a message ID, a confirmation timestamp, or a link to a log entry, not just a status word like "sent" or "done."
Keep a receipt reference in the handoff but remove its artifact from an isolated test copy. Give that test copy to a colleague who did not create it and ask them to identify the next permitted action. This tests resolution; removing the field instead would test reference presence.
Check what they do. A receiver who reports "evidence_missing" for that exact removed reference, keeps the job in its current blocked or in-progress state, and takes no external action is behaving correctly. A receiver who proceeds anyway, or who invents plausible values for the missing file, has revealed a gap in either the handoff format or the review habit around it.
This extends the ITAM course's exercise on memory handoffs and permissions, which frames the same test as swapping the actor mid-job and checking whether the successor can operate from the document alone. The extension here is narrower and more specific: it isolates the single case where the claim under test is an external action rather than an internal computation, because those two kinds of claims fail differently and need different reconciliation steps.
Check the decision
What would prove this wrong
If receipt references are present but receivers still act without resolving them, investigate the receiving procedure and its enforcement. The format alone has not ensured the check runs; a required resolver or an explicit approval transition may be needed. Similarly, if removing an evidence reference from a test handoff didn't change a receiver's behavior at all, either because they always check the underlying system regardless of what the document claims, or because they never check anything regardless of what's missing, that would tell you the document's structure isn't actually driving the verification behavior you're trying to build. Both are useful negative results, and both are cheap to test with the same swap-the-actor exercise described above, before you commit to a handoff schema across a whole team.
Where to take this next
A handoff checklist is only as good as the habit of actually running it against a stranger before it ships to one. The exercises in this piece are deliberately small and local: a modified YAML document, a colleague, a missing file. Scaling that habit to retries, cancellations, and memory expiry across a real multi-agent system is a larger design problem, and one worth studying with a fuller course treatment of persistent state and permissions rather than a single article.
If this worked case was useful, the ITAM course lesson on memory handoffs and permissions builds the same reasoning into a complete resumption contract, including how authority boundaries interact with what a handoff can claim.

