Applies to every codebase, and to a plain folder without git. _agent/ at the root is the one
folder for everything an agent leaves behind in a repo: working notes in _agent/docs/, these step
logs in _agent/handoff/. It is never committed.
Compaction keeps whatever the compactor decides and cannot be inspected. A handoff is a file the human can read, correct and diff, and a feature that spans several sessions becomes an auditable log of steps: step N's Next is step N+1's Done.
The user provided: $ARGUMENTS
to <agent>or--to <agent>, anywhere in the arguments, or a plain "hand off to Codex" - the agent that runs the next session. Strip it before reading the rest. Known agents are in Launch command per agent. Default: the agent you are (claudein Claude Code).resume [TICKET-or-slug]- you are the fresh session. Go to Resuming. With no goal, infer it the same way as below.<TICKET-or-slug> ["step description"]- you are the ending session. Go to Writing a step.- Nothing - writing mode. Infer the goal from the branch name. With no ticket in the branch, or
no git at all, use a kebab-case slug of the goal (
router-decomposition; the folder name is a fine default for a small project). If that is unclear, ask. Never invent a ticket number.
GOAL=$(git branch --show-current 2>/dev/null | grep -oE '[A-Z]{2,}-[0-9]+' | head -1) # feature/PROJ-42-search-index -> PROJ-42
Where the log lives
<root>/_agent/handoff/<TICKET-or-slug>/
01-<step-slug>.md
02-<step-slug>.md
...
if COMMON=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null); then
ROOT=$(cd "$COMMON/.." && pwd) # main checkout, also from a worktree
DIR="$ROOT/_agent/handoff/$GOAL"; mkdir -p "$DIR" # creates _agent/ when missing
grep -qx _agent "$ROOT/.git/info/exclude" 2>/dev/null || echo _agent >> "$ROOT/.git/info/exclude" # appends only when missing
else
ROOT=$PWD; DIR="$ROOT/_agent/handoff/$GOAL"; mkdir -p "$DIR" # no git: plain folder, nothing to exclude
fi
--git-common-dir, not --show-toplevel: in a worktree --show-toplevel resolves to the
worktree's own root, and since _agent/ is not in git the file would be deleted with the
worktree. --git-common-dir points at the main checkout from anywhere, so every worktree writes
into the same folder and the log outlives the worktree. A worktree's .git is a file, not a
directory, so this also makes the exclude path correct.
If _agent/ is not present, create it and add it to .git/info/exclude - the lines above do
exactly that and are no-ops when it already exists, so run them every time instead of checking
first. .git/info/exclude is per clone and never committed, so no tracked file is touched and
_agent/ never shows up in git status.
No git repository (a plain folder): ROOT is the folder itself, there is nothing to exclude,
and _agent/ simply sits next to the work. The header says so instead of naming a branch.
- One folder per goal. Step files are two digits, then a short kebab slug of what the step was
about. Sorted
lsis the reading order; the newest file is the truth. - No index, no
README.md, even past four files. The sequence is the index.
When to run it
After green - the normal case, and the one to encourage. The best moment is right after a verified milestone and before the context window degrades: a summary written by a degraded session is a degraded summary. Ending a healthy session on a handoff costs one short file, and the fresh session starts sharp.
When the session is screwed - the emergency case: the apology loop ("You're absolutely right", the same mistake twice), or the window nearly full. Write the step anyway, mark Where we are as mostly Believed with one line saying why, and stop. Do not keep working once the file is written.
The human decides when. Nothing here triggers on context size.
Writing a step
- Read the newest existing step, if there is one:
ls "$DIR" | sort | tail -1. Its Goal is copied verbatim; its Next is what your Done answers. - Number and name the file.
LAST=$(ls "$DIR" | grep -oE '^[0-9]+' | sort -n | tail -1)
N=$(printf '%02d' $((10#${LAST:-0} + 1))) # 10# so 08 and 09 are not read as octal
FILE="$DIR/$N-<step-slug>.md"
The step slug is the description from the arguments, kebab-cased, or two to four words for
what the step did (fix-duplicate-results).
3. Header. The H1, then one italic line with the date and the session log that produced the
file, then one italic line with the branch and where the session ran:
# PROJ-42 - step 02: fix the duplicate search results
*Written 2026-09-12. Session log: `~/.claude/projects/<project-key>/<SESSION-ID>.jsonl` (`claude --resume <SESSION-ID>`)*
*Branch `feature/PROJ-42-search-index`, main tree `/path/to/repo`*
TOP=$(git rev-parse --show-toplevel 2>/dev/null) # equal to $ROOT: "main tree"; otherwise "worktree" at $TOP
git branch --show-current 2>/dev/null
Without git the second line is *No git repository. Plain folder /path/to/folder*.
Working notes line. When the worknotes skill keeps a subject folder for the same goal,
add a third italic line pointing at its README:
ls -d "$ROOT"/_agent/docs/"$GOAL"*/README.md 2>/dev/null # e.g. _agent/docs/PROJ-42-search-duplicates/README.md
*Working notes: `_agent/docs/PROJ-42-search-duplicates/README.md`*
No match, no line. Do not invent a folder.
Finding the session id (Claude Code). It is the UUID directory in the scratchpad path from
the system prompt, /private/tmp/claude-<uid>/<project-key>/<SESSION-ID>/scratchpad. The same
<project-key> (the absolute cwd with / replaced by -) is the log directory, so the log is
~/.claude/projects/<project-key>/<SESSION-ID>.jsonl. ls it before writing the line. With no
scratchpad path in context, take the newest log for this repo and say in the file that it is
inferred:
ls -t ~/.claude/projects/"$(pwd | tr / -)"/*.jsonl | head -1
Why the id: months later the file is the only artifact left, and the reasoning behind every
line in it lives in the transcript. Another agent writes its own resume line when it knows its
session id (codex resume <id>, grok --resume <id>); without a known id it writes
Session: none (<tool>) and leaves the resume line out rather than inventing one.
4. Five sections, always all five, in this order. Template with hints:
references/step-template.md. Filled: references/example-step.md.
| Section | Rule |
|---|---|
## Goal |
The distant goal, repeated verbatim in every step. Copy it from the previous step. Change it only if the human changed it, and say so in a line under it. It is the anchor against drift. |
## Done |
What this step finished: file paths, commit hashes, MR links. Facts, not intentions. |
## Where we are |
Current state, split into ### Verified (something was run or observed - say what: the test that passed, the count printed, the screenshot) and ### Believed (the session thinks so but did not check). The writer does not know what it does not know either; the split is the point. |
## Next |
The first three moves for the fresh session, concrete enough to start without thinking. Then what must not be done, and why. |
## Session |
Session id and claude --resume line, repeated from the header so it survives copy-paste. Then the commands the next session runs before trusting this file: git status, git log --oneline -5, the test command, whatever applies. |
-
Keep it short. A handoff is a map, not the transcript; the transcript is one
--resumeaway. When a working-notes README exists, Where we are links it for findings instead of restating them: the README owns what is known, the step owns what happens next. An operational fact that cost time goes into Next as a "do not" line, or into the repo's own skill, not into a growing appendix. -
Spawn the fresh session, when this folder is an Orca worktree. Writing the file is not the end of the job - the point is that the next agent starts without the human relaying anything. This skill uses the
orcaCLI for that. First build the launch command for the target agent (Launch command per agent below), then two checks, both free, before the one command:ORCA_TERMINAL_HANDLEis set in the environment. If not, this session is not running inside Orca. Go to step 7 with the manual fallback.- The command uses
--worktree current, the worktree that contains the current directory. If it fails withselector_not_found, Orca does not manage this folder (a plain folder, or a repo opened outside Orca). Go to step 7. Do not retry, do not try other selectors, do not runorca open.
orca terminal create --worktree current --title "<GOAL> resume (<agent>)" \
--command "<launch command>" --json
Five rules, each of which has a failure mode behind it:
terminal create, neverworktree create. A resumed handoff continues the same branch, so it belongs in the checkout you are already in.worktree createmakes a whole new one. Orca's own help says it: "Use this, not worktree create, for a fresh agent in the current checkout."--command, never--agent.--agentexists only onworktree create, and the launch command already names the agent. One launch path for every agent.current, neveractive.activeis whatever worktree Orca has focused, which may not be the one this session runs in.currentis the one containing$PWD.- The prompt rides in
--command, never throughorca terminal send.sendsilently truncates and delivers only the tail while reporting success. Every agent in the table takes its prompt as an argument, so nothing can be cut. - No
--focus. The new terminal appears without yanking the human out of what they are doing. They switch to it when they are ready.
Report the handle from result.terminal.handle in the JSON - not result.handle, which does
not exist. Only if the error says Orca itself is not running: orca open --json and retry
once. Any other failure goes to step 7. Never claim a session was spawned that the JSON did not
confirm.
7. End with exactly these two lines, and nothing after them:
Handoff written: <FILE>
Fresh <agent> session spawned in <worktree>: <terminal handle>
When the spawn did not happen, the second line is instead a command the human can paste into any terminal:
Fresh session: cd <ROOT> && <launch command>
Launch command per agent
<P> is the prompt. For claude it is the slash command. For every other agent it is a plain
sentence with the absolute path of the step just written, because that agent may not have this
skill installed, and a path works either way:
Resume the handoff for <GOAL>: read only <FILE>, run the checks in its Session section, then start on its Next.
One line, in single quotes, no single quote inside it (rephrase instead of escaping).
| Agent | Command |
|---|---|
claude |
claude '/handoff resume <GOAL>' |
codex |
codex '<P>' |
grok |
grok '<P>' |
gemini |
gemini -i '<P>' |
opencode |
opencode --prompt '<P>' |
cursor |
cursor-agent '<P>' |
geminineeds-i: a bare positional prompt runs once and exits instead of staying open.- Check the binary with
command -v <binary>before spawning. Missing: say so in one line above the two final lines and print the fallback command anyway, so it works once installed. - Any other agent: ask the user for its launch command. Do not guess flags.
Resuming
You are the fresh session. Do not read the whole folder; that is the context you were started to save.
- Resolve
ROOTandGOALas in Where the log lives, thenls "$ROOT/_agent/handoff/$GOAL"and open only the newest step. If the folder does not exist,ls "$ROOT/_agent/handoff/"and ask which goal; do not guess. - If the header has a Working notes line, open that README too - it holds what is already known, including the conclusions that turned out wrong. Read it before re-deriving anything.
- Read Goal, then Next. Run the verify commands from Session before trusting Where
we are. If the tree disagrees with the file (dirty
git status, a commit that is not there), say so before doing anything else. - Open older steps only when Next or Where we are points at them.
- Start work. When the next milestone is green, run
/handoffagain.