← adimendelowitz.dev
POST 2 / 6 SKILLS THAT REMEMBER 10 MIN READ

Handoff: A Schema Instead of a Recap

How to preserve decisions, state, and open questions when an AI session ends

View source on GitHub

Coming back to a project after a rest, be it the next morning or after a long-awaited vacation, normally leaves me a little fuzzy on the particulars. Re-reading the previous sessions is tiresome to the point of reaching back to my suitcases, and a generated recap misses too many details and much of the context, mostly found wanting.

handoff is the skill I built to close that gap. It writes a state snapshot detailing reasons for chosen options, which parts are left uncertain, and what the next steps are. I no longer need to re-work and re-derive past decisions from a multiverse of threads.

TL;DR
  • Captures state, decisions, proposals, rejections, open questions, unverified claims, and recurring corrections, beyond just the conversation.
  • Separates what was decided from what was only proposed, and from what was considered and dropped.
  • Ends every snapshot with exactly one executable next action.

In this post

  1. 01What a recap leaves out
  2. 02A handoff is an interface
  3. 03Inside the folder
  4. 04How it connects to the rest of the ecosystem
  5. 05Worked example
  6. 06Design decisions
  7. 07Best practices
  8. 08Limits
  9. 09Next

What a recap leaves out

A narrative summary reads well as the heroic chronicles of sessions past, yet without the track record of ground gains, it fails to give traction for the next foothold.

To combat that, every snapshot handoff writes uses labelled lines instead of paragraphs, one fact per line, fields separated the same way every time, noun phrases instead of full sentences. A sentence appears only where a decision turned on the user's exact wording, and there it's quoted directly, word for word.

Typical recapStructured handoff
Narrative summaryResume-ready state record
Blends facts and assumptionsLabels evidence and uncertainty
Lists activitySeparates decisions from what was only proposed
Ends with vague next stepsNames one exact next action
Requires re-reading contextEnables targeted resumption
flowchart TD
    A[Work session] --> B[Capture decisions and evidence]
    B --> C[Record current state and open questions]
    C --> D[Write explicit next actions]
    D --> E[Future session resumes from the handoff]
      

What's not obvious: every session ends at step E whether the work is finished or not. Skip straight from B to D and a handoff turns into a to-do list with no reasoning behind it.

A handoff is an interface

I think of it as a stable API, or a well-kept experiment log. Both stand in for a contract, listing what is known, what changed, what has to happen next, and what shouldn't be assumed.

That framing also rules out a plain prose recap, which is optimised for a single read by someone who was already in the room, whereas a handoff doesn't rely on existing context. In the same sense that an API that fails to document changes in its guarantees is useless no matter how well it works internally, so is a handoff that drops the changes from the version before it.

A snapshot that does this job opens with a one-line objective, then covers eight labelled sections, each holding a specific kind of fact so it never gets confused with another:

Where PATTERN comes from

The protocol reads the project's critique log, when one exists, for any defect appearing in two or more runs. The path is fixed at ${CLAUDE_PROJECT_DIR}/.claude/critique-log/*.jsonl. Where a retrospective journal exists, it's read too, for the same slug, as a second recurrence source, since it captures friction a critique log never records. The protocol doesn't fix a path for the journal, since that pairing is optional and project-specific, and no fixed location is guaranteed for it. Absent both sources, PATTERN draws on the session alone.

graph TD
    CL["critique log<br/>.claude/critique-log/*.jsonl"] -->|"defect in 2+ runs"| PT["handoff's PATTERN section"]
    RJ["retrospective journal<br/>path not fixed by the protocol"] -->|"friction, same slug"| PT
    PT --> SS["This session's snapshot"]
      

What's not obvious: a defect only earns a PATTERN line once it's shown up twice. One occurrence is just a mistake; two, whether both sit in the review log or one sits there and one in the retro journal, is worth writing down.

DECIDED strips out reasoning by design. Separating the choice from the justification is what stops a well-argued suggestion from being misread as a commitment later.

State deserves a closer look, since it's the field most likely to have gone stale by the time anyone reads it. handoff records a line count and byte count alongside each path it names, so a resuming reader can tell in one glance whether the file has changed underneath the snapshot before acting on anything it says about that file.

Open questions get ranked by what they block, and when they came up doesn't factor in. A question raised in the first five minutes that now blocks nothing belongs below one raised in the last five minutes that blocks everything the next session needs to do.

The line doing the most work here separates DECIDED from PROPOSED. Recording that someone chose an option when they merely reacted well to it means the next session acts on a decision that was never actually made. The safer failure mode is under-claiming. Anything with a genuinely unclear origin gets logged as a proposal, marked origin-uncertain, and flagged as such. Rounding it up to a decision because that reads more confidently is the exact mistake this field exists to prevent.

Here's what actually happens, in order, each time a session invokes it:

flowchart TD
    A[Session reaches a stopping point] --> B[Check for a prior handoff to supersede]
    B --> C[Read the critique log and retrospective journal for recurring defects]
    C --> D[Assemble the eight fields, decisions kept separate from proposals]
    D --> E[Apply the hard cap on each field]
    E --> F[Write the snapshot]
    F --> G[Trigger a review pass against the snapshot itself]
    G --> H[Snapshot ready for the next session]
      

What's not obvious: step G isn't conditional on whether the session looks unfinished. The review runs every time, checking the snapshot itself, separate from the work it describes. It asks whether the handoff is honest about what happened. Whether the code or the writing behind it is any good is a different question entirely.

Inside the folder

handoff/
├── SKILL.md
├── README.md
├── example-handoff.md
├── CLAUDE_md-snippet.md
└── tools/
    ├── session_watch.py
    ├── measure_savings.py
    ├── test_session_watch.py
    └── test_measure_savings.py

Each file has one job:

The snapshot is written to <notes-root>/handoffs/, the folder the project's own CLAUDE.md names, and the next session reads it from there on turn one; .claude/handoff/ is not a destination on any surface. The format stays dense enough that pasting it by hand still works where no folder is shared, and the saved copy is what supersedes cites later.

How it connects to the rest of the ecosystem

The hook, the CLAUDE.md text, and the protocol's own PATTERN field are three separate pieces built to click together.

flowchart TD
    S["session_watch.py"] -->|"turn count, as a fact"| C["CLAUDE.md session-length rule"]
    C -->|"suggests running it"| H["/handoff"]
    H -->|"reads"| L[(critique log + retro journal)]
    L -->|"defect in 2+ runs"| P[PATTERN field]
    H -->|"archivist lens, on close"| R[critique]
      

What's not obvious: the hook and the CLAUDE.md text are two deliberately separate pieces. Detection belongs in code, since the assistant has no turn counter of its own and its sense of elapsed conversation is exactly what degrades as a session grows. CLAUDE.md only says what to do once the code's signal arrives, and that signal is worded as a stated fact, which keeps it from tripping the prompt-injection defenses an instruction-shaped hook message would trip.

session_watch.py fires at most twice, a soft nudge around turn 25 and a direct one at turn 35, bracketing the schema's own session-length: long threshold of roughly 30 turns. That first nudge is the cue to write the snapshot partway through a long session, before the whole thing has to be reconstructed from memory at the end, since recall is worst exactly where this protocol gets invoked.

On close, handoff hands off to a review skill, critique in this project, with its purpose set to the next session's objective, requesting its archivist lens explicitly instead of leaving lens selection to defaults. critique's docs bundle, which carries the archivist lens and is covered in the first post of this series, is built for exactly this kind of check, content across files that's supposed to agree, and doesn't always.

Worked example

Here's an example prompt I actually use:

create a comprehensive /handoff for this session, fact check everything in every stage, print only the final version

A typical end-of-session recap might read: "We discussed the blog series structure and made good progress. The five-post plan seems solid, though we may want to revisit post ordering later. Next step is to keep writing."

A structured handoff for the same session looks closer to this:

FieldContent
DecidedAdopt the six-post blueprint fully (supersedes the earlier "five posts, one per skill" plan)
ProposedApply the same comment-review pass and the chosen HTML template to the other two existing posts, once ready
RejectedReusing an old filename for the new post (already belonged to an older, abandoned draft)
OpenThe remaining three posts in the series aren't started against the blueprint yet (blocks: series completion)
FirstAsk whether to run the other two posts through the same review pass next, or move on to drafting the next post in the series

The second version tells a resuming reader exactly what to trust, what's still unresolved, and what to do first. The first version tells a story that has to be re-interrogated before anyone can act on it. Notice, too, that the structured version is shorter. That's a side effect of the format. Once activity gets separated from decisions and decisions get separated from open questions, most of what a recap spends words narrating simply isn't needed anymore. How much shorter varies case to case. The format's own claim about typical savings is stated as an estimate, with a comparison tool built specifically to turn it into a measured percentage against a real prose equivalent, instead of leaving it as an assertion.

That table is a simplified illustration. The real schema every snapshot actually follows is stricter, and every section carries a hard cap:

HANDOFF v3.3 | <date> | supersedes: <prior handoff id or none> | session-length: <short|long>
OBJ: <next objective, one line>
STATE            <=8 lines, paths first, then installs, then running processes
DECIDED          <=10 lines, user's explicit choices only, no reasoning
PROPOSED         <=6 lines, assistant recommendations not yet accepted
REJECTED         <=5 lines
OPEN             <=5 lines, ranked by what blocks OBJ
UNVERIFIED       <=5 lines
PATTERN          <=3 lines, corrections that recurred this session
FIRST            1 line

The caps are load-bearing. Remove one and the schema stops doing its job. The linter (tools/measure_savings.py --lint) flags a cap violation as an error, along with a missing or duplicated FIRST line, a missing header, and, on a long session, a missing UNVERIFIED line about context degradation.

supersedes names the prior handoff and voids it. Snapshots replace each other by name instead of accumulating, so there is exactly one live snapshot per thread of work at any time, and nobody has to read a chain of them to find current state.

That's the shape. Here's a real one, anonymised, from this project's own history (written under v3.0, before the current lint rules) →

Design decisions

Facts and hypotheses stay intentionally separated. I've watched a strong hunch drift into the decisions section just because it was stated with confidence, and the handoff that came out of it looked more settled than the session actually was.

An incomplete handoff says what's missing. If a claim was never checked, or a section had to be cut short, the snapshot says so plainly. A tidy document that implies more coverage than it actually has creates false continuity, and false continuity is worse than an acknowledged gap, since it fails silently on the next session's first move instead of on the handoff's own page.

A handoff always triggers a review of itself. Writing the snapshot isn't the end of the protocol; the last step is running a review against the snapshot with its purpose set to the next session's objective, every time, regardless of whether the author judges the work unresolved. An archivist-style check catches whether a stranger could actually reconstruct the state from the document alone. A handoff nobody checked is just a more organised way to be confidently wrong, and the review step exists specifically to catch that before it costs the next session anything.

None of this is asked to be taken on faith. STATE's line and byte counts are computed against the real files, never estimated, and a state-check pass can flag drift between what a snapshot claims and what's actually on disk. The format's own efficiency pitch gets the same treatment, stated as an estimate in the spec itself, with a comparison tool that turns "shorter, probably" into a measured figure against a paired document. The section caps, the single-line rule for FIRST, and the long-session UNVERIFIED requirement are enforced by a linter, backed by 55 tests across both tools, instead of being left to good faith.

Key rule

Continuity means preserving state, decisions, and open questions in a checkable format. A good story about what happened doesn't count.

Best practices

Limits

Limit

The schema makes a snapshot checkable. It doesn't make what's in it true, and it doesn't make careful reading automatic.

Next

A handoff preserves the local state of a session. A retrospective asks what repeated patterns in that state are trying to teach us.

NEXT Retrospective - turning AI workflow history into learning.