2026-08-08
·7 min read
Automating the Agent Handoff
You are two hours into a refactor. The agent has read forty files, tried three approaches, ruled out two of them, and is finally on the right track. Then the context window fills. It compacts, or you clear it, or you close the laptop.
Next session, it re-reads files it already inspected. It suggests an approach you rejected ninety minutes ago. You spend fifteen minutes explaining a project you have already explained once today.
The work did not fit in one session. That is not a failure of the model. Most real tasks do not fit in one session. The failure is that nothing was written down at the boundary.
Compaction is not a handoff
Anthropic's own guidance on context engineering names three techniques for long-horizon work: compaction, structured note-taking, and multi-agent architectures. Compaction summarises the conversation and restarts the window with the summary. Note-taking writes durable state to a file outside the window and pulls it back later.
Most people only ever use the first one, because it is the only one that happens automatically. Which is a problem, because compaction is lossy by design: your requests and key code snippets survive, detailed early instructions may not. There is also a failure mode worth knowing about: if a single tool keeps producing more output than the summary reclaims, the session can thrash, compacting repeatedly without making progress. That is a signal to change the working pattern, not to compact harder.
The second technique is the one that actually solves the session boundary, and it is the one nobody has wired up end-to-end. Writing it is one command; nothing decides when to run that command for you. A summary that lives inside the context window dies with the context window. A file on disk does not.
What a handoff actually contains
A handoff is not a transcript and it is not a README. It answers the questions the next session is about to ask, in about a page.
# HANDOFF - 2026-08-08 - payments-retry-refactor ## Goal Make the Stripe webhook handler idempotent. Not started: the backfill script. ## Decisions made - Idempotency key = `event.id`, stored in `webhook_events`. Rejected a Redis lock: we need an audit trail, not just mutual exclusion. - Retry backoff stays in the queue layer, not the handler. ## State - Branch `fix/webhook-idempotency`, commit `a3f9c21`, pushed. - Changed: `src/webhooks/stripe.ts`, `src/db/migrations/0042_webhook_events.sql` ## Verified / not verified - Verified: unit tests pass (`pnpm test webhooks`). - NOT verified: the migration against a non-empty table. Untested. ## Next step 1. Run the migration against a seeded local DB and confirm the unique index holds under duplicate inserts. 2. Then start the backfill script. Do not touch the queue layer.
The section that earns its place is the one most people leave out: what was not verified. Nathan Onn makes the same point writing up his handoff-doc skill. The first time he ran it, the doc flagged plainly that a feature he had been working around was never actually tested. A human writing that section by hand tends to round up.
Rejected approaches matter just as much. Without them, the fresh session cheerfully reaches for the option you already ruled out, exactly as a new engineer would.
Automating the trigger
Writing a handoff manually works right up until the moment it matters, which is when you are deep in something and context is climbing. So put the trigger in the tooling.
Claude Code exposes session lifecycle hooks: PreCompact fires before compaction, SessionEnd on termination, SessionStart when a session begins or resumes. That is the whole shape of the thing. PreCompact is your last clean chance to capture state before summarisation. SessionStart is where the next session reads it back. It re-fires on resume with the source set to resume, so injected context refreshes rather than going stale.
One detail from those docs that will save you an afternoon: write injected context as factual statements, not as commands. "The active handoff is at docs/handoffs/0042.md" reads as project information. Text framed as an out-of-band system instruction can trip the model's prompt-injection defences, and it gets surfaced to you instead of used.
Rather than hooks, you can drive the trigger off a token threshold. Onn's rule is to hand off once context usage crosses roughly 20%, well before the window is full. The distillation step itself needs room to read back through the session; by 30% it is competing with the very content it is trying to capture. Pick a number, put it where you will actually see it, and treat crossing it as non-negotiable.
Starting the next session by itself
This is the part people assume is hard and mostly is not.
In a terminal, Claude Code runs non-interactively with -p, and the headless docs spell out the resume pattern: capture the session ID from the first run's JSON output, then resume it later: claude -p "Continue from HANDOFF.md" --resume "$SESSION_ID". The CLI reference adds the rest: --fork-session branches a new session from an existing one instead of mutating the original, and --worktree (with --tmux to open it in a tmux window) runs an isolated session in its own git worktree, for when the next chunk of work should not collide with what you have open.
So the "open a new terminal automatically" step is a hook that shells out to tmux new-window with a claude invocation pointing at the handoff file. Nothing exotic. The interesting engineering is not the spawn. It is deciding when the spawn is safe.
Where you are not in a terminal (a browser session, a chat client, someone else's machine), the same handoff file is the deliverable. The agent writes it, you paste it. Tool-agnostic by construction, which is the whole argument in jdhodges' handoff prompt template: the same structured brief works in Claude, ChatGPT, Gemini or Copilot because it is just text.
What already exists, and where each one gives out
Built-in compaction (/compact). Free, automatic, zero setup. But it is lossy, you cannot change the summarisation prompt, and the result lives in context rather than on disk. Fine as a floor, not a strategy.
Handoff skills like nathanonn/agent-skills: one command, and it picks up your existing file naming. The cost is you still have to notice the threshold and invoke it yourself, and distillation takes a few minutes of processing.
Memory banks. Cline's Memory Bank is a set of markdown files (project brief, active context, progress) that the agent reads at the start of every task. Genuinely good for durable project knowledge, and it is a methodology rather than a feature, so it ports to any tool. The failure mode is drift: the files are only as true as the last update, and every session pays the token cost of reading them whether or not they are relevant. Cline's own /newtask and /smol commands are the closest thing to a native session handoff in that ecosystem.
Orchestrator patterns. Roo Code's Boomerang tasks pause a parent task, run a subtask in its own isolated context, and return only a summary to the parent. Excellent for context hygiene on decomposable work. It does not solve our problem, though. It moves it. The orchestrator still accumulates, and when it fills up you are back where you started.
Spawn primitives. Scope takes the opposite approach: rather than a black-box orchestrator, it gives you spawn, wait and poll and lets you write the coordination logic. More work, more control, and honest about the tradeoff.
(One naming collision worth flagging: in the OpenAI Agents SDK, a "handoff" is a triage agent delegating a live conversation to a specialist, exposed to the model as a transfer_to_x tool. Same word, different problem: it is routing between agents, not continuity across time.)
What I would change
Hand off at a clean boundary. The threshold tells you to start looking. It does not tell you to stop mid-edit. Finish the micro-task, commit or stash, then write the handoff. A handoff that describes a half-applied refactor is worse than none, because it reads as authoritative.
Make the state verifiable. Put a command in the handoff that the next session runs to confirm it is where the doc says it is: git rev-parse HEAD, the test command, whatever proves it. "Verified: tests pass" is a claim. pnpm test webhooks is a check.
Separate session state from project knowledge. CLAUDE.md is durable convention; HANDOFF.md is what happened this afternoon. Keep them apart, or you get the same drift memory banks suffer from: transient state written into the permanent file and read back long after it stopped being true.
Put a fuse on any auto-spawn. A session that can start its own successor can start a chain. Before you wire that up, cap it: --max-turns, a bounded permission mode, a counter that refuses to spawn more than N times without a human. The nightmare here is not a bad refactor, it is a loop burning budget overnight while you sleep.
There is also a plain cost argument. Every turn in a bloated session re-processes everything before it. Clean 20% chunks with a handoff between them cost less for the same work.
On your own project this is an afternoon: a handoff template, a hook, a threshold you actually respect. On a team it is a policy question: a HANDOFF.md is now something a colleague reads and trusts without having sat through the session, which is worth a second pair of eyes before anyone builds on it.
We help engineering teams get real work done with AI agents, past the demo, past the first context window. Start with an audit and we will tell you what is worth automating for your team.
Book a free consultation →