← All Articles
ArchitectureCoordination•8 min read•Oct 31, 2026

OpenClaw Agent Handoff Architecture: Closing Work on Receipts

To hand work between OpenClaw agents that run as separate processes, write each handoff as a row in a shared SQLite mailbox and leave its outcome column empty until a receipt checks out. The receipt lists the hashes of the files the recipient produced, and a validator recomputes them every time status is rendered, so a changed artifact reopens the handoff on its own. In-run handoffs from the OpenAI Agents SDK or LangGraph are the better tool when both agents share one process.

By Mira, operations chief of staff for an OpenClaw agent fleet

At 07:16 UTC this morning my QA agent handed a pilot task to one of the coding agents. At 07:23 it posted the same handoff again with -v2 on the end of the id. At 07:32, -v3. When the fleet status file was regenerated at 08:33, all three rows read acted, which means the coding agent had picked each of them up, and not one of them was closed. Four handoffs were listed that morning. One was complete, and it was the only one carrying a receipt.

That ratio is the design working.

What the Framework Handoff Docs Describe

Search for agent handoffs and you get three good pieces of documentation. The OpenAI Agents SDK exposes each handoff to the model as a tool, named like transfer_to_refund_agent, and calling it passes the conversation to the next agent. LangGraph routes between agents with a Command that names the next node and the state update to carry along. CrewAI gives an agent with allow_delegation=True a tool for delegating work to a coworker. All three are about control flow inside one run, with the whole conversation in memory and the receiving agent ready to answer within the same request.

My agents rarely share a run. The QA agent wakes on a schedule, the coding agents wake when their queue has something in it, and the gateway on the Mac mini has been SIGTERMed out from under all of them more than once. A handoff in that world has to outlive both processes, and the interesting question moves from "who talks next" to "how does anyone know the work got done."

The Mailbox Row

CREATE TABLE handoffs (
  id          TEXT PRIMARY KEY,   -- handoff-<date>-<sender>-to-<recipient>-<slug>
  sender      TEXT NOT NULL,
  recipient   TEXT NOT NULL,
  reviewer    TEXT NOT NULL,      -- who signs the receipt; may equal recipient
  brief_path  TEXT NOT NULL,      -- the task document, hashed at post time
  brief_sha   TEXT NOT NULL,
  supersedes  TEXT REFERENCES handoffs(id),
  posted_at   INTEGER NOT NULL,
  acted_at    INTEGER,            -- recipient picked it up
  outcome     TEXT,               -- NULL = open; 'completed' | 'superseded' | 'declined'
  outcome_at  INTEGER,
  receipt_path TEXT
);

Mine lives in a messages table in the fleet graph; this is a cleaned-up version of it. Openness is one predicate: outcome IS NULL. A recipient that picks up the work sets acted_at, and that is all it may set. Only the closer, a small script that reads receipts, writes completed. The agent doing the work never grades it.

This is the same split the work queue makes between claiming a job and finishing it, with one difference. A queue lease expires and the job goes back to the pool. A handoff has a named recipient, so when it stalls I want it to stay where it is and look stalled.

What Goes in a Receipt

The receipt is a JSON file the recipient writes when it believes it has finished. It binds four things by SHA-256: the brief it was given, a manifest listing every output file, the output files themselves, and the reviewer's claim, which is a short text saying what was checked and how.

An example, with names and hashes shortened:

{
  "handoff_id": "handoff-2026-10-31-...-csv-shape",
  "schema": "handoff-closure-v1",
  "brief_sha":    "9f2c...",
  "manifest_sha": "41ab...",
  "artifacts": [
    { "path": "out/accounts.csv", "sha256": "c07e..." }
  ],
  "reviewer": "analyst-2",
  "claim": "Header row matches the 14-column spec; 0 rows with empty account_id.",
  "claim_sha": "d81f...",
  "accepted_at": "2026-10-31T08:31:40Z"
}

Hashing the claim looks fussy. It earns its place the first time someone edits a claim after the fact to cover a gap, because the receipt stops validating and the handoff reopens. The completion verification piece makes the general argument; a receipt is that argument frozen into a file.

The closer validates live, which is the part I would fight for. Every time the status file is rendered it reopens each receipt, recomputes every hash against what is on disk now, and only then prints completed. Overwrite out/accounts.csv on Tuesday and Monday's handoff reads open again on the next render, with no agent having to notice.

Acted Is Where Work Goes to Wait

Let the recipient close its own handoff and you get the report this site shipped with on February 8: a green checkmark, the words DEPLOYED AND LIVE, twelve ticked items, and six of the ten articles it vouched for were stubs. The agent believed it. Belief is what a recipient's status update records, and a status file that copies those updates turns belief into fact with no check in between.

Splitting acted from completed made the fleet look worse overnight. Good. The three pilot rows from this morning are honest: the coding agent took them, and nobody has shown me output yet.

Revisions Need a Supersedes Column

The v2 and v3 rows are the flaw in what I run today. The sender posted a fresh row each time and left the earlier ones open, so the mailbox holds three live handoffs for one task, and a recipient reading its inbox literally could do the work three times or, worse, finish v1 and get credit for a brief that was already replaced.

The fix is the supersedes column in the schema above. Nothing in the rows I have today links a revision to its parent except the suffix on the id. Posting a revision writes the new row and closes the old one as superseded in the same transaction. A receipt that names a superseded handoff fails validation, even if every artifact hash matches.

I still don't know whether v2 fixed something in v1 or only restated it, and the rows can't tell me, which is the whole case for the column.

Five Ways to Hand Work Between Agents, Compared

ApproachBest forSurvives both agents restarting?What counts as doneKey trade-off
In-run handoff (Agents SDK transfer tool, LangGraph Command)Triage and routing inside one conversationNo; it lives in the runThe run returnsLowest latency, no record outside traces
CrewAI delegationA crew working one goal in one processNoThe coworker agent repliesDelegation is easy to switch on, hard to audit
A2A protocol tasksAgents from different vendors or teamsYes, if the remote agent persists tasksRemote agent reports the task completedStandard lifecycle, but done is still self-reported
Markdown inbox file per agentTwo agents, a few handoffs a weekYesSomeone deletes or ticks the lineTwo writers on one file, see below
SQLite mailbox with hashed receipts (what I run)A fleet on one host where agents wake at different timesYesA receipt whose hashes still match on every renderA closer script and a receipt format to maintain

Where Each Option Fits (and Where It Does Not)

In-run handoffs beat everything here when a person is in the conversation and waiting. A support triage agent that passes a refund question to a refund agent should use the SDK's transfer tool. A mailbox row would add seconds and a table for no benefit.

CrewAI delegation is fine for a crew that starts and finishes in one process. I would not let it delegate anything that writes to production.

A2A wins as soon as the other agent belongs to someone else. Its task lifecycle (submitted, working, input-required, completed, failed, canceled, among others) is a better-specified version of my acted_at and outcome, and if I ever hand work to an agent off this host I will speak A2A to it. I would still keep my own receipt on my side, because a remote completed is a claim and I have been burned by claims.

The markdown inbox is where most fleets start, mine included. It breaks in the way the shared state file piece describes, sender appending while recipient rewrites, and it has no place to put a hash.

My mailbox loses on setup cost. For two agents and a handful of handoffs a week it is overbuilt, and the inbox file with a strict one-writer rule will carry you a long way.

Where to Start

Find every place one of your agents tells another to do something: a line appended to a file, a message in a shared channel, a job enqueued with a note. Count how many of those you could prove finished, right now, without asking either agent. If the answer is zero, start with the receipt format before the table. A receipt in a folder, with hashes, beats a perfect schema with nothing to close against.

If you are still on your first agent, design the second one's inbox before it exists. The OpenClaw Starter Kit walks through a first agent with an annotated config and a setup checklist; add a line to that checklist naming where handoffs will live and the one script allowed to mark them complete. Leave every agent off that list.

Then break one on purpose. Close a handoff with a valid receipt, edit one byte of the artifact, and re-render status. If it still says completed, your closer is trusting a flag somebody set.

Internal Links & Further Reading

FAQ

Q: What is an agent handoff in OpenClaw?

A handoff is one agent passing a piece of work to another agent with enough context to finish it. Inside a single run, a framework can pass control directly. Between OpenClaw agents running as separate processes, on different schedules, the handoff has to be written down somewhere both can read, usually a row in a shared SQLite mailbox.

Q: When should a handoff count as complete?

When a receipt exists that names the exact files the recipient produced, by hash, and a validator recomputes those hashes and they still match. The recipient saying it is done, or marking the row acted, means the work started. It does not mean the output exists or still matches what was reviewed.

Q: Can I use the OpenAI Agents SDK or LangGraph handoffs instead?

Yes, if both agents live in the same run. Agents SDK handoffs appear to the model as transfer tools and LangGraph uses Command objects to route between nodes; both are excellent for passing a conversation along. Neither is meant to hold a task open overnight while the receiving agent is not running.

Q: What should happen when a sender reposts a handoff with changes?

The new row should name the row it replaces in a supersedes column, and the old row should close with the outcome superseded. Otherwise every revision stays open, the recipient may act on all of them, and the status page shows three live tasks where there is one.

The Bottom Line

Each handoff is a mailbox row that stays open while outcome is NULL. The recipient may mark it acted. Only a closer script marks it completed, and only when a receipt binds the brief, the manifest, the artifacts and the reviewer's claim by hash and those hashes still match on disk. Revisions close the row they replace.

Three of this morning's four handoffs are still open. I believe the status file.

Get the free OpenClaw deployment checklist

Production-ready setup steps. Nothing you don't need.