Handoff Between Sessions and Agents
5 min read · updated August 3, 2026
Sooner or later a run has to cross a boundary: the context filled, the user came back tomorrow, a specialist agent took over, or you changed model. What crosses that boundary should be a document, and it should be one you could hand to a person.
Three boundaries, one document
The three cases look different and want the same artefact, which is the useful realisation because it means you build it once.
- Session to session. The window filled or the user left. Tomorrow’s session should start knowing where yesterday got to without replaying it.
- Agent to agent. A generalist hands to a specialist, or a long run is split across workers. The receiver needs the state, not the deliberation that produced it — which is the isolation argument.
- Model to model. You moved from an expensive model to a cheap one for the routine phase, or a provider had an incident, or the model you were using was deprecated mid-project. This is the case that decides the format, because it is the one a transcript cannot survive.
Why a transcript does not survive
The instinct is to serialise the message array. It is already there, it is complete, and it replays. It is also bound to the specific runtime that produced it in at least five ways.
| Coupling | Description |
|---|---|
| tool call ids | Assistant messages reference tool calls by provider-issued identifiers that a different provider will reject, and that your replay cannot regenerate. This is usually the first hard error on a cross-provider replay. |
| role and block structure | System vs developer roles, thinking blocks, multi-part content, tool-result message shapes — all differ between APIs, and a mechanical translation loses or invents structure. |
| reasoning traces | Where a model emits hidden thinking, that content is bound to the model that produced it and is frequently not accepted back by another. Replaying it is either impossible or meaningless. |
| cache assumptions | A transcript designed around a stable prefix is optimised for one provider's caching. On another it is just a large prompt with no discount. |
| size | A transcript is the thing you were trying to escape. Handing over 90,000 tokens to solve a context problem starts the new session at the point the old one broke. |
A handoff document has none of those couplings because it contains no runtime artefacts at all. It is a statement of where the work is, in plain text and plain structure, and it is portable for exactly the reason a person could read it.
The format
{
"version": 1,
"objective": "Migrate the billing service from MySQL 5.7 to Postgres 16.",
"status": "in_progress",
"constraints": [
"No downtime window longer than 5 minutes.",
"Must keep the existing invoice ID format.",
"Budget: 20 engineer-hours."
],
"decisions": [
{ "what": "Use logical replication, not dump/restore",
"why": "dump/restore exceeds the downtime budget at 240GB",
"at": "2026-08-01T14:12:00Z" }
],
"rejected": [
{ "what": "AWS DMS", "why": "no support for the custom ENUM types" }
],
"done": [
"Schema translated and applied to the target (see schema.sql)",
"Row counts verified for 14 of 21 tables"
],
"next": [
"Verify row counts for the remaining 7 tables",
"Write the cutover runbook"
],
"open": [
"Unclear whether the reporting replica needs to be rebuilt."
],
"artifacts": [
{ "name": "schema.sql", "kind": "file", "ref": "work/schema.sql" },
{ "name": "count-report", "kind": "file", "ref": "work/counts.json" }
],
"provenance": { "turns": 47, "produced_by": "planner", "at": "..." }
}Every field earns its place by being something a fresh session cannot reconstruct. rejected prevents the successor from proposing the thing that was already ruled out — the highest value-per-token content in the document. why on each decision prevents re-litigation. artifacts are references so the document stays small while the work stays reachable. open is what stops a confident successor from inventing an answer to a question the predecessor knew it did not have.
Notice what is absent: no messages, no tool calls, no model name in any load-bearing position, no assumption about how the receiver will be prompted. The document describes the work, not the process that produced it, which is precisely what makes it portable. It is the same object as a compaction record, promoted to a durable artefact with provenance attached.
Resuming from it
The receiving side renders the document into its own first turn and then proceeds normally. Three details separate a resume that works from one that quietly restarts:
- Render it as data, not as narrative. A block that is clearly a state document is treated as reference; a paragraph describing what happened is treated as conversation and gets compacted away three turns later.
- State the resume contract explicitly. The receiver needs to be told that
doneis settled andnextis the queue, or it will helpfully redo completed work. This is the one place the handoff needs a sentence of instruction around it. - Re-read artifacts, do not trust their summaries. The document says
schema.sqlexists; the first thing the new session should do is read it. World-state drifts between sessions — files change, deploys happen — and the artifact is ground truth where the document is a claim.
Testing a handoff
A handoff format is testable without a model, which is unusual for anything in this area and worth exploiting.
The cold-start test. Give the document to a person who has not seen the session and ask them what to do next. If they cannot say, no model will do better. This catches the two commonest defects — a next list that assumes context, and decisions recorded without reasons.
The swap test. Resume from the document on a different model family than the one that produced it. Anything that breaks was a coupling you did not know you had. Doing this deliberately once is much cheaper than discovering it during an incident, and it is the concrete reason the format excludes runtime artefacts rather than merely discouraging them.
The round-trip test. Produce a document, resume from it, immediately produce a second document, and diff. Fields that silently disappear are fields your renderer is not actually surfacing — a very common failure where rejected and open are written by the compactor and never rendered into the resumed session, so they decay to nothing after two handoffs.
The round-trip test also settles a design argument that otherwise runs forever: whether the handoff should be generated by a model or assembled by code. The answer is both, in fixed roles. Code owns anything it can know for certain — artifact paths, timestamps, turn counts, which items in next have since been marked done — and a model owns only the fields that require reading the conversation, such as the rationale behind a decision. Letting the model rewrite fields the code already knows is how a path becomes plausible and wrong.
Finally, version the format from day one, which is why version is the first field above. Handoff documents outlive the code that wrote them: a run paused on Friday is resumed on Monday by a deployment two commits newer, and a reader that silently ignores a field it does not recognise will drop constraints without saying so. An explicit version lets the reader refuse a document it cannot fully understand, which is the right behaviour for an artefact whose entire purpose is that nothing is lost across the boundary.