Writing session handoffs

Write a handoff that a fresh session can pick up and finish, and let it lint clean once the work is done.

What a handoff is

A handoff is a ready-to-run prompt for the next session, one file per handoff under docs/handoffs/<NNN>-<name>.md. It is never deleted. A session that picks one up sets its status to in_progress, then to consumed once the work lands.

1. Add the namespace

ctxgrd pack add handoff

This writes [HANDOFF] into ctxgrd.toml, path-claimed at docs/handoffs/**. If your project already uses the workflow pack for SPEC/TASK/PROMPT, you already have it – workflow depends on handoff, so ctxgrd pack add workflow writes both:

Added pack 'handoff' — 1 namespace, 0 rule families.
Added pack 'workflow' — 3 namespaces, 2 rule families.

2. Scaffold one

ctxgrd new HANDOFF "Ship the widget"

This writes docs/handoffs/001-ship-the-widget.md with an id, an empty status, and five empty H2 headings: Goal, Context, Next Steps, Routing, Verification.

3. Fill in the required headings

While a handoff is open, all five headings must hold content. Goal, Context, Next Steps, and Routing take prose:

---
id: HANDOFF-1
title: Ship the widget
status: pending
---

# HANDOFF-1: Ship the widget

## Goal

Ship the widget to production.

## Context

The widget passed review.

## Next Steps

Deploy it.

## Routing

developer

4. Put a runnable command under Verification

Verification is different: it must contain a fenced code block, not prose. Under the ## Verification heading, put the command in its own fence:

curl -sf https://widget.example.com/health

A handoff whose Verification holds only prose reports:

error[core.required-headings]: section 'Verification' contains no fenced code block
  help: put the command a reader runs in a fenced block under `## Verification`

A fresh session runs that command to find out whether the work is actually done, instead of taking your word for it.

5. Pick a status from the fixed vocabulary

ctxgrd rules core.allowed-values

[HANDOFF] allows five values: pending, in_progress, consumed, superseded, deferred. There is no done or open – done would duplicate consumed and open would duplicate pending.

6. Let a closed handoff stay as it is

The heading and code-block checks apply only while status is pending or in_progress. A handoff with no status at all is still checked – leaving it out is not the same as closing it. Once you mark a handoff consumed, superseded, or deferred, it can keep whatever shape it already has:

$ ctxgrd
ok: 1 document · 8 rules · 0 diagnostics

This holds even for a consumed handoff missing every heading. It is a record of finished work, not a live document, so nothing more is asked of it.

7. Watch the size budget

[HANDOFF] warns past 20,000 characters, because a fresh session loads a handoff whole:

warning[core.file-budget]: file is 21112 characters (budget 20000)
  help: trim 1112 characters — move settled detail into a linked file and reference it, or archive the parts that are done

It is a warning, so it never fails your build. Trim the handoff, or move settled detail into a file it links to.

8. Lint and iterate

ctxgrd
ctxgrd --format json | jq '.diagnostics[] | select(.file | startswith("docs/handoffs"))'

Exit codes follow the standard contract: 0 clean, 1 diagnostics reported, 2 kernel or config error.

Reusing the status-scoped check elsewhere

statuses and code_in are general keys on core.required-headings, not specific to handoffs. Any namespace with a status lifecycle can require headings, or a code block in one of them, only while a document is in a given state:

[RUN."core.required-headings"]
headings = ["Trigger", "Steps", "Rollback"]
statuses = ["active"]
code_in = ["Rollback"]

Run ctxgrd rules core.required-headings for the full parameter list.

Next steps