feat(wiki): document session-handoff skill design rationale
Capture the design decisions behind the session-handoff skill in a dedicated `.wiki/concepts/session-handoff-skill-design.md` page, indexed and logged per Karpathy LLM Wiki conventions. Covers the problem statement (cold-start fog between CC sessions), the sliding-overwrite contract for `.tasks/NEXT_SESSION.md`, the phrase whitelist + anti-pattern guards + ambiguity-asks-not-guesses rule, the substantive-commit heuristic (prefix exclude AND (body>200 OR files>3) + first-non-trivial-commit-always exception), the opt-in PostToolUse hook that replaces agent-side memory with harness-side determinism, the orient+ask read mode default, project scope guarantee, the five-section handoff content contract, secret-detect abort, staleness-7d query, mid-task capture, precedent comparison against using-tasks / MEMORY.md / log.md / Karpathy diary, and the closure record of cluster 7/7 (install / hermes-mapping / bootstrap-template-extend / posttooluse-hook / existing-projects-upgrade / test-trigger / review). Source buffer: `~/projects/.workshop/.archive/2026-05-24-session-handoff-skill.md` Round 1 design + Round 2 Q1–Q10 resolution. Also serves as the live-hook e2e smoke artifact for the deferred follow-up from `[session-handoff-posttooluse-hook]` — substantive commit (feat: prefix, body well above 200 chars) should trigger the PostToolUse `commit-detector.ps1` hook just enabled in `~/.claude/settings.json` and emit `hookSpecificOutput.additionalContext` on the next agent turn. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
163
.wiki/concepts/session-handoff-skill-design.md
Normal file
163
.wiki/concepts/session-handoff-skill-design.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
---
|
||||||
|
title: session-handoff skill — design rationale
|
||||||
|
type: concept
|
||||||
|
updated: 2026-05-25
|
||||||
|
---
|
||||||
|
|
||||||
|
# session-handoff — design rationale
|
||||||
|
|
||||||
|
Why the skill exists in this shape, with the trade-offs that were considered and the decisions that closed them. Source buffer: `~/projects/.workshop/.archive/2026-05-24-session-handoff-skill.md` (Round 1 brainstorm + Round 2 Q1–Q10 resolution).
|
||||||
|
|
||||||
|
## The problem
|
||||||
|
|
||||||
|
Every fresh CC session in a project starts cold. The agent re-reads `STATUS.md`, greps recent buffers, looks at `MEMORY.md`, and asks the user "where were we?". That's a recurring fog — the user already told the previous session what to do next, and the previous session may have already formulated the plan, but the bridge between sessions doesn't exist.
|
||||||
|
|
||||||
|
The fix: the agent **writes a forward-looking handoff prompt** at session boundaries, into a canonical location the next session reads on cold start. Sliding overwrite: one file, one current state, history through `git log -p`.
|
||||||
|
|
||||||
|
## Why a new skill, not an extension
|
||||||
|
|
||||||
|
The shape was tempting to fold into `using-tasks` — it already touches `.tasks/`. But the lifecycles don't match:
|
||||||
|
|
||||||
|
- `using-tasks` is **per-task** (switch, start, pause, close).
|
||||||
|
- `session-handoff` is **per-session** (start-cold, end-warm).
|
||||||
|
|
||||||
|
Different triggers, different readers, different writers. Per-task state and per-session state happen to share a directory but they answer different questions.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**Location:** `.tasks/NEXT_SESSION.md`. Sits next to `STATUS.md` so the `using-tasks` reader already walks `.tasks/` on cold start and notices the handoff without an extra hook.
|
||||||
|
|
||||||
|
**Sliding overwrite:** every write fully replaces the file. No `.archive/handoff-<date>.md` fanout — `git log -p .tasks/NEXT_SESSION.md` is the history if anyone needs it. Rejected the append-with-archive variant because it produces N artefacts the user didn't ask for; the git-log path covers the same need on demand.
|
||||||
|
|
||||||
|
**Project scope:** no global state. Workshop and `.admin/` sessions don't see each other. "Wrap up session" in one tree does not touch the other.
|
||||||
|
|
||||||
|
**Modes:** read on session start (orient + ask, never auto-execute); write on session-end phrase or on substantive commit.
|
||||||
|
|
||||||
|
## Triggers — the resolved choices
|
||||||
|
|
||||||
|
### Read-mode (session start)
|
||||||
|
|
||||||
|
Activated by the `CLAUDE.md` trigger line `session handoff: read on start, write on end` (canonical, added to `project-bootstrap` v1.12.0 template). On cold start: if `.tasks/NEXT_SESSION.md` exists and is fresh, summarise + ask user before any action. If `_last_updated_` is older than 7 days, flag staleness explicitly: "handoff от <date> (N days ago) — overwrite or continue?".
|
||||||
|
|
||||||
|
Default is **orient + ask**, never auto-execute. The previous session might have been wrong; user agency survives.
|
||||||
|
|
||||||
|
### Write-mode — phrase whitelist
|
||||||
|
|
||||||
|
Strict whitelist (rejects close-but-different phrases):
|
||||||
|
|
||||||
|
- Russian: «завершаем сессию», «сворачиваемся», «закругляемся»
|
||||||
|
- English: «wrap up session», «end session», «we're done for now»
|
||||||
|
|
||||||
|
Explicit anti-patterns that **must not** trigger:
|
||||||
|
|
||||||
|
- «закрываем эту таску» — task close, lives in `using-tasks` zone
|
||||||
|
- «pause», «приостанови» — task-pause, not session-end
|
||||||
|
- «отбой», «разбегаемся» — too broad; may refer to a different context
|
||||||
|
- «сейчас завершу одну задачу и тогда поговорим» — partial completion
|
||||||
|
|
||||||
|
On ambiguity (e.g. «закругляемся» with a task-marker tail), the skill **asks** "session or task?" rather than guessing. Closing-bias is the failure mode to avoid.
|
||||||
|
|
||||||
|
### Write-mode — substantive-commit heuristic
|
||||||
|
|
||||||
|
```
|
||||||
|
prefix NOT IN (meta:|docs:|style:|chore:|fix typo)
|
||||||
|
AND (body_length > 200 chars OR files_changed > 3)
|
||||||
|
```
|
||||||
|
|
||||||
|
Plus an explicit "first non-trivial commit of the session always triggers" exception. The reasoning: the *start* of work is itself a context shift worth recording, even when the first commit is small (bootstrap, scaffolding).
|
||||||
|
|
||||||
|
The thresholds are tuned to skip the noise (`chore: bump dep`, `docs: typo`) while catching the actual session-shaping commits. They're not magic numbers — they're the floor below which a handoff regen would dominate signal with noise.
|
||||||
|
|
||||||
|
## Optional PostToolUse hook
|
||||||
|
|
||||||
|
A behavioral memory ("after `git commit`, check the substantive heuristic") is fragile — one missed check leaves the next session with a stale handoff. Solution: an opt-in PostToolUse hook (`skills/session-handoff/hooks/commit-detector.{ps1,sh}`) that emits a `hookSpecificOutput.additionalContext` system reminder after every substantive commit. Harness-side determinism replaces the agent-side memory.
|
||||||
|
|
||||||
|
**Why opt-in, not auto-installed:** `install.sh` deliberately does not mutate `~/.claude/settings.json`. Auto-rewriting the user's hook config on every skill install is the wrong shape — user expects `install.sh` to copy files, nothing more. Hook is shipped as scripts; user enables once per machine via the snippet in `hooks/README.md`.
|
||||||
|
|
||||||
|
**Known caveats:**
|
||||||
|
- Rebase / cherry-pick noise: every commit in a batch re-fires the hook. Deferred — opt-in bounds the cost.
|
||||||
|
- Hook can't see session boundaries, so it under-detects small first-commits-of-session that the agent-side heuristic does catch. Acceptable trade-off for harness-side determinism.
|
||||||
|
- Hook only **signals**; never auto-invokes write-mode. The agent still decides — preserves the user-agency invariant.
|
||||||
|
|
||||||
|
## Handoff content contract
|
||||||
|
|
||||||
|
Five required sections. Empty sections keep their heading + `(нет на этом раунде)` note so the next agent sees "nothing to do here", not "missing":
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
_last_updated_: <ISO date>
|
||||||
|
session_id: <hash or date>
|
||||||
|
---
|
||||||
|
|
||||||
|
# Next session handoff
|
||||||
|
|
||||||
|
## Recent commits
|
||||||
|
- <slug>: <subject> (3–5 most recent)
|
||||||
|
|
||||||
|
## Open треки
|
||||||
|
| Трек | Готовность | Entry-point |
|
||||||
|
|---|---|---|
|
||||||
|
|
||||||
|
## Спроси user'а
|
||||||
|
- <pending decision>
|
||||||
|
|
||||||
|
## Не делать (preemptive guards)
|
||||||
|
- <guard>
|
||||||
|
|
||||||
|
## Memory updates за сессию
|
||||||
|
- <what was saved / updated>
|
||||||
|
```
|
||||||
|
|
||||||
|
Handoff is **forward-looking** — a bridge of new things specific to the next turn, not an overview of the whole project. `STATUS.md`, `MEMORY.md`, and `.wiki/log.md` remain authoritative for their respective scopes. Don't duplicate them; reference them.
|
||||||
|
|
||||||
|
## Mid-task capture
|
||||||
|
|
||||||
|
If a 🔴 active task exists in `STATUS.md` at write time, the handoff captures `left mid-task: <slug> / where_stopped: <text>`. Rationale: friction of refusing the user ("can't wrap up, you have active work") is worse than the cost of capturing the mid-task state for the next session to resume. User agency owns the call, not the skill.
|
||||||
|
|
||||||
|
## Failure modes that exit early
|
||||||
|
|
||||||
|
- `CLAUDE.md` missing the trigger line → silent exit (opt-in per project).
|
||||||
|
- Not in a git work-tree → silent exit.
|
||||||
|
- `.tasks/NEXT_SESSION.md` absent in read-mode → silent exit (first session of project).
|
||||||
|
- Content matches secret patterns (`AKIA…`, `sk-…`, `ghp_…`, `BEGIN PRIVATE KEY`, `password=…`, etc.) → **abort write**, surface to user. File goes to git, no credentials.
|
||||||
|
- Stale handoff (>7 days) in read mode → **ask** rather than silent — overwrite-or-continue is a user call.
|
||||||
|
|
||||||
|
## What the skill explicitly doesn't do
|
||||||
|
|
||||||
|
- Auto-execute action items from a read handoff. Default is orient + ask.
|
||||||
|
- Append-with-archive. Sliding only.
|
||||||
|
- Trigger on `chore:` / `docs:` / `meta:` commits, on broad farewells, or on partial-completion phrases.
|
||||||
|
- Touch other projects. Per-project scope, full stop.
|
||||||
|
- Depend on a harness `SessionEnd` hook — Claude Code doesn't have one. The available hooks are `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `Stop`, `Notification`. The substantive-commit detection rides on `PostToolUse`.
|
||||||
|
|
||||||
|
## Precedent comparison
|
||||||
|
|
||||||
|
| Source | Lifecycle | Why it doesn't cover the handoff case |
|
||||||
|
|---|---|---|
|
||||||
|
| `using-tasks` STATUS.md `where_stopped` / `next_action` | per-task | misses per-session orientation; handoff needs to bridge tracks, not lock onto one task |
|
||||||
|
| `_queue.md` | parked topics | passive park, not active handoff |
|
||||||
|
| `MEMORY.md` | long-term facts | not anchored to a session boundary |
|
||||||
|
| `.wiki/log.md` | append-only chronology | timeline, not active orientation |
|
||||||
|
| Karpathy daily logbook | personal diary | points to the past (what happened); handoff points to the future (what to do next) |
|
||||||
|
|
||||||
|
Handoff is forward-looking; everything else is backward-looking or timeline-agnostic. That's the slot the skill fills.
|
||||||
|
|
||||||
|
## Acceptance — how the cluster closed
|
||||||
|
|
||||||
|
The skill shipped 2026-05-24 at v0.1.0, with PowerShell hook bug-fix at v0.3.1. Closure cluster — 7/7 tasks:
|
||||||
|
|
||||||
|
1. `[session-handoff-install]` — install.sh + reload + smoke.
|
||||||
|
2. `[session-handoff-hermes-mapping]` — `pending` mode in `hermes/mapping.yaml`.
|
||||||
|
3. `[session-handoff-bootstrap-template-extend]` — `project-bootstrap` v1.12.0 template gets the trigger line out of the box.
|
||||||
|
4. `[session-handoff-posttooluse-hook]` — `hooks/` shipped, opt-in snippet documented, stdin smoke verified.
|
||||||
|
5. `[session-handoff-existing-projects-upgrade]` — manual edit-pass on this machine; 4 repos deferred per-machine.
|
||||||
|
6. `[session-handoff-test-trigger]` — 15/15 behavioral outcomes match (6 whitelist + 4 antipatterns + ambiguity ASK + read-mode R1/R2 + hook H1/H2/H3).
|
||||||
|
7. `[session-handoff-review]` — 6/6 review dimensions ✓ via test-trigger smoke, 0 findings filed, skill v0.3.1 ships unchanged.
|
||||||
|
|
||||||
|
The smoke validated the load-bearing design decisions: ambiguity resolution by asking, the always-first-commit exception, default orient + ask in read-mode. None of them surfaced as gaps — every test came back as a confirmation of the resolved design.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- `pulling-before-work` — the precedent for a `CLAUDE.md`-triggered skill that runs once per session at a defined boundary.
|
||||||
|
- `project-bootstrap` v1.12.0+ — adds the canonical trigger line to new projects.
|
||||||
|
- `using-tasks` — owns `STATUS.md` and `<slug>.md`; the handoff explicitly does not replicate them.
|
||||||
@@ -39,6 +39,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever
|
|||||||
- [tdd-criteria-design](concepts/tdd-criteria-design.md) — tdd-criteria-design
|
- [tdd-criteria-design](concepts/tdd-criteria-design.md) — tdd-criteria-design
|
||||||
- [project-bootstrap-meta-isolation.md](concepts/project-bootstrap-meta-isolation.md) — project-bootstrap@1.11.0 — Step 1 ships meta-isolation block in `.gitignore` (`!.claude/`, `!.tasks/`, `!.wiki/`, ...) so own greenfield/upgrade projects re-enable agent meta-paths against global `core.excludesFile` cutter. Marker-based append-only on existing files; smoke-tested with negative control
|
- [project-bootstrap-meta-isolation.md](concepts/project-bootstrap-meta-isolation.md) — project-bootstrap@1.11.0 — Step 1 ships meta-isolation block in `.gitignore` (`!.claude/`, `!.tasks/`, `!.wiki/`, ...) so own greenfield/upgrade projects re-enable agent meta-paths against global `core.excludesFile` cutter. Marker-based append-only on existing files; smoke-tested with negative control
|
||||||
- [interns-grep-audit-design](concepts/interns-grep-audit-design.md) — interns-grep-audit-design
|
- [interns-grep-audit-design](concepts/interns-grep-audit-design.md) — interns-grep-audit-design
|
||||||
|
- [session-handoff-skill-design.md](concepts/session-handoff-skill-design.md) — design rationale for the `session-handoff` skill (sliding overwrite into `.tasks/NEXT_SESSION.md`, phrase whitelist + substantive-commit heuristic, optional PostToolUse hook for harness-side determinism, orient+ask default, project scope, cluster 7/7 closure)
|
||||||
|
|
||||||
## Packages
|
## Packages
|
||||||
|
|
||||||
|
|||||||
@@ -59,3 +59,5 @@ Parseable: `grep "^## \[" .wiki/log.md | tail -20`.
|
|||||||
## [2026-05-10] decision | project-bootstrap-meta-isolation — v1.11.0 ships meta-isolation block in `.gitignore` template + Step 1 upgrade-case append; restores agent meta-paths visibility against global `core.excludesFile`; smoke-tested greenfield + negative control + idempotency
|
## [2026-05-10] decision | project-bootstrap-meta-isolation — v1.11.0 ships meta-isolation block in `.gitignore` template + Step 1 upgrade-case append; restores agent meta-paths visibility against global `core.excludesFile`; smoke-tested greenfield + negative control + idempotency
|
||||||
|
|
||||||
## [2026-05-22] ingest | concepts/interns-grep-audit-design
|
## [2026-05-22] ingest | concepts/interns-grep-audit-design
|
||||||
|
|
||||||
|
## [2026-05-25] decision | session-handoff-skill-design — design rationale for the `session-handoff` skill captured in wiki after cluster 7/7 closure; sliding overwrite of `.tasks/NEXT_SESSION.md`, phrase whitelist + substantive-commit heuristic, opt-in PostToolUse hook, orient+ask default, source: `~/projects/.workshop/.archive/2026-05-24-session-handoff-skill.md` Round 1 + Round 2
|
||||||
|
|||||||
Reference in New Issue
Block a user