dynamic/throughline

handoff

Distill the current session into the durable HANDOFF.md plus a timestamped session log, using the throughline capture buffer as source.

Voir la source

Mode d’emploi

  1. Copiez la commande et examinez le paquet avant de l’installer dans votre environnement agent.
  2. Consultez le document original pour les outils, identifiants et environnements requis.
  3. Décrivez les entrées et le livrable souhaité ; comparez la sortie aux sources.
npx skills add https://github.com/dynamic/throughline --skill handoff

Avant de commencer

  • L’installation n’inclut ni abonnement au modèle, ni quota API, ni accès aux fichiers.
  • Étoiles et installations reflètent l’activité, pas un test indépendant de qualité ou de sécurité.
Document Skill original

Rendu depuis le dépôt source en conservant titres, exemples, code, tableaux, liens et images.

throughline — Handoff (distillation)

Goal: Turn a session's raw, continuously-captured actions into curated, durable project memory. Capture is automatic (hooks); distillation is this skill — the judgment layer that decides signal vs. noise.

Data directory (resolve once): $THROUGHLINE_DATA_DIR if set, else <project-root>/.claude/throughline/. Below, DATA refers to that path. In a linked git worktree, <project-root> here is the main working tree, not the worktree itself (see README "Git worktrees") - so DATA is shared across every worktree of the repo.

  • Durable handoff: DATA/HANDOFF.md
  • Session logs: DATA/logs/handoff-YYYY-MM-DD-HHMM.md
  • Raw capture buffers: DATA/buffer/session-<id>.md

When to run (including proactively)

Run when the user explicitly asks, or when you detect wrap-up: the planned work is complete, the user says "thanks / done / that's all / ship it", or a natural stopping point is reached. At wrap-up, just run it and report the diff — don't ask permission first. HANDOFF.md updates are idempotent, so an early run is simply refined by the next. Never run it more than once per unchanged state.


Phase 1: Gather source

  1. Read all DATA/buffer/session-*.md — these are the continuously-captured

actions (commands, file edits) for this and any unconsumed prior sessions. They survive context compaction, so trust them over fuzzy recollection. One session is one file (keyed by a session id stable across compaction).

  • Lines marked ` [interrupted] (or [failed] ` where a tool exposes

an error flag) are actions that did not complete cleanly; do not record them as done. The Claude Code Bash result carries no exit code, so a plain failed command is unmarked: cross-reference the conversation to judge its success.

  • A <!-- compaction-boundary ... --> line marks where a compaction happened.

For actions above the most recent boundary, distill from the buffer text itself: the conversation's account of why they were done has been summarized away and your recall of it is no longer trustworthy.

  1. Read the current DATA/HANDOFF.md (create from the Phase 5 template if absent).
  2. Cross-reference the buffer against the live conversation: the buffer says *what

happened; the conversation says why*. Distillation needs both (subject to the boundary caveat above).

  1. **Check for legacy durable-fact sections, unconditionally — not just when the

file is over budget.** If DATA/HANDOFF.md still has an "Architecture & Services", "Environment & Infrastructure", "Tools & Integrations", "Authentication & Secrets", or "Key Files & Resources" section (the pre-this-PR template), migrate it this run per Phase 5's migration note, regardless of the file's current size. A file can carry all five sections and still read as "under budget" today; leaving them until the size gate trips just delays the fix and lets the content erode the new budget as the file is edited meanwhile.


Phase 2: Synthesize (signal vs. noise, then durable vs. in-flight)

First cut: decide what is a permanent, reusable change vs. session-specific noise. Discard the noise.

Second cut, and the one that keeps HANDOFF.md from bloating: of what's left, split by durability, using the test "would this still be true in three months?"

  • Yes → native memory, not `HANDOFF.md`. Architecture/topology, environment/infra

facts, tool/integration inventory, config & secret names (never values), key files & resources, and anything else that reads as a fact about the project rather than a status. Write it as a memory topic file per Phase 4 step 4 below — that store is durably-recalled and does not cost resident context every turn the way HANDOFF.md does.

  • No → `HANDOFF.md`. Only genuinely volatile, in-flight state belongs here:
  • Resolved issues — recently closed, not yet promoted to memory (see Phase 4 cap)
  • Pending items — open problems, next steps surfaced this session
  • Current state — what's mid-flight: uncommitted work, open PRs/branches, blockers
  • Consolidation passes — not durable-vs-in-flight at all, it's the consolidate

skill's own scope-tracking record. Never touch it here; leave it exactly as found.

If you're unsure which bucket something falls in, prefer memory — HANDOFF.md is read in full every session (see Phase 4's size discipline); memory topic files are read on demand.


Phase 3: Write the session log

Create DATA/logs/handoff-YYYY-MM-DD-HHMM.md (local time; create logs/ if needed):

markdown
# Handoff: <Brief Title>
**Date:** <YYYY-MM-DD HH:MM TZ>   **Session:** <session-id if known>
**Follows:** [<predecessor title>](handoff-YYYY-MM-DD-HHMM.md) <!-- same work stream only; omit if none -->

## Objective
The big-picture goal of the session.

## What happened
Distilled from the capture buffer + conversation: commands run, files changed,
decisions made — with the *why*.

## What we tried (including what failed)
Approaches attempted and abandoned, with the REAL evidence: the actual command,
error text, or number that killed the approach, not a summary of it. A failed
approach is the most expensive thing for a future session to rediscover from
scratch; a summary like "tried caching, didn't work" gives a future session
nothing to avoid re-trying. Omit this section only if nothing was actually tried
and abandoned this session.

## Progress
### Completed   ### In progress   ### Not started

## Key learnings & gotchas
Root causes, non-obvious config, workarounds, things that look like they should
work but don't.

## Current state
Repo (branch, uncommitted, sync), deploy status, open PRs/issues.

## Next steps
Ordered, specific — exact commands, paths, expected outcomes.

## Files & resources
Key paths, URLs, credential locations (names only).

Chain link: if this session continues work a prior log already started (same work stream, not just the same project), name that predecessor in the Follows line above so a reader can walk the decision history log-to-log without HANDOFF.md having to carry it. Omit the line entirely for a session that starts something new.


Phase 4: Update durable HANDOFF.md + memory binding

  1. Apply the Phase 2 updates to DATA/HANDOFF.md: durable facts go to memory (step 4

below), only in-flight state stays here. Keep each entry to 1–2 lines — it's a reference doc, not a journal. Update its Last Updated date. Keep the "Consolidation passes" section untouched if present (owned by consolidate, not a candidate for this trim).

  • Match length to substance. Cover the actual in-flight state; do not pad with

filler sections, redundant summaries, or boilerplate. An empty section (nothing durable was moved out, nothing in-flight exists) should not appear at all.

  • Cap "Resolved Issues" to the most recent ~5 rows. Past the cap, promote

the row to a memory topic file (step 4 below) if the fact is worth keeping, then drop it here — never leave the only copy of a fact in an already-over-budget file.

  1. Add a link to the new session log under "Recent Session Logs" — keep only the

last 5.

  1. Now that the file is final, measure it — a stop condition, not guidance. This

is the part that previously failed once already (see throughline#26: closed, then regrown past budget in production). HANDOFF.md is read in full every session — it is resident context, paid for on every turn, not a one-time write. Count it: wc -l DATA/HANDOFF.md and wc -c DATA/HANDOFF.md. Target 150 lines / ~8KB (~2,000 tokens). The byte count is the binding number — this file is table-heavy, so line count alone can look fine while running well over budget in tokens. If either exceeds the hard stop (200 lines or ~11KB), you are not done — go back to step 1 and cut before finishing this phase. (Native memory's own MEMORY.md index enforces a comparable limit at load time — writes past 200 lines/25KB get truncated silently on every load — which is the failure mode this measured check exists to avoid here, by catching it at write time instead.)

  • First, re-check every section against the Phase 2 durability test. Anything

that would still be true in three months and isn't already in memory is the likely offender — move it now, don't defer it to a later consolidate pass.

  • If nothing left to cut is actually droppable (every line is genuinely

in-flight state), the count is real signal that there's too much in flight at once — say so in your report rather than forcing an artificial trim.

  1. Memory binding (native system) — the default destination for anything durable.

Every durable item identified in Phase 2 gets written here, not just rare standout insights — but still curated, not auto-dumped: one entry per genuine fact, and check MEMORY.md's existing index first so you update or merge into an existing topic file instead of writing a near-duplicate one. Types: a confirmed preference (feedback), a fact about the user/context (user), a project constraint/decision/architecture fact (project), a resource pointer (reference). Native memory is two layers: MEMORY.md is an always-loaded index (truncated silently past 200 lines/25KB on every load — a write past that limit isn't rejected, but everything past the cutoff becomes invisible to future sessions, so every entry there must stay a single short line), and each entry's full content lives in its own topic file under ~/.claude/projects/<slug>/memory/, read on demand. Topic files have no enforced limit, so they need the same discipline applied by hand: keep each to ~400 words (match the length to the fact — a one-line preference doesn't need 400 words either; this is a ceiling, not a target) and split a topic that grows past it into two files rather than letting one file become a second journal. Write the topic file with frontmatter shaped like this, then add its one-line pointer to MEMORY.md:

markdown
   ---
   name: short-kebab-case-slug
   description: one-line summary used to judge relevance in a future session
   metadata:
     node_type: memory
     type: feedback   # or: user, project, reference
     originSessionId: <current session id>
   ---
  1. Consume the buffers: move distilled DATA/buffer/session-*.md into

DATA/buffer/archive/ so they aren't re-processed next session. Archive only, never delete: an archived buffer is the recovery path if a distillation later turns out to have missed something. See "Housekeeping" in the README for when an archived buffer is old enough to actually delete.

  1. Clear resolved breadcrumbs: if DATA/.capture-errors exists and its contents

were surfaced above (as a Phase 2 "Resolved Issues" entry or in the session log), clear the file now that it has been distilled - it exists to make a swallowed capture failure visible exactly once, not to keep nagging on every future onboard after it's already been read and acted on.

  1. Emit a next-session briefing. Render a second, compact view of the session

log's ## Next steps (plus ## Objective, what landed, and the sharpest ## Key learnings & gotchas item) as a copy-pasteable fenced block in your report - this is not new synthesis, just a different rendering of what the log already holds, aimed at priming a brand-new agent/session rather than documenting for a human reader. Do not write it to a file; emit-only.

   ## Next session briefing - <project>
   Continuing: <one-line objective>
   Last done: <what just landed - PRs merged, deploys done>
   Next: <ordered, specific next steps - exact commands/paths where known>
   Watch out for: <sharpest gotcha from this session, if any>
  1. Offer to commit + push the handoff artifacts (offer only, never auto-run -

the write already happened; this is the review-gated action). throughline's data is local-only by default (see README "Local by default") - this offer only ever produces a commit when the project has deliberately opted in by un-ignoring HANDOFF.md/logs/, so on an unmodified default layout it will correctly find nothing to stage and skip silently. Do not treat "nothing offered" as a bug; it's the expected outcome unless the project opted in.

  • If DATA lives outside the current project's git tree (an absolute

THROUGHLINE_DATA_DIR pointed at a shared, cross-harness location - a documented, supported config), skip the offer entirely: those files aren't part of this repo's commit at all, and git check-ignore on a path outside the repo fails with a fatal error rather than a clean answer (same reasoning session-onboard.sh's gitignore nudge already applies - see its comment there).

  • **In a linked git worktree with worktree-sharing active, DATA lives under

the MAIN working tree, not the worktree the session is actually running in** (see README "Git worktrees"). Run git check-ignore with -C pointed at (or cd into) the directory that actually contains DATA before checking it, not the session's own cwd - git check-ignore on a path outside the repo it's invoked from fails with the same kind of fatal error as the outside-the-git-tree case above (exit 128, "outside repository"), which is NOT distinguishable from "not ignored" by exit code alone. Treat that fatal error the same as the skip-the-offer case above, not as "not ignored."

  • Otherwise, check DATA/HANDOFF.md and the new session log independently

with git check-ignore. Offer to stage whichever of the two is NOT ignored; if both are ignored, skip the offer silently - nothing committable exists (the default, local-only layout - including this repo's own dogfooded .claude/throughline/ - is itself gitignored, so this is the common case, and announcing a skipped optional offer on every handoff is filler). When only ONE of the two is ignored, name the file being left out in a clause: a partial staged set is the one case here that surprises the reader.

  • Stage exactly the not-ignored file(s) by their literal paths - DATA/HANDOFF.md

and/or the specific DATA/logs/handoff-YYYY-MM-DD-HHMM.md just written this session. Never a glob like logs/handoff-*.md (it would sweep in older, previously-declined session logs too), never git add -A, never buffer/ or .capture-errors (see README "Local by default" - those two paths stay gitignored even when a project opts in to tracking HANDOFF.md/logs/).

  • Propose a conventional message, e.g. docs: session handoff - <brief summary>,

matching whatever commit-message convention the repo already follows.

  • Treat push as a separate, remote-affecting confirmation after the commit -

defer to the calling agent's existing git safety norms (branch-first on a default branch, respect any repo-configured push gate, don't bypass one without explicit authorization each time).


Phase 5: HANDOFF.md template (if absent)

In-flight state only — no Architecture/Environment/Tools/Auth/Key-Files sections. Those are durable facts; they go to native memory (Phase 4 step 4), not here:

markdown
# <Project> — Handoff
**Last Updated:** <YYYY-MM-DD>

## Resolved Issues
| Issue | Resolution | Date |
<!-- most recent ~5 rows only; promote older ones to memory, then drop -->
## Pending Items
| Item | Priority | Tracking |
## Current State
<!-- what's mid-flight: uncommitted work, open PRs/branches, blockers -->
## Recent Session Logs
1. [Title](logs/handoff-YYYY-MM-DD-HHMM.md) — YYYY-MM-DD

Do not pre-seed a "Consolidation passes" section (note the exact casing — matches Phase 2 and consolidate's own lookup, which is case-sensitive). Leave it absent until consolidate's own Phase 4 creates it on its first pass; adding an empty one here just gives consolidate a stray, differently-worded header to reconcile against.

A project migrating from the old template: at the next handoff, sort its existing Architecture/Environment/Tools/Auth/Key-Files content into memory topic files (Phase 4 step 4), then delete those sections. Don't do this migration silently — report the diff same as any other handoff. This check is unconditional, not just for an over-budget file — run it whenever any of those five sections still exist, even if the file is currently under the 150/200-line target (see Phase 1 step 4 below).


Reminders

  • Distillation needs judgment — that's why this is a skill, not a hook. Be terse,

preserve structure, don't duplicate existing entries.

  • Re-scan for secrets before writing. Capture masks obvious credential shapes,

but it is best-effort pattern matching, not a guarantee — it has no entropy analysis, so a bare opaque token with no recognizable keyword or prefix passes through unmasked. Before writing anything into HANDOFF.md or logs/ - and especially before any opt-in commit of them - scan your draft for token-shaped strings (case-insensitive token|key|secret|password|credential|auth, Bearer, ghp_/github_pat_/ gh[oprsu]_/sk-/AKIA, URL userinfo) and for any other long, opaque, random-looking string regardless of keyword — reduce all of them to key names only. Two shapes capture's own redaction structurally cannot catch, so they need a human/model eye specifically: (1) a credential attached to a bare CLI flag with no keyword (mysql -p<password>, curl -u user:pass) — flags like -u/-p are too overloaded across tools (e.g. docker run -u uid:gid) to redact mechanically without false positives; (2) a value that's only partially masked — seeing a *** in a captured line doesn't mean the whole secret was caught, so check what's still readable around it, not just whether a mask is present. This is defense in depth, not the sole barrier.

  • Report, then let the user review. After writing, show the HANDOFF.md diff +

session-log path, then the copy-pasteable next-session briefing block, then - only if something is committable (step 8) - the commit/push offer; with both artifacts gitignored there is no offer to make and nothing to say about it. The review gate is post-write, not pre-write - commit/push is the only action in this sequence that waits on an explicit go.

  • Buffers are the source of truth for what happened*** — they don't lie about

which commands ran or files changed, even after a long, compacted session.

Tâches connexes

du même dépôt

Autres Skills

Tous les Skills