Handoff
Produce a standalone brief that a cold reader can execute from — assume they have zero memory of this session.
When to use
- Ending a session or approaching a context reset, and the work isn't done.
- Switching agents/models, or escalating a stuck task to a more capable model.
- Delegating a sub-task to a sub-agent that won't share your context.
- Passing work to a teammate who wasn't present for any of it.
When NOT to use
- You're continuing the same task in the same context — just
compactinstead; a handoff is heavier. - The task is trivial or already fully captured in a clear final message.
- Mid-flight notes for your own use — that's
scratchpad, not a handoff.
The method (numbered, concrete — the heart)
- Write for a stranger. Assume the reader knows nothing about this session. No "as discussed", no unexplained pronouns, no implicit context.
- State the goal and the done-definition. What is being achieved, and how will the reader know it's complete? Acceptance criteria, not vibes.
- Give current state precisely. What exists now — files/branches changed, what's verified vs. assumed, what's passing vs. failing, where exactly things stand.
- List decisions with rationale. What was chosen, what was rejected, and why — so the successor doesn't undo good choices or retry dead ends.
- Flag the gotchas. Non-obvious traps, environment quirks, brittle spots, "looks wrong but is intentional" notes. This is the highest-value section.
- Give ordered next steps. A concrete, prioritized to-do the reader can start on immediately — first action first.
- Point to resources, don't inline them. Link/path the relevant files, logs, scratchpad, and docs rather than pasting bulk.
- For escalation, lead with the failure. If handing to a bigger model because you're stuck, state plainly what you tried, what failed, the exact error/symptom, and your best hypothesis — give it the full failure context, not just the task.
- Make it standalone and durable. Save it where the next reader will find it (a file, an issue, the top of a thread), so it survives the boundary.
What good looks like
- A fresh agent or person resumes productively from the brief alone, asking no clarifying questions.
- Decisions, rejected paths, and gotchas are all present — the successor neither relitigates nor steps on a known mine.
- Next steps are concrete and ordered; the reader knows the very first thing to do.
- For escalations, the receiving model gets what-failed-and-why, not a bare restatement of the goal.
Anti-patterns
- Context-dependent writing — "continue from where we left off" means nothing to a cold reader.
- State without gotchas — handing over what's done but hiding the traps, so the successor falls into them.
- Decisions without reasons — inviting the next agent to undo settled choices.
- Inlining everything — a wall of pasted code/logs instead of pointers, defeating the purpose.
- Escalating with just the task — giving a bigger model the goal but not the failure trail, so it repeats your dead ends.
Example (short, vivid)
# HANDOFF — Checkout flow fix
Goal: stop the duplicate-charge bug on retry. Done = no double charge under
flaky-network retries; integration test `checkout.retry.spec` green.
State: root cause found — retry sends a new idempotency key each attempt.
Fix started in `payments/charge.ts` (key now derived from order id). Unit
tests pass; integration test still RED on timeout path.
Decisions: derive key from order id, NOT a fresh uuid (rejected — that's the
bug). Did NOT touch the Stripe webhook handler (out of scope).
Gotchas: local Stripe mock doesn't simulate timeouts — must test against the
`stripe-mock` container, not the unit fake. The retry wrapper swallows errors;
log at line 88 before trusting "success".
Next: 1) reproduce timeout via stripe-mock 2) assert single charge
3) backfill key for in-flight orders (see scratchpad.md "migration").