handoff
Three Claude Agent Skills that carry an agent's working context across the session boundary. A parent contract defines the document; a producer writes and read-back-verifies it and owns storage; a consumer resumes from it. The point is that context survives a session reset without the next agent re-asking what was already established — and without trusting a job's self-reported success.
Problem
A long agent session ends — context window exhausted, container recycled, or just a fresh chat
the next morning. The naïve fix, "summarize the conversation," loses the two distinctions that
actually matter for a successor: what was verified versus what was assumed, and
where a running background job's real state lives. A summary that promotes a guess to a fact sends
the next session down a path the last one already ruled out; a summary that says "the job
succeeded" because a RESULT: ok line said so inherits the lie. And a summary written
by a helpful model happily copies the API token it saw into a durable file. The work needed a
contract, not a paragraph.
Architecture
latest() / read(), so
moving storage never breaks pickup.Design decisions
- Three skills, not one. The rejected alternative was a single "handoff" skill that both writes and reads. Splitting it into a stateless parent contract, a producer that solely owns storage, and a consumer that reaches storage only through the producer's interface means the physical location is private to one skill.
- Storage behind an owner interface, not open paths. The consumer could have
globbed for
HANDOFF-*.mditself. Instead it uses five operations —resolve / write / latest / read / format— so swapping the backend (a remote shell, an object store, a notes app) rewrites only the producer's storage section. - Persist only where the filesystem outlives the session. In an ephemeral
sandbox, writing to disk is not persistence. The producer returns
storage_unresolved, prints the document in chat, and refuses to claim it was saved. - Read-back or it didn't happen. After writing, the producer reads the file back and compares; a mismatch or a failed read is a failure, never a reported success.
- Facts and hypotheses are separate headings. The schema forces the split, and the consumer is forbidden to promote a hypothesis to a fact just because the session is new.
Numbers
The deepest chain is on the nightshift project: seven handoffs written between 2026-09-22 20:55 and 2026-09-23 07:45, each session's document seeding the next — a real producer→consumer→producer relay across seven sessions, not a single demo.
The corpus is older than the schema: only 14 of the 35 handoffs use
the v1.0 headings (Durable context / Verified facts /
Next action), 10 carry an explicit Hypotheses section, and 21 record a
background job's PID, log, or exit-code inline. Adoption is partial, and the page says so rather
than counting only the compliant files.
Failure modes the contract is built against
- False persistence. A skill that writes to a chat container's scratch disk and reports "saved" has saved nothing. The producer detects the ephemeral case and downgrades to printing in chat.
- Hypothesis laundering. A guess repeated across three sessions starts to read like a fact. The separate heading and the pickup rule keep it labelled a guess until a command confirms it.
- Inherited lie. A background job reports
RESULT: okyet failed its own acceptance test. Pickup inspects the live process, its log and exit code, and runs the task spec's Verify — it does not take the line on faith. - Secret in a durable file. The one thing a handoff must never contain is a credential. The contract records only the owner/interface to obtain a secret; pickup treats a secret-shaped value as a producer defect and redacts it.
Limits
- Read-back proves the bytes round-trip, not that the content is correct or complete — an honest-but-wrong handoff still round-trips cleanly.
- The facts/hypotheses split is only as good as the producing session's honesty; there is no automated fact-checker behind it.
- The secret scan is shape-based (key/token/password-like values), a filter, not a proof.
- The public repo (v1.0.0, English, local-directory backend, MIT) is a de-specialized fork of the version actually installed here (a VPS-backed backend at a higher version). The generalized backend is claimed-compatible by keeping the same interface — one storage backend is exercised in production, not two.
What's next
- A non-directory backend (remote shell or object store) exercised end-to-end, to actually test the "swap only the storage section" claim rather than assert it.
- A lint that flags handoffs missing a required section, so schema adoption across the corpus is measured, not eyeballed.