Installs into .claude/skills of the current project.
Are you the author of Gz Session Handoff?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/tvproductions-gz-session-handoff)
---
name: gz-session-handoff
persona: main-session
description: Use to create or resume a session handoff document.
category: agent-operations
compatibility: Requires GovZero v6 framework; works with any agent operating under GovZero governance
metadata:
skill-version: "7.6.4"
govzero-framework-version: "v6"
version-consistency-rule: "Skill major version tracks GovZero major. Minor increments for governance rule changes. Patch increments for tooling/template improvements."
govzero-compliance-areas: "charter (gates 1-5), lifecycle (state machine), session continuity"
govzero_layer: "Layer 3 - File Sync"
lifecycle_state: active
owner: gzkit-governance
last_reviewed: 2026-10-17
model: sonnet
---
# gz-session-handoff (v7.6.1)
## Purpose
Create and resume session handoff documents that preserve agent context across engineering sessions. When an agent pauses work on an ADR or OBPI, a handoff document captures the full state — what was done, what decisions were made, and what comes next — so that a resuming agent (or the same agent in a new session) can continue without losing context.
When the project declares `## Workflow fronts` in its registered active campaign,
read that map on CREATE and RESUME. Carry its source and each front's relevant
session delta in Important Context / Pending Work, identifying what was observed
and what still needs verification. Use `gz-status` for a general project-status
answer. The campaign owns the standing map; a handoff preserves continuity and
does not replace campaign sequencing or authorize work on another front.
## Three subjects (operator ruling, verbatim canon)
Keep three subjects distinct: transit is ecosystem movement through the airlock (ADR-0.33.0); exchange is one block’s occupancy (ADR-0.0.41); handoff is session memory (ADR-0.0.65). Classify by the citing event type, never a shared field name or path. Token blocks implement features through the airlock’s Build door. The airlock provides awareness and synthetic memory, controls project movement, keeps the agent focused and watches for contamination, and monitors results/disturbance; it is not a verification gate. ADR-0.33.0 incompletely captures those purposes; silence does not revoke them. Transit supplies current ecosystem orientation and handoff carries the prior session model; they cooperate, and neither alone supplies a resident project model.
> Carried verbatim from root `AGENTS.md` § Operator Doctrine on 2026-09-17 (GHI #921); the corpus `.gzkit/corpus/AGENTS.md.jsonl` keeps the ruling's history.
---
## CLI surface: `gz handoff` (ADR-0.0.65)
This skill wields the `gz handoff` verb — the governed CLI surface over the
handoff authoring API. Authoring routes through the fail-closed
`validate_handoff_document` gate rather than hand-written markdown:
```bash
uv run gz handoff list --adr ADR-<X.Y.Z> # list handoffs newest-first (read-only)
uv run gz handoff resume --adr ADR-<X.Y.Z> # newest handoff + staleness + first next step (read-only)
uv run gz handoff create [--adr ADR-<X.Y.Z>] --slug <slug> --agent <id> \
--summary "<text>" --context "<text>" --decisions "<text>" --next-steps "<text>" \
--pending "<text>" --verification "<text>" --evidence "<text>"
uv run gz handoff decide --handoff <path> --session-id <id> --decision proceed --operator-text "<operator's exact words>"
uv run gz handoff rulings [--limit N] [--search TEXT] # the settled-ruling corpus (read-only, GHI #838)
uv run gz handoff archive --older-than 30d --dry-run # preview move-not-delete retention (read-only)
uv run gz handoff archive --older-than 30d # move handoffs older than the threshold into archive/
```
`create` is fail-closed and requires **all seven** sections populated — each has
its own flag, and an unsupplied section is a refusal, not an empty heading
(GHI #692). `decide` books the operator's transit decision on a resumed handoff
to Layer 2 (GHI #574, #757). It **gates nothing** — the PreToolUse resume gate
was retired 2026-08-15 (see § Operator Authorization) and no tool call is
refused for want of a ruling. The record is provenance: what the operator said,
verbatim, about which document. `authorize` is a retained deprecated alias. `archive` is move-not-delete: it
relocates handoffs older than `--older-than` into `.gzkit/handoffs/archive/`,
skipping any that are lock-coupled or are the `continues_from:` target of a
still-canonical handoff, so the audit trail is preserved and no resume chain is
orphaned. See the manpages under `docs/user/manpages/handoff*.md`.
### Settled-citation annotation
`create` resolves every reference cited in a **prospective** section — `Immediate
Next Steps` and `Pending Work / Open Loops` — against its Layer-2 authority, and
annotates a settled one `[settled]` in place. A handoff therefore cannot be
*written* naming settled work as open.
Retrospective sections are never annotated: a closed GHI in `Current State
Summary`, `Evidence / Artifacts`, `Decisions Made`, or `Settled Rulings` is the
correct record of what the traversal did, and marking it would falsify the
archive. `Pending Work / Open Loops` is in scope because that is where a handoff
parks work for a *future* session — the longest-lived place a stale citation hides.
It annotates, never refuses: citing and depending are different claims, and a step
may name a closed GHI as provenance rather than as a precondition. Only `settled`
marks — an unresolvable reference is `unknown`, which is missing evidence rather
than evidence of a closed issue.
**Do not hand-author around it.** Writing a handoff with `Write` instead of
`gz handoff create` bypasses this check along with the whole fail-closed gate.
---
## Trust Model
**Layer 3 — File Sync:** this skill writes files; a handoff proves nothing about the state it describes (§ Claim Verification Gate).
- **Reads:** User input, handoff template, canonical handoff directory `.gzkit/handoffs/`
- **Reads (CREATE):** every reference cited in a **prospective** section, each against its own Layer-2 authority — GHI via `gh issue view`, OBPI and ADR from the ledger — to annotate settled citations at authoring time (§ Settled-citation annotation). Unreachable `gh` resolves `unknown` and annotates nothing.
- **Writes:** Handoff markdown files under `.gzkit/handoffs/` (canonical storage per ADR-0.0.41 / OBPI-0.0.41-03); the settled-ruling store `.gzkit/handoffs/rulings.jsonl`; and, through `gz handoff decide`, one Layer-2 `handoff_resume_decided` event
- **Validates:** No placeholders, no secrets, all sections present **and populated**, referenced files exist
- **Blocks (RESUME): NOTHING. There is no gate.** A handoff ADVISES, and that is the whole mechanism; § Operator Authorization carries the retirement ruling. Every mutating tool call — `Write`, `Edit`, `NotebookEdit`, `gz obpi complete`, `gz attest`, commits — runs on an unruled handoff. **Gate 5 human attestation, the commit-time hooks (ruff/ty/xenon and the guards) and the pre-push `gz check`, which runs every validator and the tests the change touches, are untouched.**
- **Still records:** `gz handoff decide` books the operator's verbatim ruling to Layer 2 (`handoff_resume_decided`, `proceed` | `pause` | `hold` | `revert`). That record is provenance an operator and an auditor read; nothing gates on its absence. The booking-coupling control (GHI #795/#797) still fails `gz check` if a ruling is recorded against a document other than the one advised.
- **Does NOT write:** ADR status, OBPI brief status
---
## Inputs
| Parameter | Required | Description |
|-----------|----------|-------------|
| `adr_id` | No | ADR identifier (e.g. `ADR-0.0.25`). Omit for work with no parent ADR — a handoff carries continuity for **any** work, not only ADR-scoped work (GHI #709). `mode`, not `adr_id`, is the is-this-a-handoff discriminator. |
| `branch` | Yes | Current git branch (or use `git branch --show-current`) |
| `agent` | Yes | Agent identifier (e.g. `claude-code`, `codex`) |
| `slug` | Yes | Short descriptor for filename (e.g. `create-workflow`) |
| `obpi_id` | No | OBPI identifier if handoff is scoped to a specific brief |
| `last_lock_event_timestamp` | When concluding a held lock | Frontmatter key — ts of the matching `obpi_lock_claimed` event (Sub-Invariant 2; read by `gz validate --lock-exchange-coupling`) |
| `last_commit_sha` | When concluding a held lock | Frontmatter key — HEAD at handoff creation (`git rev-parse --short HEAD`) |
| `session_id` | No | Session identifier for tracing |
| `continues_from` | No | Path to previous handoff document (for chained sessions) |
## Outputs
- Handoff markdown file at `.gzkit/handoffs/{timestamp}-{slug}.md`
- Validation result (pass/fail with error details)
- First next action from "Immediate Next Steps" section, surfaced as an **advisory** for operator review on resume (not a license to execute — see § RESUME Procedure, Resume contract)
## Assets
- **Handoff Template:** `assets/handoff-template.md` (co-located with this skill)
- **Staleness rules:** `assets/staleness-rules.md` (the multi-factor classification thresholds)
---
## CREATE Procedure
The CREATE workflow writes a new handoff document when an agent is pausing work. `gz handoff create` is the only authoring path (§ Settled-citation annotation): it stamps the timestamp, branch and frontmatter, writes `.gzkit/handoffs/{timestamp}-{slug}.md`, and writes nothing when validation fails.
### Steps
1. **Read the template** `assets/handoff-template.md` (co-located with this skill) for what each section must carry.
2. **Choose the mode** (`--mode`):
- `mode: CREATE` — or `mode: CHECKPOINT` when the session is **bookmarking, not departing** (GHI #756). Both write a full seven-section handoff; they differ in what they mean. A `CHECKPOINT` records a pause mid-flight — before a long verification run, at a `/clear` boundary inside a multi-task session — and does **not** satisfy token-block § Sub-Invariant 5, so it can never discharge a lock release. Use `CREATE` when the session is concluding.
3. **Set the optional links:** `--adr` and `--obpi` when the work has them, `--session-id`, and `--continues-from <path>` for a chained session (repeatable).
4. **Draft each required section** with session-specific content; each is passed by its own flag:
| Section | Content |
|---------|---------|
| Current State Summary | What was done, what phase the work is in, last action status |
| Important Context | Architectural constraints, non-obvious dependencies, gotchas |
| Decisions Made | Decisions with rationale and rejected alternatives. **Lead each entry with `[operator-ruled]` or `[agent-chose]`** (GHI #696 defect 4) |
| Immediate Next Steps | Ordered list of 3-5 concrete next actions |
| Pending Work / Open Loops | Deferred items, blockers, discovered work |
| Verification Checklist | Commands and checks for the resuming agent |
| Evidence / Artifacts | File paths (backtick-quoted) produced during the session |
**Attribute every decision, and write it as a list item.** An operator ruling
and an agent's own choice rendered identically is what made both equally
re-arguable in the next session (GHI #696 defect 4) — operator canon is
verbatim, *"MY WORD IS AUTHORITY IN ALL CASES."* Each entry MUST begin with a
list marker (`- `, `* `, or `N. `) and then lead with `[operator-ruled]` or
`[agent-chose]`; attribution matching is case- and spacing-tolerant. An
unmarked entry parses as **unattributed** and is never promoted to a ruling nor
demoted to a preference — but it also does not carry forward, so an unmarked
operator ruling is a ruling you will re-argue.
```markdown
- [operator-ruled] Ship the thing (verbatim: "ship it").
- [agent-chose] Used a temp dir for the fixture.
```
**The list marker is load-bearing, not styling (GHI #722).** `[operator-ruled] ...` with no `- ` parses to NOTHING, and the successor's `Settled Rulings` promotes zero rulings; ten operator rulings left the chain that way before it was caught. `validate_decision_markers` now refuses the shape at authoring.
**The mechanical completion handoff attributes its own decision.** `write_completion_handoff` (the token-block exit edge, GHI #619) marks its entry `[operator-ruled]` unconditionally — Gate 5 is universal (ADR-0.0.36), so a completion handoff's decision is *always* a human attestation. The abandon-path writer stays unattributed on purpose — its entry is a mechanical lock-surrender record, not a ruling. In these mechanically written handoffs, embedded evidence (attestation text, implementation summary, key proof) is cut at 600 chars on a word boundary with a trailing `…`; the full attestation always remains in the ledger receipt.
**`Settled Rulings` is written for you — do NOT hand-fill it.** The optional
`## Settled Rulings` section is composed by construction: `create_handoff`
carries the predecessor's settled entries forward and promotes its
`[operator-ruled]` decisions into it, de-duplicated (GHI #696 defect 3). A
ruling booked once keeps arriving, so it is never re-filed as an open loop and
re-adjudicated. A handoff's OWN `[operator-ruled]` decisions are booked into
the store the moment it is written (GHI #1000, operator ruling 2026-09-13), so
`gz handoff rulings --search` returns them before any successor exists; only
INHERITANCE still waits for a `continues_from` link. It is deliberately NOT a required section — the
`handoff-documents` gate validates the whole post-cutover corpus, and a
required section would fail all of it.
**The corpus lives in a store; the section is a POINTER (GHI #838).** Rulings
are recorded in `.gzkit/handoffs/rulings.jsonl` — append-only, keyed on
`ruling_key` — and the rendered section carries a count and a pointer rather
than the entries. Read them with `uv run gz handoff rulings` (`--search TEXT`
to check whether a question is already settled before re-arguing it; `--limit
N` for the newest few). Nothing retires: the retention question is answered NO, and only the transport changed (the section once copied every predecessor's prose and reached 91.4% of a document). `ruling_key` is untouched — widening it to shrink the corpus is the fix GHI #838 rejects, because collapsing two genuinely distinct rulings drops a booked operator ruling silently.
**Never hand-edit `rulings.jsonl`.** It is written by `create_handoff`
composing from the predecessor and booking the document's own rulings. Editing it directly is the same class of defect
as editing `.gzkit/ledger.jsonl` by hand.
**A ruling that arrives AFTER the handoff is committed must be seated in the
next one.** Composition runs at authoring time, so a late ruling — the operator
rules on a GHI once the session's handoff is already written — has no home in
that handoff. On the next CREATE, pass it via `--settled "<ruling>"`
(repeatable). It unions with the carried set and never replaces it, so seating
one late ruling cannot drop the booked history. Check the prior session's
trailing rulings before authoring: if one is not already carried, seat it.
Routine use of the flag is a signal that rulings are arriving outside the
handoff cycle and belong in a durable ruling store (campaign Movement D box 3).
**Never restate an inherited ruling in `Decisions Made` (GHI #1003).** A
successor already carries every ruling its `continues_from` lineage booked.
Writing one out again as its own `[operator-ruled]` entry books it a SECOND
time the moment the handoff is written (GHI #1000), and any rewording makes it
a new corpus entry: `ruling_key` folds only quote glyphs, whitespace and case,
and widening it is the fix GHI #838 rejects.
`Decisions Made` holds only the rulings THIS traversal received; a ruling that
arrived after the predecessor was written goes in via `--settled`. Check with
`gz handoff rulings --search` before writing an `[operator-ruled]` entry.
**(Advisory — telling a reworded restatement from a distinct ruling needs
ruling identity, which is Movement D's `ruling_issued` event.)**
5. **Run `gz handoff create`** with all seven section flags (§ CLI surface). It validates before writing:
- Parse frontmatter and validate with `HandoffFrontmatter` model
- No placeholder markers (TBD, TODO, FIXME, ...) in the body
- No secrets (passwords, API keys, tokens, private keys)
- All 7 required sections present and populated
- All file paths referenced in Evidence / Artifacts exist on disk
6. **Report** the result:
- File path where the handoff was written
- Validation result (pass or list of errors)
- First item from "Immediate Next Steps" (for quick resumption context)
### Programmatic API (`gzkit.handoff_api`)
The CLI wraps `gzkit.handoff_api` (OBPI-0.0.65-02): `create_handoff`, `scaffold_handoff`, `list_handoffs`, `load_handoff_chain` and `resume_handoff`. Every authoring path routes the produced document through `gzkit.handoff_validation.validate_handoff_document`, so a handoff that fails the gate is **never written** (`create_handoff` raises `HandoffValidationError` carrying the violation list). `scaffold_handoff` pre-fills the factual sections from injected observed state with **no LLM or network call**; the judgment sections (Decisions Made, Important Context) are author-supplied. `resume_handoff` derives staleness from an injected `now`, so classification is deterministic. Signatures live in the module docstrings.
---
## Auto-load on session start (CAP-13, GHI #326)
A SessionStart hook (`.claude/settings.json` for Claude Code, `.codex/hooks.json` for Codex CLI) runs `scripts/session_orientation.py` on every session boot. Its "Most-recent handoff" section selects the newest file under `.gzkit/handoffs/`, classifies its age in the Fresh / Slightly-Stale / Stale / Very-Stale buckets this skill uses, and carries the lineage, every advised step and the `gz handoff decide` pointer, so the resuming agent sees them before its first response without operator prompting.
That section is the one session-start channel for the handoff; a project without the orientation script gets the same advisement from `.claude/hooks/session-start-advisement.py`, on `additionalContext` only (drydock RO-1 act a, 2026-10-17). The hook is the mechanical floor; this skill's RESUME workflow remains the canonical path for the full chain traversal, branch verification, or staleness classification. Operators who do not want orientation injection can disable the hook in their local settings.
## RESUME Procedure
The RESUME workflow discovers, loads, validates, and reports on existing handoff documents so a resuming agent can continue work.
> **Resume contract — a handoff ADVISES; it does not authorize.** A handoff
> records a *proposed* plan and its context. It is **NOT** a clearance to execute
> that plan. On resume — at **every** freshness level, including Fresh — you MUST:
> (1) present the advised next steps and current state to the operator;
> (2) obtain explicit operator authorization before executing any of them — no
> file mutation, no `gz` ceremony, no migration until the operator says go;
> (3) treat the human-as-final-witness doctrine as binding from the first step —
> **you advise; the operator rules; you note variance and stop.**
> Barreling into execution from a handoff is the exact failure this contract
> exists to prevent. The plan is the destination; operator authorization is the
> ignition. Staleness escalates *verification depth* (below); it never relaxes the
> authorization requirement, and freshness never waives it.
### Steps
1. **List available handoffs** using `list_handoffs(adr_id)` (`uv run gz handoff list`; `adr_id` is optional — GHI #709). This scans `.gzkit/handoffs/` for `.md` files, matches the `adr_id:` frontmatter when one is given, parses each frontmatter, and returns them sorted newest-first.
2. **Select a handoff** — either the newest (default) or a specific file if `handoff_path` is provided.
3. **Classify staleness** using `classify_staleness(timestamp)`. Staleness sets
how much you re-verify before presenting — it does **not** decide whether you
need operator authorization (you always do, per the Resume contract above):
- **Fresh** (< 24h): present advised steps + state; verify branch and evidence paths; await operator authorization
- **Slightly Stale** (24-72h): also re-verify key assumptions before presenting
- **Stale** (72h-7d): deep re-verification required; `requires_human_verification` flag set
- **Very Stale** (> 7d): deep re-verification required; `requires_human_verification` set; consider re-creating the handoff
4. **Load the handoff content** — read the file and parse frontmatter.
5. **Follow the handoff chain** via `load_handoff_chain(handoff_path)` — recursively traverse `continues_from` links (depth limit: 20) to reconstruct session lineage from oldest ancestor to current document.
6. **Verify context** using `verify_context(content)`:
- Check branch mismatch (handoff branch vs. current branch)
- Re-validate referenced file paths in Evidence section
7. **Verify the handoff's claims against Layer-2 (Claim Verification Gate).** Walk
the Current State Summary, Decisions Made, and Immediate Next Steps; for every
completion / lock / gate / readiness claim, run the matching Layer-2 check from
the § Claim Verification Gate table and tag the claim **VERIFIED**, **STALE**,
or **UNVERIFIABLE**. Verify the **precondition of each advised step** too — a
step whose precondition is STALE is void. Never relay a handoff claim as fact
without this check.
**`uv run gz handoff resume` runs the advised-step arm of this gate for you**
(GHI #696 defect 2). It extracts every governance reference each step cites and
resolves each against its own Layer-2 authority — GHI through `gh`, OBPI and
ADR from the ledger (GHI #1076, #1078) — rendering `live` / `settled` /
`unknown` and marking a step **CITES SETTLED** when a citation is `settled`.
An ADR is `settled` when its closeout is RECORDED, never when a ceremony
merely began. Start there rather than hand-rolling it; hand-verify each `unknown`.
**The flag is a citation, not a verdict — you still adjudicate.** A step may
name a closed GHI as a *precondition* (the work is done, the step is a STALE
claim, do not relay it) or as *provenance* ("the fix that landed in #696", the
step still stands). No available signal distinguishes them, so the tool reports
and you decide. Treating every flagged step as void discards live work; treating
none as void is the decay this gate exists to catch.
8. **Extract the next steps** from the "Immediate Next Steps" section — `resume_handoff` returns `ResumeResult.steps`, one entry per authored step (with its references), and `next_steps` / `first_next_step` as derived text projections. An enumeration collapsed onto one line still yields one entry per step.
9. **Report** the result, then **stop and await operator authorization** (do not begin executing):
- File path of the resumed handoff
- Staleness classification and re-verification depth applied
- **Each presented claim tagged VERIFIED / STALE / UNVERIFIABLE** with its Layer-2 receipt; STALE claims and the advised steps they void called out explicitly
- First next step, presented **for operator review and authorization** (not for immediate action)
- Validation errors and context warnings
- Chain of predecessor handoffs
### Operator Authorization — ADVISORY, NOT MECHANIZED
**A resume is presented to the operator, who rules.** The agent presents the
advised next steps and current state, then waits. This is the
human-as-final-witness doctrine applied to session resumption: the agent
advises, the operator rules, the agent notes variance and stops.
**Nothing enforces this any more, by operator ruling.** The obligation is on the
agent's professionalism and on the § Claim Verification Gate below — not on a
hook.
**The gate is RETIRED (operator ruling 2026-08-15).** It bound mechanically from
2026-07-16 as `.claude/hooks/handoff-resume-gate.py`, a PreToolUse refusal on an
unruled handoff. Both arms are now gone — `Bash` on 2026-08-14 (*"why is the
handoff gate so picky? it is a reminder of what we were doing and what to do
next, not a nanny"*), `Write|Edit|NotebookEdit` on 2026-08-15 (*"the handoff
should be an advisor, not a gate-keeping nanny"*). The hook, its generator
template, and both negative controls (`-bash`, `-write`) are deleted; a control
asserting enforcement that no longer happens is worse than no control.
Why the whole arm: GHI #574, which authorized the gate, quoted the remedy it was
meant to apply — *"place the human at a mechanical gate, **not at every
keystroke**"* — and it shipped as a hook on every mutating call. Entry/exit
authorization is TRANSIT's subject (the airlock, ADR-0.33.0); a handoff is a
synthetic memory refresh, which has no natural blast radius, so "what should
reading a reminder prevent?" never had a principled answer. Measured lifetime:
**9 lifts, 1 block** over the single day refusal-recording existed, against 13
admission-breadth corrections in 29 days.
What still binds is named in § Trust Model rather than hidden: Gate 5 human
attestation, the pre-commit hook, and the pre-push `gz check`.
**Booking the ruling.** When the operator rules, record their VERBATIM words:
```bash
uv run gz handoff decide --handoff <path> --session-id <id> \
--decision proceed --operator-text "<their exact words>"
```
**This is an acknowledge-and-decide transit, NOT an attestation (GHI #757).**
ADR-0.0.33 § Alternatives rejects that conflation by name — completion
attestation is reserved for claims about completed planned work, and spending
that register on an every-transit gate cheapens the sacred word. The grammar is
borrowed from the airlock's `Decision`; the records stay this layer's own.
| Decision | Meaning |
|---|---|
| `proceed` | work the advised steps |
| `pause` / `hold` / `revert` | do not |
**No decision lifts anything now — there is no gate.** The token records what the
operator ruled. Book the `hold` anyway: the predecessor shape was a consent
boolean, so an operator who looked and ruled *not yet* left no record at all, and
that silence is what the decision grammar exists to end. Add
`--set-aside "<step>"` for any advised step the ruling declines: that is the
clearance-amendment record (*"ATC keeps a record of all clearances issued and
all amendments."*).
A ruling given in conversation and never booked leaves no Layer-2 record of what
the operator decided. Nothing refuses you for that now; the loss is the record
itself. Never author `--operator-text` for words the operator did not say: that is fabrication, the same failure as fabricating a receipt id.
The words stay VERBATIM (operator ruling 2026-08-05); only the register changed.
`gz handoff authorize` remains as a deprecated alias and behaves identically.
**Nothing is refused while unruled.** The read allowlist this section once carried has no subject with the gate retired — it needed 13 corrections in 29 days, every one a false refusal of a mandated verification. Run whatever verification the § Claim Verification Gate obliges.
One prohibition is unaffected because it never came from this gate: `gh issue
create` is forbidden by `AGENTS.md` § Behavior Rules (author GHIs
through `/ghi-author`).
### Claim Verification Gate (universal)
**A handoff is Layer-1 narrative authorship. Every assertion it makes about
completion, lock state, gate status, or "now unblocked / now satisfiable" is
UNVERIFIED until checked against Layer-2 truth (the ledger and `gz` state).**
This is `AGENTS.md` § Behavior Rules (completion evidence is the ledger; rationale in `docs/governance/behavior-rules.md` § Never #7) applied to resume: do not read a status claim as proof of the status — read the ledger. This gate governs whether
you may **believe or relay** what the handoff says. It is now the only gate in
this skill — the execution gate is retired — and it fires at every freshness
level, Fresh included.
Before you present any handoff claim to the operator, and before you suggest any
advised step, verify the claim — and verify the **precondition of each advised
step**, because an advised step is only actionable while its precondition still
holds:
| Handoff claim shape | Layer-2 check | Source of truth |
|---------------------|---------------|-----------------|
| "OBPI complete" / "attested-complete" | `uv run gz obpi status <OBPI-ID>` → `Runtime State` / `Completion` | ledger |
| "lock still held" / advises "release the lock" | `uv run gz obpi lock list` | lock registry |
| "Gate N passed" / "gates green" | `uv run gz status` | ledger |
| any artifact-state / readiness claim | `uv run gz state` | artifact graph |
| "tests were green" / coverage claim | re-run the canonical step (see Verification Checklist) | observed output |
| "GHI #N CLOSED / OPEN" / advises "rule on GHI #M" | `uv run gz handoff resume` (**mechanized** for advised steps — GHI #696), else `gh issue view <N> --json state,title` | GitHub issue state |
| "PR #N merged" / "released vX.Y.Z" | `gh pr view <N>` / `gh release view vX.Y.Z` | GitHub |
| "origin/main in sync" / "pushed" / any ahead-behind claim | `git rev-list --left-right --count origin/<branch>...HEAD` | git refs |
**This table is the instrument index for verification — advice, not a permission boundary.** Nothing refuses a verification command. Keep adding claim shapes and their instruments here: the table's job is to tell you HOW to check a claim, and an unlisted shape is unhelpful, not uncheckable.
**Tag every claim you present** as **VERIFIED**, **STALE**, or **UNVERIFIABLE**.
A STALE claim voids any advised step that depends on it: surface the variance and
stop — do not relay the step as actionable. Worked example (2026-06-14): a handoff
asserted *"OBPI lock still held (release is step 1)"*; `gz obpi lock list`
returned **no active locks** — the lock had already been released in a later
session. Relaying "release the lock" as the next action would have acted on a
claim that was false at read-time. The completion claim in the same handoff
verified TRUE (`gz obpi status` → `ATTESTED COMPLETED`); claims are verified
**individually**, never trusted as a block.
---
## Failure Modes
| Failure | Cause | Resolution |
|---------|-------|------------|
| Template not found | `assets/handoff-template.md` missing or path incorrect | Verify skill directory structure |
| Canonical handoff directory missing | `.gzkit/handoffs/` does not exist at project root | Run `gz init` or create the directory; the path is doctrine-canonical per ADR-0.0.41 |
| Validation: placeholders | Body contains TBD, TODO, FIXME, or `...` markers | Replace all placeholder text with actual content |
| Validation: secrets | Body contains password=, api_key=, Bearer tokens, etc. | Remove all secret material from the document |
| Validation: missing sections | One or more of the 7 required sections not present | Add all required section headings |
| Validation: missing files | Evidence section references files that don't exist on disk | Verify file paths or remove stale references |
| No handoffs found | `list_handoffs()` returns empty for the ADR | Create a handoff first using the CREATE workflow |
| Stale handoff | Handoff age exceeds 72 hours | Deep re-verification before presenting; every handoff is presented to the operator, whatever its age |
| Branch mismatch | Handoff branch differs from current branch | Verify with human whether branch change is intentional |
| Broken chain | `continues_from` points to a non-existent file | Treat current handoff as chain start; note missing predecessor |
---
## Acceptance Rules
### CREATE
- All 7 required sections **populated** with session-specific content — **mechanized** by `validate_sections_populated` since GHI #692; an empty required section is a refusal, not a warning
- **"No HTML comments remaining" is NOT mechanized — do not read the population gate as enforcing it.** `validate_sections_populated` and `validate_no_placeholders` both *strip* comments before scanning, so a section containing only a scaffold comment is flagged empty, but a comment sitting above real content passes untouched. `_sanitize_handoff_text` drops comments from mechanically embedded evidence; an **author-written** handoff must still be read by a human for leftover scaffold prompts. A wider validator was rejected deliberately: it would fail the whole legacy corpus, the same reason `validate_decision_markers` is asymmetric
- Frontmatter validates against `HandoffFrontmatter` Pydantic model
- Full validation pipeline passes (no placeholders, no secrets, sections present **and populated**, references exist)
- File written to correct path: `.gzkit/handoffs/{timestamp}-{slug}.md`
### RESUME
- `list_handoffs()` discovers and sorts available handoffs newest-first
- `classify_staleness()` correctly categorizes handoff age (Fresh / Slightly Stale / Stale / Very Stale)
- Stale and Very Stale handoffs set `requires_human_verification = True`
- `extract_first_next_step()` extracts the first action item for quick resumption
- `load_handoff_chain()` traverses `continues_from` links with depth limiting and cycle detection
- `verify_context()` detects branch mismatches and missing referenced files
- `resume_handoff()` orchestrates the full workflow and returns a `ResumeResult`
---
## Common Rationalizations
These thoughts mean STOP — you are about to lose context across the session boundary:
| Thought | Reality |
|---------|---------|
| "The handoff says the OBPI is complete, so it's done" | The handoff is Layer-1 narrative; completion is a Layer-2 fact. Run `gz obpi status <OBPI-ID>` and read `Completion` before you say "done." `AGENTS.md` § Behavior Rules (completion evidence is the ledger). |
| "The handoff says the lock is held — I'll release it (step 1)" | The lock-held claim is unverified until `gz obpi lock list` confirms it. If the lock was already released in a later session, "release the lock" is a void step acting on a stale precondition. Check before relaying. |
| "I'll just relay the handoff's next steps to the operator as the plan" | Relaying a claim is asserting it. An advised step whose precondition is STALE is not a plan, it's misinformation. Tag each claim VERIFIED / STALE / UNVERIFIABLE first. |
| "The handoff is Fresh, so I can just start on its next steps" | Freshness shortens re-verification; it never authorizes execution. Nothing mechanically stops you since 2026-08-15 — which is exactly why this is on you. Present the advised steps, then wait for the operator to rule. |
| "The handoff is slightly stale but I remember the work" | Staleness deepens re-verification for a reason. Memory is not a substitute for explicit verification. Re-verify, present to the human, and wait. |
| "Branch mismatch is fine, I know what I'm doing" | The branch field exists because branch state is part of session context. Mismatch means the world changed under the handoff. Verify with the human. |
| "I'll fill the placeholders in later — let me write the scaffold first" | The validation gate rejects placeholders. "Later" means the next agent inherits TBD/TODO markers. Populate every section now. |
| "All 7 sections are overkill for a 30-minute session" | The 7 sections are the minimum for context preservation. Skipping any one strands the resuming agent in exactly the place that section would have explained. |
| "The Evidence section references files that exist locally — close enough" | Validation checks every referenced path on disk. A broken reference in a handoff is a broken handoff. Fix or remove. |
| "I can summarize the chain in one document instead of following continues_from" | The chain is the lineage. Summarizing it loses the audit trail and the rationale that led to the current state. Traverse it. |
| "This work is uncommitted — I'll handoff after I commit" | Handoffs preserve the in-flight state including uncommitted decisions. Commit pressure is exactly when context is most fragile. Write the handoff now. |
## Red Flags
- Writing a handoff with HTML-comment placeholders still present in any section
- Relaying a handoff's completion / lock / gate claim as fact without a Layer-2 check (`gz obpi status`, `gz obpi lock list`, `gz status`, `gz state`)
- Suggesting an advised step whose precondition you have not re-verified at read-time (the lock-already-released trap)
- Executing a handoff's next steps without explicit operator authorization — at any freshness level. No hook refuses this any more (gate retired 2026-08-15); the contract is unchanged and is now held by the agent, not the harness.
- Resuming a Stale or Very Stale handoff without the deep re-verification its age requires
- Resuming with a branch mismatch and "I'll fix it as I go"
- Creating a handoff that references files via prose instead of backtick-quoted paths
- Skipping the Decisions Made section because "nothing important was decided"
- Filling Immediate Next Steps with vague intent ("continue the work") instead of concrete actions
- Creating a chained handoff without setting `continues_from`
## Related Skills
| Skill | Relationship |
|-------|-------------|
| `gz-adr-create` | Creates ADR packages this handoff's `adr_id:` may reference (handoff storage is canonical at `.gzkit/handoffs/`, independent of ADR package layout) |
| `gz-obpi-specify` | OBPI briefs that handoffs may reference |
| `gz-adr-closeout-ceremony` | Closeout may reference handoff chain as evidence |