Context Handoff Protocol
Workflow
Are you ending a session? → Write a Handoff
Are you starting from a handoff? → Resume from Handoff
Ending Session: Write Handoff
Before stopping, write the handoff to .claude/plan-progress.md.
Use your Write tool to create or overwrite that file with the exact format below.
See references/handoff-format.md for a worked example and field-by-field guidance.
---
session_id: "<session id if known, else 'unknown'>"
timestamp: "<UTC ISO-8601, e.g. 2026-03-14T10:30:00Z>"
task: "<one-line description of the overall task>"
status: "<in_progress | blocked | done>"
---
## Accomplished
- <bullet: concrete item completed>
## In Progress
- <bullet: partially-done work — what's left and ~% complete>
## Next Steps
- <ordered action for the next agent>
## Search Hints
### Key Files
- <relative/path/to/file.py> — <why it matters>
### Key Directories
- <src/module/> — <what lives here>
### Grep Patterns
class MyClass def authenticate TODO.*payment
### Key Symbols
- MyClass
- authenticate_user
- PAYMENT_TIMEOUT
## Open Questions
- <unresolved decision or blocker>
## Artifacts
- <file created or significantly modified this session>
## Notes
<free-form context that doesn't fit above>
Search hints philosophy — hints, not dumps. Give the next agent a map, not the territory. List file paths and grep patterns so it can read the actual current code itself. Never paste file contents into the handoff — those go stale and bloat the context.
Resuming Session: Use the Handoff
Read the handoff file with the
Readtool:Read: .claude/plan-progress.mdRun the search hints to verify current code (do NOT trust handoff descriptions as ground truth — always verify):
Readeach file in Key FilesGrepeach pattern in Grep PatternsGlobeach pattern in Key Directories to discover structure- Search for each Key Symbol with
Grep
Continue from Next Steps — unless your search reveals a reason to change course.
Overwrite the handoff when your own session ends, with a fresh summary reflecting the updated state.
CLI: Write a Handoff from Bash
Use skills/write_handoff.py when you want to record the handoff by running a
script instead of composing Markdown by hand with the Write tool.
# Minimal — just task, status, and search hints:
python skills/write_handoff.py \
--task "Add JWT authentication" \
--status in_progress \
--key-files "src/auth/service.py" "src/config.py" \
--grep "class AuthService" "def validate_token" \
--symbols "AuthService" "JWT_SECRET"
# Full handoff with all fields + progress log row:
python skills/write_handoff.py \
--task "Add JWT authentication" \
--status in_progress \
--accomplished "Scaffolded AuthService" "Added JWT config" \
--in-progress "Wiring middleware (~30% done)" \
--next-steps "Import AuthService in gateway.py" "Wire into handle_request()" \
--key-files "src/auth/service.py — AuthService class" \
"src/api/gateway.py — wire here" \
--key-dirs "src/auth/" "src/api/" \
--grep "class AuthService" "def handle_request" "JWT_SECRET" \
--symbols "AuthService" "handle_request" "JWT_SECRET" \
--open-questions "Should tokens expire after 1h or 24h?" \
--artifacts "src/auth/service.py (new)" "src/config.py (modified)" \
--notes "Using PyJWT 2.x — import is jwt not JWT" \
--also-progress-log \
--plan-id "feature/jwt-auth" \
--agent-id "agent/coder-v1"
The script writes three things:
.claude/plan-progress.md— overwrites with the latest Markdown handoff..plan_progress.jsonl— appends one JSON line (append-only audit trail).docs/exec-plans/progress.md— appends a summary row (when--also-progress-log).
Programmatic Integration (Agent SDK)
The HandoffTracker (hook-based, JSONL + progress log) and HandoffProtocol
(system-prompt injection, Markdown) Python classes live in harness_skills:
# Hook-based — auto-syncs handoff to JSONL + progress log on Stop event
from harness_skills.handoff import HandoffTracker
from claude_agent_sdk import ClaudeAgentOptions
tracker = HandoffTracker(
task="Add rate-limiting to API gateway",
plan_id="feature/rate-limiting",
agent_id="agent/coder-v1",
)
# Ending session — inject writing instructions + register Stop hook
options = ClaudeAgentOptions(
system_prompt=tracker.system_prompt_addendum(),
hooks=tracker.hooks(),
)
# After the session ends, read from the JSONL audit log:
resume_prompt = HandoffTracker.get_resume_prompt() # pre-rendered preamble
search_hints = HandoffTracker.get_search_hints() # SearchHints object
# Protocol-based — injects instructions into system prompt only
from harness_skills.handoff import HandoffProtocol
protocol = HandoffProtocol()
options = protocol.ending_agent_options(base_options, task="...") # ending
options, doc = protocol.resuming_agent_options(base_options) # resuming
How HandoffTracker works
Ending session:
1. tracker.system_prompt_addendum() → injected into agent system prompt
2. Agent writes .claude/plan-progress.md using its Write tool
3. Session ends → Stop hook fires automatically:
a. Reads .claude/plan-progress.md
b. Appends JSON line to .plan_progress.jsonl (audit trail)
c. Appends summary row to docs/exec-plans/progress.md
Resuming session:
HandoffTracker.get_resume_prompt() ← reads from .plan_progress.jsonl
HandoffTracker.get_search_hints() ← parses hints from latest JSONL entry
Key files
| Path | Purpose |
|---|---|
harness_skills/handoff.py |
HandoffDocument, SearchHints, HandoffProtocol, HandoffTracker, _append_jsonl, _append_progress_log_entry |
skills/write_handoff.py |
CLI — write a handoff from Bash arguments |
skills/context-handoff/scripts/read_handoff.py |
CLI — display a saved handoff |
.claude/plan-progress.md |
Latest Markdown handoff (overwritten each session) |
.plan_progress.jsonl |
Append-only JSONL audit trail across all sessions |
See references/handoff-format.md for the full format reference and a worked example.