@avee1234/handover
v0.2.1
Published
The open format for handing off an in-progress agent task. A portable, harness-neutral JSON packet capturing everything a fresh agent needs to RESUME a task mid-flight — goal, context, progress, working state, next steps, open questions, and artifacts — s
Maintainers
Readme
handover
The open format for handing off an in-progress agent task. When an agent has to stop mid-flight — context window full, session ending, a better-suited model or harness needed, a human stepping away — it writes a handover packet: a single portable JSON document capturing everything a fresh agent needs to resume the work exactly where it stopped. The goal, the constraints, what's been done, the live working state, what's next, what's still unknown, and the artifacts in play. So an in-progress task can move between agents or harnesses without losing its state. Zero dependencies.
Working name — see
vision.md. Grounded in the mid-2026 state of long-running agent tasks.
Long-running agent tasks routinely outlive a single agent. A coding job spans more context than one window holds; a session hits its limit; the work would go faster on a different model; a human hands the thread to a teammate's agent overnight. Coding agents — Claude Code, Codex, Cursor, and Google Antigravity — all hit this wall. Today, when that handoff happens, the working state evaporates. The next agent gets a cold transcript (if anything) and re-derives the plan, re-reads the files, re-discovers the decisions already made — or silently drops a half-finished thread. git blame doesn't hold it; a chat transcript is lossy and harness-specific; every harness that gestures at "resume" or "session export" does it in its own private, non-portable shape.
A2A standardizes how one agent invokes another. Nothing standardizes the working state an agent hands over when it stops mid-task — so every resume starts from scratch.
handover is the missing layer: an open format for the working state handed over between two agents, not another orchestrator, memory store, or runtime.
npx @avee1234/handover pack --goal "add PKCE to login" --agent me # capture the current handoff
npx @avee1234/handover completeness .handover/packet.json # is this good enough to resume from?
npx @avee1234/handover resume .handover/packet.json # render the briefing for a fresh agent
npx @avee1234/handover diff old.json new.json # what did one checkpoint advance over another?
npx @avee1234/handover from claude-code transcript.jsonl # draft a packet from a native session (any harness)Why it's different: content-addressed, so a packet is self-identifying — its id is the sha256 of its content, so any edit produces a new identity and a resumed packet is provably the one that was handed off. It's a single living document, not a ledger — no registry to fold, no server to run. Harness-neutral: Claude Code, Codex, Cursor, Google Antigravity, or a factory worker — anything that can run a CLI or import a function.
Same open-format playbook as opentrajectory (traces), provenant (provenance), and worklease (coordination) — the standard for the one thing a mid-task agent handoff currently lacks: a portable working-state document.
The packet format
A handover packet is one JSON document. id is the sha256 content hash of the record itself (its own id excluded), so a record's identity is its content. goal and provenance are the required spine; every other section is optional but type-checked when present.
{
"id": "19483d7ee3c50e5f4ee2507643f205e9c6f28bc6f972e04bece928519b4a3374",
"version": 1,
"goal": "add PKCE to the OAuth login flow",
"context": "Zero new deps. The auth module must stay framework-agnostic.",
"progress": [
"wrote the PKCE verifier/challenge helpers",
"wired them into the /authorize handler"
],
"state": {
"cursor": "src/auth/login.ts:142",
"approach": "S256 challenge",
"key_files": ["src/auth/login.ts", "src/auth/pkce.ts"]
},
"next_steps": [
"handle the token-exchange callback",
"add tests for the verifier"
],
"open_questions": ["should we fall back to plain challenge for legacy clients?"],
"artifacts": [{ "path": "src/auth/pkce.ts", "note": "new helper module" }],
"provenance": {
"handed_off_by": "claude-opus-4-8/claude-code",
"created": "2026-07-11T12:00:00Z",
"from_session": "sess-9f21"
}
}goal— the task being handed off (required, non-empty).context— constraints, background, and decisions the resumer must respect.progress— what's already been done (array of notes).state— the live working state (free-form object); an optionalkey_filesarray names the files to open first.next_steps— what to do next.open_questions— what's still unresolved.artifacts—{ path, hash?, note? }for the files produced or touched;hash(if present) is the sha256 of the file's bytes.provenance— who handed off (handed_off_by), when (created, ISO-8601-UTC,…Z; offsets and impossible calendar dates are rejected), and optionally the originatingfrom_session.version— the checkpoint number, incremented byrevise.
Library API
Zero-dependency ESM. import { … } from "@avee1234/handover". Every function is pure and clock-injected (no I/O except the packet store), so the whole core is deterministic and unit-testable.
Schema & validation — never throw; each returns { valid, errors } collecting every violation.
validatePacket(obj)/validateArtifact(obj)isSha256Hex(s),isIso8601Utc(s)— the two format primitivesPACKET_FIELDS,REQUIRED_SECTIONS,ERROR_CODES
Construct — pure packet constructors (throw on bad input rather than emit a malformed packet).
pack(input)→ a validated packet with a content-hash id and all section defaults applied.inputis{ goal, agent, created, context?, progress?, state?, next_steps?, open_questions?, artifacts?, from_session?, version? };createdis injected (no clock inside).revise(packet, patch)→ a NEW checkpoint:versionincremented, array sections appended, scalar sections (goal,context) replaced when present,stateshallow-merged, provenance refreshed (patch.createdrequired — a revision is a fresh handoff).
Hash & id — the content-address primitives.
computeId(record)→ the sha256 content hash of a record (its ownidexcluded)canonicalize(record)→ the deterministic hash pre-image (sorted keys, recursive)computeHash(content)→ the sha256 hex of a string/Buffer (an artifact fingerprint forartifacts[].hash)
Assess & render — pure, total, over a single packet.
completeness(packet)→{ score, total, present, missing, warnings }— how much of the handoff a fresh agent has to work with, over the sevenREQUIRED_SECTIONS, plus plain-language warnings about gaps that strand a resumer.resume(packet)→ the Markdown briefing a fresh agent can be handed verbatim (goal, context, what's done, live state + key files, next steps, open questions, artifacts, and a provenance footer).resumeInto(packet, harness)→ the same briefing in a harness's opening-context shape:'system-prompt'(a system-prompt string),'user-turn'(a first-user-message string), or'mcp-resource'({ uri, mimeType: 'text/markdown', text }). The Markdown body is the realresume()output in every shape; an unknown shape throws (the supported shapes are inRESUME_SHAPES).summarize(packet)→ a one-line status:"<goal> — N done, M next, K open".diffPackets(a, b)→ what checkpointbadvanced overa:{ goal_changed, version_delta, progress_added/removed, next_steps_added/removed, questions_opened/closed, artifacts_added/removed, state_keys_changed }(set semantics on the string arrays — reordering reads as no change).
Packet store — the single-document I/O layer.
savePacket(path, packet)→ write the packet as pretty JSON (creating its parent dir); overwrites in place — a packet is one living document, not an append log.loadPacket(path)→ the parsed packet (clear throw on a missing file or malformed JSON).defaultPacketPath(cwd)→HANDOVER_PACKET, else.handover/packet.json.
Adapters — seed a draft packet from a harness's native session artifact so writing one is cheap. Every adapter shares one contract: (raw, opts) → draftPacket — latest user message → goal, last assistant turn → context, assistant bullet/numbered lines → progress. All are pure and tolerant of malformed input, and all return a PARTIAL draft (no id, no created) — not a validated packet. Refine it, then pack(). opts is { agent?, from_session?, max_progress? }.
fromClaudeCode(rawJsonlTranscript, opts)→ draft from a Claude Code.jsonlsession transcript.fromCodex(rawJsonlRollout, opts)→ draft from an OpenAI Codex CLI.jsonlrollout (unwraps thepayloadenvelope; readsinput_text/output_textblocks).fromCursor(rawSession, opts)→ draft from a Cursor chat export (whole-document JSON,.jsonl,User:/Assistant:prose, or a marker-less scratchpad).fromAntigravity(rawSession, opts)→ draft from a Google Antigravity session (JSON /.jsonl/ prose) or anAGENTS.md-style task brief (headings → goal, bullets → progress).ADAPTERS,getAdapter(name)— the name→builder registry keyedclaude-code/codex/cursor/antigravity(add new harnesses here).
CLI
handover pack [--file <in.json>] [--agent <id>] [--goal "<task>"] [--out <path>] [--json]
handover show <file> [--json]
handover validate <file> [--json]
handover completeness <file> [--json]
handover resume <file>
handover diff <a> <b> [--json]
handover from <harness> <file> [--agent <id>] [--json]
handover from-<harness> <file> [--agent <id>] [--json]pack— read a JSON object (from--fileor stdin), fill section defaults, stamp the handoff (agent + timestamp), validate, and write the packet.--goal/--agentoverride the input; the clock is read only here. Writes to--out(default:HANDOVER_PACKET, else.handover/packet.json).show— pretty-print a packet (--json), or a one-line summary without it.validate— validate a packet against the schema. Exit0if valid,1otherwise (errors listed with their paths and codes).completeness— score how much of the handoff a fresh agent has to work with, and warn about the gaps that strand a resumer.resume— render the Markdown briefing a fresh agent can be handed verbatim.diff— show what checkpoint<b>advanced over checkpoint<a>.from— resolve a harness adapter from the registry (claude-code,codex,cursor,antigravity) and parse its native session artifact into a DRAFT packet to refine and pipe back intohandover pack.from-<harness>(e.g.from-claude-code) is the shorthand form.
Common flags: --agent <id> (or HANDOVER_AGENT), --out <path> (or HANDOVER_PACKET, default .handover/packet.json), --json for machine-readable output.
Install
npm install @avee1234/handover # library
npx @avee1234/handover pack … # CLI, no installRequires Node ≥ 18. Run the test suite with node --test.
Status: v0.2 — multi-harness adapters (Claude Code, Codex, Cursor, Google Antigravity) + resumeInto. See roadmap.md. MIT · zero dependencies · harness-neutral.
