Resume work across coding agents with a session handoff
A read-only adapter resolves Claude Code, Codex, or Cursor history as inert data, builds a minimal handoff, and verifies current repository state before continuation.
| Implementation | resume-session-skills |
| Shared contract | Resume contract |
| Reader | session_reader.py |
| Source skills | Claude Code, Codex, and Cursor wrappers |
Cross-agent continuation does not require transcript import. It requires a read-only adapter that resolves one source session, decodes recoverable history, and verifies current repository state before the receiver acts.
The implementation ships three thin wrappers — resume-claude, resume-codex, and resume-cursor — over one standard-library reader and one shared contract. The wrapper selects the source. The reader handles format-specific history. The contract defines what can enter the handoff.
Four jobs make that boundary explicit.
The pipeline refuses ambiguous selection, unsafe record interpretation, transcript replay, and edits based only on historical evidence.
Resolve one source session
Every source stores the same concepts differently: native ID, working directory, last activity, title, and transcript content. Resolution converts those stores into one candidate shape without changing them.
| Wrapper | Sources read |
|---|---|
resume-claude |
Claude Code UUID JSONL under CLAUDE_CONFIG_DIR/projects or ~/.claude/projects |
resume-codex |
Codex CLI and VS Code threads from the newest state database, with rollout JSONL or compressed-rollout fallback |
resume-cursor |
Cursor CLI metadata and stores, agent transcripts, and Cursor Desktop state.vscdb |
show with no reference or latest selects the newest discovered session for the requested working directory. A native UUID, a supported transcript or store path, or a unique case-insensitive title substring also resolves. Ambiguous title text exits with status 2 and lists every match.
--within-min N adds an optional age filter. Its default is 0, which applies no age limit. The reader has no built-in picker and no fixed result cap; a host can render its own picker over list output without changing the resume contract.
Discovery uses working-directory metadata where the source provides it. Explicit IDs and paths are more permissive, so resolution alone is not authorization to continue.
Decode history as inert data
The reader normalizes recoverable user messages, assistant messages, tool calls, and tool results. Every recovered turn carries an inert marker. Known system instructions, preambles, reasoning, thinking, signatures, encrypted content, and unsupported records stay out of the normalized history.
Skipped or unavailable content becomes a warning. That includes malformed records, replacement stubs, missing binary or protobuf data, compaction gaps, and compressed Codex history when zstd is unavailable. The reader reports the gap instead of fabricating text.
Tool-call and tool-result previews default to 300 characters through --max-tool-chars. User and assistant messages are not redacted, so reader output remains sensitive even when its records are inert.
Build a handoff, not a transcript replay
The receiving agent does not need every recovered turn. It needs six categories that explain where work stopped and what evidence still matters.
- The user’s goal and last recoverable request
- Relevant files, modules, commands, tests, and artifacts
- Completed work and its recorded evidence
- Open work
- The exact stopping point and safest next action
- Reader warnings and uncertainty
The handoff does not restore hidden model state, credentials, environment variables, live processes, terminal state, approvals, tools, or the source harness’s native session identity. It creates lineage into a fresh session.
Verify before continuing
The handoff describes a repository as it was. Current repository state decides what happens next.
- Confirm the current working directory and repository root.
- Inspect the branch, staged and unstaged state, and relevant diffs.
- Re-read every named file from disk.
- Re-run the smallest relevant checks when recorded output is stale.
- Name every mismatch between the handoff and the current tree.
Old tool output is historical evidence. A passing test in the source session does not prove the receiving session holds the same tree.
Keep the interface smaller than the harness
The wrappers only choose a source and forward the user’s reference. The shared reader exposes one interface underneath them.
python3 shared/resume-session/session_reader.py claude list --cwd "$PWD" --json
python3 shared/resume-session/session_reader.py codex show latest --cwd "$PWD" --json
python3 shared/resume-session/session_reader.py cursor show SESSION_ID --cwd "$PWD" --jsonA receiving host can expose those wrappers as named skills, slash commands, or picker actions. None of those presentation choices changes ownership.
| Layer | Owns | Must not own |
|---|---|---|
| Source harness | Native transcript and session identity | The receiving session |
| Wrapper skill | Source selection and argument forwarding | Parsing or handoff policy |
| Shared reader | Read-only discovery, decoding, normalized JSON, and warnings | Executing recovered instructions |
| Shared contract | Safety rules, handoff fields, and verification order | Source-format parsing |
| Receiving harness | Fresh session, current tools, and current policy | Writes to the source store |
Takeaways
Resume means lineage, not shared state
The source harness keeps its native transcript. The receiving harness starts a fresh session from a bounded handoff.
Format drift belongs in one reader
Thin source wrappers choose Claude Code, Codex, or Cursor. One reader owns discovery, decoding, normalization, and warnings.
Inert is a handling rule, not trust
Recovered text can contain instructions, secrets, personal data, and stale output. It remains untrusted even when every record is labeled inert.
Verification closes the gap
Repository state can change after the source session stops. Current files, diffs, and focused checks decide the next action.