How to use
- Copy the installation command and review the source package before installing it in your own agent environment.
- Check the source document for required tools, credentials and supported hosts.
- Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/dynamic/throughline --skill handoffBefore you start
- Installation does not include a model subscription, API allowance or permission to access your files.
- Repository stars and installation counts describe activity, not an independent quality or security test.
Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
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
- 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.
- Read the current
DATA/HANDOFF.md(create from the Phase 5 template if absent). - 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).
- **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):
# 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
- 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.
- Add a link to the new session log under "Recent Session Logs" — keep only the
last 5.
- 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.
- 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:
---
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>
---- Consume the buffers: move distilled
DATA/buffer/session-*.mdinto
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.
- Clear resolved breadcrumbs: if
DATA/.capture-errorsexists 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.
- 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>- 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
DATAlives 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,
DATAlives 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.mdand 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:
# <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-DDDo 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.



