I'm Down
You are signing off. This skill is the producer side of the session-boundary contract. Its receiver is im-up.
Stable contract
The next session must take the correct first action without trust in conversational memory.
The receiver contract in PACKET-FORMAT.md defines the producer output.
Preflight
- Read
.claude/session-boundary.json. - Stop if the config or packet directory is absent.
- Treat
$ARGUMENTSas the next-session purpose. - Stop if
$ARGUMENTSis empty. - Confirm the exact objective and next action from current repository state.
The close and the packet are one action
A close ritual writes durable session state and commits it before the packet is produced. One public entrypoint owns the full sequence: write the state file, commit it (moving HEAD), then produce the packet that records the post-commit HEAD. Callers cannot reorder those stages.
"close_commit": { "contains": "RITUAL:" },
"state_file": ".claude/session-state.json"
Stable contract: the close_commit.contains key, the state_file key, and that produce mode refuses a packet whose HEAD commit message does not contain the close marker. Illustrative: RITUAL: itself — that is one project's marker, and yours is whatever string the close writes into its commit message.
contains is a literal substring test, not a regex. "^RITUAL:" matches nothing and would refuse every packet.
A project that declares no close_commit is unaffected by the marker check; the merged close still requires state_file.
Procedure
- Run
close_session.pywith the objective, next action, and$ARGUMENTSpurpose. This is the only close command: it writes durable state, commits with theclose_commitmarker, runs the receiver checks, and writes the packet scaffold at the post-commitHEAD. 1a. A red receiver check refuses the packet; the state commit stands. Fix the cause now, while this session holds the context, and close again. - Open the generated packet path from the script's JSON output.
- Replace every
__REQUIRED__marker. - Record failed approaches and null results in time order.
- Record decisions with their reasons.
- Add each claim the work rests on with
verifiedorunverifiedstatus. - Use typed
path,commit, orcommandprobes for verified claims. 7a. Use acommandprobe only when the config authorises that exact command. An unlisted command probe rejects the packet. - Label
skills_dispatchedastelemetryonly when an event source exists. - Otherwise use
model-reportedand preserve that evidence limit. - Reference source artifacts. Do not copy their contents.
- Run
validate_packet.pyin produce mode. - Claim handoff readiness only after an
ACCEPTEDreceipt. - Return the packet path, packet ID, HEAD, and exact receiver command.
Example close (one command):
python <skill-dir>/close_session.py \
--config .claude/session-boundary.json \
--objective "<bounded outcome>" \
--next-action "<exact action>" \
--purpose "$ARGUMENTS" \
--repo-root .
Example validation after the packet body is filled:
python <skill-dir>/validate_packet.py <packet.md> \
--mode produce \
--repo-root . \
--config .claude/session-boundary.json
When the project owns its own close sequence
close_session.py writes its own state file and generates its own commit message. A project whose close commits a caller-authored message, or decides by judgement what belongs in the commit, drives the packet stage directly with snapshot_state.py:
python <skill-dir>/snapshot_state.py \
--config .claude/session-boundary.json \
--objective "<bounded outcome>" \
--next-action "<exact action>" \
--purpose "$ARGUMENTS" \
--repo-root .
snapshot_state.py runs the receiver checks, refuses when one is red, and otherwise writes the packet scaffold and prints its path. It does nothing else.
That caller now owns the ordering guarantee close_session.py holds inside one process: commit first, snapshot second, then assert that the packet's recorded head equals HEAD measured after the commit. A caller that skips the assertion ships packets the receiver rejects as stale.
Use close_session.py unless the project's close needs that control.
Boundary limits
The operator runs /clear. The producer owes them a safe-to-clear verdict, computed from five checks and never asserted. /clear is the one action at a session boundary whose cost is one-way, so an ACCEPTED receipt is not the verdict; it is the first check.
- The packet re-validates
ACCEPTEDat the currentHEAD, not theHEADit was minted against. HEADequals the packet's recorded head and the remote ref.- The working tree is clean apart from the exclusions the boundary config declares.
- Every long-running job the session launched is finished, judged on two signals: process state, and output or CPU. A flat log alone is not a verdict; a job can sit silent for an hour while its worker burns a core.
- Everything the next session needs is in committed state. A scratchpad path and the conversation are not committed state.
Report residual risk beside the verdict even when it is yes: an unpushed branch, an untracked file, a job whose output lands where the durable state does not name it.
Produce the packet after the session's final commit. A later commit moves HEAD and the receiver rejects the packet as stale. Keep the packet directory out of version control.
close_commit checks that the close happened, not that nothing follows it. A commit made after an accepted packet still invalidates it, and the stale-HEAD check is what catches that.
Do not install a Stop hook in this version. A Stop hook fires after ordinary responses and misses interrupts.
Native Claude Code transcripts remain the abnormal-exit recovery path. This packet is an audited execution bootstrap.
Band rotation (removing stale session-band index stubs from the state file) is out of scope. The convention is carried as prose in the project's state file, not enforced by this skill. No enforcing surface exists; the operator manages it manually.