throughline
v0.5.0
Published
Claude Code hooks plugin for structured context compression (/clear-safe persistent memory)
Downloads
751
Maintainers
Readme
Throughline
English · 日本語
Cut ~90% of Claude Code's context usage while keeping nearly all the memory. Throughline separates conversation by type, not time — humans-readable text stays, machine-generated tool output retires to SQLite. Same decisions, same context, 90% lighter.
In 30 seconds
npm install -g throughline
throughline install # registers hooks/skills and provisions the VS Code monitor taskThat's it. Open any Claude Code session and your turns flow into
~/.throughline/throughline.db automatically. After 50 turns of work, just
type /clear — the new session resumes mid-thought instead of starting from
zero. (For non-/clear boundaries such as a brand-new chat or a VSCode
restart, type /tl first to mark the predecessor.)
Global install also registers Codex UserPromptSubmit, PostToolUse, and
Stop hooks in ~/.codex/hooks.json and enables both
[features].codex_hooks = true and [features].hooks = true in
~/.codex/config.toml. The Codex hooks invoke the installed
bin/throughline.mjs through an absolute Node path, so Codex App Server PATH
differences do not hide the command. They are registered synchronously
(async: false), matching the Codex hook behavior verified in Caveat. Existing
non-Throughline Codex hooks are preserved. The prompt and tool-loop hooks capture
rollout memory and write monitor state, but they do not inject $throughline at
usage thresholds; token-monitor is display-only and is never an auto-refresh
trigger. It also installs a global $throughline Codex skill. Bare
$throughline starts a new Codex thread through app-server, injects Throughline
DB handoff memory as a developer item, and opens that thread in the selected
host; ask explicitly for current-thread rollback diagnostics when you want the
guarded trim --execute --host codex surface.
How it compares
| | Throughline | MemGPT / SummaryBufferMemory | Plain Claude Code |
|---|---|---|---|
| Compression axis | content type (text vs tool I/O) | recency (old → summarized) | none |
| Coding-assistant fit | high — tool I/O is the heavy 80% | medium — also compresses what you want to keep | — |
| /clear survival | ✅ via SQLite + typed /clear / /tl baton | depends on host | ❌ |
| Auto-inheritance risk | low (typed /clear or /tl names the predecessor) | high | — |
| Runtime deps | zero (Node 22.5+ built-in node:sqlite) | many | — |
| Multi-session token monitor | ✅ Claude real message.usage; Codex rollout token_count when available | — | — |
In a typical Claude Code session, 80% of the context window is tool I/O — file reads, Bash output, grep results. This data is consumed the moment Claude acts on it, but it stays in the context forever, pushing you toward the window limit.
Throughline fixes this by separating conversation content by type, not time:
Without Throughline (50 turns, no /clear):
Context = user text + assistant text + tool I/O + system messages
≈ 125,000 tokens (80% is tool I/O you'll never re-read)
With Throughline (50 turns → /clear → resume):
Context = recent 20 turns of conversation text (L2)
+ older 30 turns as one-line summaries (L1)
+ zero tool I/O (L3 — retired to SQLite, on-demand)
≈ 13,000 tokens — same decisions, same context, 90% lighterUnlike MemGPT or LangChain's SummaryBufferMemory which compress by recency (old = summarized), Throughline separates by content type: human-readable conversation stays, machine-generated tool output retires. This is purpose-built for coding assistants where tool I/O is heavy but transient.
The retired L3 data isn't lost — Claude can pull it back on demand via
throughline detail <time> when a past turn's tool output becomes relevant
again.
Throughline also ships a multi-session token monitor that reads real
Anthropic API usage from the transcript JSONL (no length / 4 heuristics).
Three-layer memory model (schema v7)
flowchart LR
T["Live turn<br/>user · assistant · tools · thinking"]
T --> H["Stop hook"]
H --> L2[("L2 · bodies<br/>verbatim text")]
H --> L3[("L3 · details<br/>tool I/O · thinking")]
H -. "async<br/>Haiku" .-> L1[("L1 · skeletons<br/>one-liners")]
L2 -- "recent 20 turns" --> S["Next SessionStart<br/>injection"]
L1 -- "older turns" --> S
L3 -. "on demand · throughline detail" .-> S
classDef l1 fill:#3aa0ff,stroke:#1a1f2e,color:#fff
classDef l2 fill:#7c5cff,stroke:#1a1f2e,color:#fff
classDef l3 fill:#4a5568,stroke:#1a1f2e,color:#fff
class L1 l1
class L2 l2
class L3 l3| Layer | Name | Where it lives | Content | Cost per turn | | ----- | ---------- | --------------------- | --------------------------------------------------------------------- | ------------- | | L1 | Skeleton | injected when old | one-line summary of the turn (current Claude-primary path: Claude Haiku, optional Codex sidecar) | ~10 tok | | L2 | Body | injected when recent | user text + assistant reply, verbatim | full natural | | L3 | Detail | SQLite only | tool I/O, system messages, images, extended thinking (on-demand) | heavy, retired |
The layers are complementary and disjoint — nothing is duplicated across
them. Extended thinking blocks are stored at L3 (kind='thinking') so the
next session can see what the previous Claude was thinking at the moment it
was interrupted, not just what it said aloud. On SessionStart the thinking
of the final turn is injected inline above the L2 history; older thinking
remains retrievable via throughline detail <time>.
On SessionStart, Throughline rebuilds the context from SQLite and
injects it as plain text:
- The most recent 20 turns are injected as full L2 (
bodies) text - Older turns are injected as L1 (
skeletons) one-liners - L3 stays in SQLite and is retrieved on demand via
/sc-detail <time>
L1 summaries are generated lazily: for sessions that stay under 20 turns, no
external summarizer is invoked. In the current Claude-primary path, Throughline uses Claude Haiku 4.5
via a subprocess (claude -p --model claude-haiku-4-5-*), reusing your Claude
Max login — no API key required. When codex-sidecar is explicitly configured
for the summarize-l1 preset, Throughline can use that instead.
For Codex-primary capture, the L1 backend is the Codex CLI; failures are explicit
and do not fall back to Claude Haiku or raw L2.
All three layers (L1/L2/L3) have working write paths as of schema v5.
/sc-detail HH:MM:SS returns user/assistant text (L2) plus a kind-grouped view
of tool inputs, tool outputs, and hook output captured at L3 for that turn.
Inheritance: typed /clear and /tl write a baton, source-clear is the fallback
Throughline 0.4.1+ supports two inheritance paths. The baton path is the
primary route; the source-clear auto path is the fallback for cases where
the user's /clear does not reach the UserPromptSubmit hook (for example
the VSCode extension's menu-driven /clear).
flowchart LR
U["User types<br/>/clear or /tl"] -->|UserPromptSubmit| W["writeBaton<br/>(session_id + TTL 1h)"]
W --> B[("handoff_batons<br/>SQLite")]
M["VSCode menu<br/>clear"] -->|no UserPromptSubmit| X["no baton"]
NS["Next SessionStart"] --> C{"baton<br/>present?"}
B -.-> C
X -.-> C
C -->|yes| P1["baton path<br/>(primary)<br/>merge that exact predecessor"]
C -->|no, source='clear'| P2["auto path<br/>(fallback)<br/>findLatestClaudePredecessor"]
C -->|no, source!='clear'| P3["fresh session<br/>no merge"]
P1 --> INJ["inject L1 + L2 + L3 refs"]
P2 --> INJ
classDef primary fill:#7c5cff,stroke:#1a1f2e,color:#fff
classDef fallback fill:#3aa0ff,stroke:#1a1f2e,color:#fff
classDef neutral fill:#4a5568,stroke:#1a1f2e,color:#fff
class P1 primary
class P2 fallback
class P3,INJ neutralbaton path (primary): typed /clear or /tl → deterministic inheritance
When the user types /clear or /tl in the prompt, the UserPromptSubmit
hook writes a handoff baton with that session's session_id into the
handoff_batons table. The next SessionStart (within the 1-hour TTL)
consumes the baton and merges that exact predecessor's memory into the new
session, regardless of the source value.
This path is deterministic: it names the predecessor by id rather than
guessing, so multi-window scenarios where "most recently updated session"
does not equal "the session you just /clear-ed" still inherit correctly.
typed /clear: Session A → /clear → Session B (consumes A's baton, merges A)
typed /tl: Session A → /tl → (new chat / restart) → Session B (consumes baton, merges A)auto path (fallback): source='clear' → heuristic inheritance
Since Claude Code 2.1.128, the SessionStart hook receives source='clear'
reliably after /clear. When no baton is present (for example because the
/clear was triggered by the VSCode extension's menu and never reached
UserPromptSubmit), Throughline falls back to findLatestClaudePredecessor
to pick the most recent unmerged session for the same project and merges it.
Set THROUGHLINE_DISABLE_AUTO_HANDOFF=1 in your environment to opt out of
this fallback. The env var only affects the fallback; typed /clear and
/tl still write a baton and inherit because the user explicitly signalled
"continue this work".
What gets injected
Both paths inject the same curated memory:
- A "現在地 (latest exchange)" anchor (added in v0.4.12) re-surfaces the most recent user directive and the most recent assistant turn directly under the header, each truncated to 600 characters
- L1 summaries (older turns, one-line)
- L2 verbatim (most recent 20 turns, full text)
- L3 references (
throughline detail <time>retrieval commands, attached inline to each L1/L2 row; bodies stay in SQLite)
The injection is reframed as "resuming an interrupted task" rather than "reading past logs". The L2 verbatim already contains the last assistant turn — what Claude was about to do next — so no separate memo or extended thinking section is injected. The current-state anchor exists because, on long L2 windows, attention can fixate on the first L2 entry (the oldest turn in the window) and misread an old plan discussion as the current task; pinning the latest exchange at the top of the injection prevents that drift.
Each merged row keeps its origin_session_id, so repeated handoffs
accumulate memory through chains:
S1 (4 turns) --/clear--> S2 (auto-merges S1, adds 3 turns) --/clear--> S3 (auto-merges S2, adds 5 turns)
origin=S1×4 origin=S1×4, S2×3, S3×5Codex sidecar and Codex trim
Throughline is still Claude Code first. Codex support is an adapter layer:
it can project the same HandoffRecord into a throughline_handoff JSON block,
and it can optionally use codex-sidecar for read-only review / risk-check
work when that tool is installed and configured. It does not replace Claude
hooks, slash commands, transcript parsing, or /tl baton handoff behavior.
Useful inspection commands:
throughline handoff-preview --session <id>
throughline codex-summarize --session codex:<thread-id> --json
throughline codex-resume --session codex:<thread-id>
throughline codex-resume --session codex:<thread-id> --format handoff
throughline codex-handoff-start --session codex:<thread-id>
throughline codex-handoff-start --session codex:<thread-id> --execute --open-host vscode
throughline codex-handoff-smoke --session codex:<thread-id>
throughline codex-handoff-model-smoke --session codex:<thread-id> --dry-run --json
THROUGHLINE_EXPERIMENTAL_CODEX_HANDOFF_MODEL_SMOKE=1 \
throughline codex-handoff-model-smoke --session codex:<thread-id> --json
throughline codex-resume --session codex:<thread-id> --format item-json
printf '**Next move**: continue the Codex implementation\n' \
| throughline codex-resume --session codex:<thread-id> --memo-stdin
printf '**Next move**: continue the Codex implementation\n' \
| THROUGHLINE_EXPERIMENTAL_CODEX_MODEL_VISIBLE_SMOKE=1 \
throughline codex-visibility-smoke --session codex:<thread-id> --memo-stdin \
--request-timeout-ms 150000 --json
THROUGHLINE_EXPERIMENTAL_CODEX_MODEL_VISIBLE_SMOKE=1 \
throughline codex-visibility-smoke --session codex:<thread-id> \
--resume-after-inject --request-timeout-ms 180000 --json
throughline codex-threads --limit 5
throughline codex-sidecar-diagnostics --project . --preset review
throughline codex-sidecar-dry-run --project . --preset risk-check \
--context-file docs/throughline-handoff-context.example.jsonThe only existing external model call in core Throughline is L2→L1
summarization. When codex-sidecar is configured for summarize-l1,
Throughline can use it for that step; otherwise it keeps the existing Claude
Haiku path. This is an explicit compatibility mode, not silent auto-detection.
Codex current-thread rollback / inject is explicit-only. The 2026-05-06
incident initially looked like a rolled-back user prompt could reappear after
VS Code restart / reconnect, and later live experiments showed token usage can
drop briefly and then return in the same thread. For that reason, Codex hooks do
not perform automatic current-thread refresh. throughline trim --execute --host
codex remains available as an explicit diagnostic current-thread path when
injectable DB memory is available. Bare $throughline instead starts a new
thread with the handoff prompt, matching the safer Claude-style handoff model.
throughline codex-host-primitive-audit can inspect the installed Codex
app-server schema read-only. On the current tested Codex CLI, it finds
thread/rollback, thread/inject_items, and new-thread primitives, but no
current-thread primitive that either clears/rewrites retained rollback sources
or isolates/projects them away from model-visible input. This is now diagnostic
evidence only, not an execute blocker. The audit also emits a host-agnostic same-thread repair contract:
a passing design needs a current-thread non-resurrection primitive, rollback
non-resurrection guarantee, memory reinjection, post-repair read verification,
and a restart / reconnect non-resurrection smoke. VS Code evidence can inform
that contract, but it is not itself the repair primitive.
Dry-run and preflight also inspect planned rollback risk. If the user text that
would be removed is already present in Codex compacted.replacement_history,
Throughline reports Planned rollback restore safety: risk as diagnostic
context, but it no longer refuses preflight or execute on that basis.
The intended memory contract remains:
older turns come back as L1 summaries, the latest 20 turns come back as full L2
conversation bodies, and L3 remains detail references instead of inline tool
payloads. Throughline never guesses the active Codex thread: use
throughline codex-threads to inspect read-only rollout candidates, then pass
the chosen id explicitly. If a wrapper or host exports
THROUGHLINE_CODEX_THREAD_ID or CODEX_THREAD_ID, throughline trim --host
codex treats that as a current-thread identity signal; the CLI flag still wins
when both are present. Throughline does not fall back to "latest rollout"
guessing. When the chosen Codex thread has a current-project rollout,
throughline trim can use the rollout as the trim source even if the
Throughline DB has no captured Codex turn bodies; rollback events in the rollout
are applied before the rollback plan is built. When DB memory exists, the
injected memory is built from the Throughline DB rather than from the rollout
preview; guarded execute refuses instead of injecting a rollout preview when
Throughline DB memory is absent. During Codex
preflight, Throughline also compares the rollout active-turn count with
app-server thread/read / thread/resume counts. The separate read-only
codex-restore-smoke also checks paginated thread/turns/list counts across
fresh app-server processes. These are live app-server guards only; they are not
by themselves a durable restart-safe proof. The guarded
execute path is enabled by explicit --execute; it may still report
execute-sent-live-only or execute-unverified if durable rollout evidence is
not observed. Throughline reports execute-durable-verified when the rollout
records a new rollback marker and records the injected active-work memory.
Developer memory injection is item-level on current Codex hosts and may not add a
host-visible turn during the immediate post-inject read; Throughline therefore
uses the thread/inject_items response shape to decide whether a turn-count
increase should be expected.
doctor --codex also reports this context-refresh readiness explicitly:
rollback source, inject memory source, the L1/L2/L3 memory contract, current
L1/L2/L3 counts, the heuristic reduction estimate when rollout text is
available, and the host primitive audit status as diagnostic context. L3 is
reported as references-only; L3 bodies and tool payloads are not injected.
Codex trim dry-run reports a context reduction estimate when rollout text is
available: rollback-candidate estimated tokens, injected-memory estimated
tokens, and net estimated reduction. This is a chars / 4 heuristic from the
rollout text, not an exact host tokenizer measurement. If rollback candidate
turns are 0, there is no current trim saving under the active keep-recent
setting.
Claude-side rewind UI itself is not driven by Throughline. The auto-handoff
flow is /clear → new SessionStart → automatic injection of curated memory.
Throughline does not invoke /rewind or any Claude Code internal command.
Codex-primary setup has an installed Stop hook after global
throughline install. In real sessions, verify capture rather than assuming it:
doctor --codex compares the current Codex thread with the latest captured DB
session, and makes any missing Throughline DB advance visible. A Codex VSCode
session that was already open before hook shape changes may not be a clean
natural Stop smoke; use a newly started session or codex exec smoke for that
check. A newly started VSCode-origin Codex session has been verified with
matching current Codex thread and latest DB session in doctor --codex.
The following commands are the explicit diagnostic, resume, smoke, and
guarded trim surfaces:
throughline doctor --trim --host claude
throughline doctor --trim --host codex
throughline doctor --codex
throughline codex-capture --codex-thread-id <id> --json
throughline codex-summarize --session codex:<id> --json
throughline codex-resume --session codex:<id> --format handoff
throughline codex-handoff-start --session codex:<id>
throughline codex-handoff-start --session codex:<id> --print-prompt
throughline codex-handoff-smoke --session codex:<id> --json
throughline codex-handoff-model-smoke --session codex:<id> --dry-run --json
# optional model smoke; uses codex exec --ephemeral --sandbox read-only:
# THROUGHLINE_EXPERIMENTAL_CODEX_HANDOFF_MODEL_SMOKE=1 throughline codex-handoff-model-smoke --session codex:<id> --json
printf '**Next move**: continue the current Codex task\n' \
| throughline codex-resume --session codex:<id> --memo-stdin
printf '**Next move**: continue the current implementation\n' \
| throughline trim --dry-run --host claude --memo-stdin
throughline codex-threads --json --limit 5
throughline trim --dry-run --host codex --codex-thread-id <id>
throughline trim --dry-run --host codex --codex-thread-id <id> --preview-max-chars 4000
throughline trim --preflight --host codex --codex-thread-id <id>
CODEX_THREAD_ID=<id> throughline trim --preflight --host codex
throughline trim --execute --host codex --all
# read-only app-server process restart smoke; not full VS Code restart-safe proof:
# THROUGHLINE_EXPERIMENTAL_CODEX_RESTORE_SMOKE=1 throughline codex-restore-smoke --codex-thread-id <id> --json
# read-only local restore source inventory; not full VS Code restart-safe proof:
# throughline codex-restore-source-audit --codex-thread-id <id> --json
# manual two-phase VS Code reload/reconnect smoke:
# THROUGHLINE_EXPERIMENTAL_CODEX_VSCODE_RESTORE_SMOKE=1 throughline codex-vscode-restore-smoke --prepare --codex-thread-id <id> --json
# after reloading/reconnecting VS Code and sending the printed prompt:
# throughline codex-vscode-restore-smoke --verify --codex-thread-id <id> --marker <marker> --prepared-at <iso> --after-vscode-restart --json
# explicit current-thread trim:
throughline trim --execute --host codex --all --codex-thread-id <id>For Codex trim, the default memory session is the current Codex thread:
codex:<CODEX_THREAD_ID> / codex:<THROUGHLINE_CODEX_THREAD_ID>. Throughline
does not fall back to the latest project session for Codex injection, because
that can mix Claude-side memory into a Codex rollback. Pass --session only
when deliberately injecting a different captured session.
That current-work framing matters: the original /tl design learned that L1/L2
memory alone can read like past logs rather than "the work in progress". The
memo is one strong signal, but the broader mechanism is explicit structure:
recent L2 is labeled as an active work thread, older hypotheses may be
superseded by later entries, and the continuation instruction appears at the
top and bottom of the injected memory.
For Codex-primary sessions, throughline codex-capture --codex-thread-id <id>
stores active rollout turns under codex:<thread_id>, and throughline
codex-summarize --session codex:<thread_id> can write older captured L2 turns
to L1 with the Codex CLI backend. throughline codex-resume --session
codex:<thread_id> renders that memory as an active-work context. --format
handoff renders a shorter prompt for starting a new Codex thread without
mutating the old one; it caps recent L2 entries, long body text, and detail
references while pointing back to the full codex-resume context. --format
item-json returns a Codex developer-message item for hosts that accept
structured item injection; it is a rendering surface only and does not mutate
the Codex thread by itself. --memo-stdin prepends an explicit Codex-primary
current-work memo to that rendered context; this is a Codex-side opt-in for
"what was I about to do next" signal, independent from the Claude /tl baton.
throughline codex-visibility-smoke is the experimental mutation check for
that rendered memory: it injects the active-work developer message and starts a
marker-check model turn through the Codex app-server, so it requires the
THROUGHLINE_EXPERIMENTAL_CODEX_MODEL_VISIBLE_SMOKE=1 opt-in. In local real-host
smoke testing, the marker appeared in item/agentMessage/delta; use
--resume-after-inject to verify injected memory still survives a second
thread/resume before turn/start, and use --request-timeout-ms /
--timeout-ms when the model turn may take longer than the default wait.
throughline codex-restore-smoke is the read-only restart diagnostic for the
same boundary: it starts fresh Codex app-server processes and compares
thread/read / thread/resume / paginated thread/turns/list turn counts with
the rollout active turn count. Its proof scope is
app_server_process_restart_only; even a passing result is not VS Code
restart-safe proof for rollback / inject. With --inspect-risky-rollout, it can
inspect a risky rollout read-only; if retained rollback text appears in
app-server responses, the status becomes app-server-restore-text-retained
when it appears in blocking candidates such as direct turn text or
replacement_history, or app-server-restore-text-quoted when it appears only
inside quoted/tool-output fields such as aggregatedOutput. The text-match
report includes sample JSON paths, location kinds, risk classes, and
blocking-candidates so quoted old output is not confused with resurrected user
message fields.
throughline codex-restore-source-audit is the local inventory companion: it
checks the Codex rollout, session_index.jsonl, state_*.sqlite, and VS Code
globalStorage / workspaceStorage candidates, VS Code settings.json, and VS
Code logs for the chosen thread id and retained rollback text. SQLite-backed VS
Code storage candidates such as .vscdb, .sqlite, .sqlite3, and .db are
opened read-only and summarized by table / column / needle matches. It also
scans installed OpenAI/Codex VS Code extension bundles for restore-path signals
such as thread/read, thread/resume, thread/turns/list, reconnect
needs_resume, persisted webview atoms, follow-up queue signals, and explicit
rollback non-resurrection projection candidates such as replacement_history
filter / tombstone paths. A match here is static evidence only; current tested storage roots can still report
VS Code storage matches: 0 for the selected thread and retained rollback text.
VS Code log matches are also classified into thread-id hits, retained rollback
text hits, patch-apply failures, thread stream broadcasts, and
replacement_history signals so incidental log mentions can be separated from
restore-path evidence.
Its proof scope is local_restore_source_inventory_only; it can narrow the
restore-source hypothesis, but it still does not prove VS Code restart safety.
These VS Code scans are diagnostics only. The product repair path remains the
host-agnostic same-thread contract reported by codex-host-primitive-audit, not
a VS Code-only implementation path.
throughline codex-vscode-restore-smoke is the manual full-boundary protocol:
--prepare injects a hidden active-work developer-memory marker, then VS Code
must be reloaded or reconnected and asked a prompt that does not contain the
marker. --verify scans the rollout for a marker answer after the prepare
timestamp and rejects the proof if the marker leaked through the user prompt.
Only a marker-free smoke prompt followed by an assistant answer whose trimmed
text exactly equals the marker, plus --after-vscode-restart, is treated as
restartSafe: true; prepare remains an explicit experiment gated by
THROUGHLINE_EXPERIMENTAL_CODEX_VSCODE_RESTORE_SMOKE=1. A real VS Code
reload/reconnect run has passed this marker proof, showing hidden developer
memory can remain model-visible after reconnect. That is not the same as
proving rollback-targeted user turns cannot resurrect, so rollback-specific
smokes remain the stronger diagnostic boundary.
throughline codex-vscode-rollback-smoke --verify --after-vscode-restart is
the read-only verifier for that next proof: it requires a rollback event,
rolled-back user text, a later user turn, and restoreSafety: ok before it
will report restartSafe: true.
throughline codex-rollback-model-visible-smoke is a stricter controlled
experiment for the current thread: --prepare creates a unique user marker and
rolls that turn back, while --verify later starts a model turn that contains
only the marker prefix, not the full marker. It reports reproduced only if the
model returns the hidden full marker. This mutates the thread and is gated by
THROUGHLINE_EXPERIMENTAL_CODEX_ROLLBACK_MODEL_VISIBLE_SMOKE=1. Use
--marker-file <path> for live runs so the full marker is stored locally instead
of being printed into the same conversation being tested. Verify output reports
rolledBackMarkerModelVisible separately from restartSafe. In a controlled
2026-05-08 current-thread run, both the immediate fresh app-server verify and
the post VS Code reload/reconnect verify returned not-reproduced with no full
marker in the prompt or observed assistant output.
A 2026-05-07 incident-shaped live rollback run produced useful risk evidence:
thread_rolled_back and injected active-work memory were recorded in the
rollout, but rollback-targeted user text remained in
compacted.replacement_history, and the verifier later observed rolled-back
user text reappearing in rollout/app-server diagnostics (restoreSafety:
risk). A later risky restore inspection found those retained matches only
inside aggregatedOutput, so this is not yet proof that the text is sent as a
fresh user message or model-visible input. Because the controlled reproduction
smoke stayed clean across app-server restart and VS Code reload/reconnect,
Throughline no longer blocks Codex trim solely on this diagnostic risk.
For Codex, fresh-thread handoff remains an explicit continuation path in trim
plans and trim diagnostics, but it is not a replacement for current-thread
trim. The guided entrypoint is
throughline codex-handoff-start --session codex:<thread-id>; it shows the
structural smoke, model-smoke dry-run boundary, handoff render command, optional
live model smoke, and can include the prompt with --print-prompt. With
--memo-stdin, it also propagates --memo-stdin into the replay commands and
reminds you to pipe the same memo when using them separately. Add --execute
to create a new Codex app-server thread, inject the handoff memory as a
developer item, and open it with --open-host auto|vscode|cli|none. The
individual commands remain available: validate the fresh-thread handoff with
throughline codex-handoff-smoke --session codex:<thread-id>, optionally audit
the model-smoke boundary with
throughline codex-handoff-model-smoke --session codex:<thread-id> --dry-run --json,
render it with throughline codex-resume --session codex:<thread-id> --format handoff,
then start a new Codex thread with that context. This does not mutate the current
thread. trim --execute --host codex is the current-thread mutation path and
still requires explicit execution, injectable Throughline DB memory, and
explicit Codex thread identity.
Human-readable dry-run output truncates the inline memory preview for scanability;
the full text remains in --json as memoryPreview.text, and for Codex the
fresh-thread continuation can be guided with codex-handoff-start or rendered
directly with the codex-resume command shown in the fresh-thread continuation path.
Multi-session token monitor
Run:
throughline monitor # all active sessions in the current project
throughline monitor --all # every project, every session
throughline monitor --session <id-prefix>Example output:
[Throughline] 1 セッション
▶ Throughline Claude 2ed5039c just now ██░░░░░░░░ 205.1k / 1.0M claude-opus-4-6
Throughline Codex 019e085c just now ██████░░░░ 151.9k / 258.4k gpt-5.5- Claude token counts are accurate. Read straight from the latest
message.usagefield in the session transcript JSONL, which is what Anthropic's API actually reported (input_tokens + cache_creation_input_tokens + cache_read_input_tokens). Nolength / 4approximation. - Codex token counts use the rollout
token_countevent when present. The monitor discovers live Codex rollouts directly from~/.codex/sessions/**/rollout-*.jsonl, so a current Codex session can appear even before the Codex Stop hook writescodex:<thread_id>monitor state. While the monitor is running it reads the live rollout every tick and prefers the latest verifiedtoken_countsample. During an open Codex turn, the monitor overlays transientoutput_tokenson top ofinput_tokens; whentask_completearrives it drops back to verifiedinput_tokensonly. If a Codex rollout has no token-count event, Throughline can show an explicit estimate withestimated: trueand the monitor marks it withest; it is not presented as exact usage. - Codex auto-refresh is disabled. The Codex
UserPromptSubmitandPostToolUsehooks capture rollout memory and write monitor state, but they do not inject$throughlineinstructions. The Codex Stop hook also captures DB memory, writes monitor state, and stays quiet instead of sending rollback + injection above a threshold. - 1M-context detection is automatic. It checks the
[1m]suffix in the transcript, falls back to string matching on1M context, and finally promotes to 1M if observed usage exceeds 200k. - Multi-session view. Each Claude Code or Codex session writes its own
state file (
~/.throughline/state/<session_id>.json), and active Codex rollouts are discovered directly from the Codex session directory. Codex session ids are stored ascodex:<thread_id>in JSON state, while the display shows the raw first 8 thread-id characters (for example019e085c) to avoid the ambiguouscodex:01prefix slice. The monitor scans every second and displays one row per live session, sorted by last activity. The most recent one is highlighted with▶. - Stale hiding. Sessions that haven't been touched in 15 minutes drop out of the default view; files older than 24 hours are deleted entirely. This is the only time threshold in the system and is used solely for display hygiene — no memory decisions are made from it.
- Line-wrap safe. Each line is truncated to
process.stdout.columns - 1before drawing, preserving ANSI color codes. The redraw cursor math cannot desync on narrow terminals. - Resize resilient via OSC 18t. Windows ConPTY + VS Code task terminals
freeze
process.stdout.columnsat the PTY's initial size and never propagate panel resizes into Node, so polling orresizeevents can't catch them. Throughline queries the terminal itself with the CSI18 tescape (\x1b[18t) every tick, parses the\x1b[8;rows;cols treply off stdin in raw mode, and uses the real current width for truncation. On terminals that don't answer the query, the renderer falls back toprocess.stdout.columns → env.COLUMNS → 80. When the width changes the viewport is cleared in full (\x1b[2J\x1b[3J\x1b[H) before the next frame so the previous, wrongly-sized frame can't stack beneath it. - Per-row "last updated" stamp. Each session row carries an 8-cell
just now/24m agostamp right after the session id, placed before the bar so narrow terminals don't truncate it. It follows the newest state, transcript, or Codex rollout mtime, so active sessions stay visible and the stamp can move before the next Stop hook completes. When you need more detail,throughline doctor --session <id-prefix>compares the state file against the actual transcript JSONL and flags drift, idle time, and/clear-induced transcript path staleness. - Live usage first, state snapshot as fallback. When the Stop hook finishes
a turn it persists the latest
tokens / model / contextWindowSizeback into the state file. The monitor now prefers live Claude transcript / Codex rollout reads and uses the snapshot only when the live file cannot provide usage, so the display no longer waits for Stop to update. - Host-aware state. Missing
hostmeans an older Claude state file. Codex states usehost: "codex", keeptranscriptPath: null, and store the Codex rollout path separately asrolloutPathso the Claude transcript parser is never pointed at a Codex rollout. - Non-blocking Claude Stop hook (v0.3.22+). The Claude Stop hook is registered with
"async": truesothroughline process-turnruns in the background and does not delay Claude's reply from reaching you. L1 Haiku summarization (claude -psubprocess + inference, seconds to tens of seconds) would otherwise stall the user-facing response of every turn; since L1 is only needed for the next session's SessionStart injection, there is no reason to block the current turn on it. Existing installs needthroughline uninstall && throughline installto promote the flag (the dedup logic skips entries that match by command string).
VS Code auto-start (automatic)
After throughline install, the current VS Code / Cursor / VSCodium project
gets .vscode/tasks.json provisioned immediately when VS Code environment
variables are present. Any other VS Code project you work in also gets the file
on the first session event. The file configures runOn: folderOpen so the
monitor appears in a dedicated terminal panel the next time you open that
folder.
How it works. ensureMonitorTaskFile is called from throughline install
and from all three Claude hooks (SessionStart, UserPromptSubmit, Stop). Whichever
one fires first in your environment creates the file; the rest are idempotent
no-ops. Once per project it inspects .vscode/tasks.json:
- No file yet → creates one with a single
Throughline Monitortask, and emits a one-time<system-reminder>to stdout so Claude tells you a Developer: Reload Window is needed to activate thefolderOpentask once (v0.3.19+). - Plain JSON with other tasks → appends the monitor task, preserves your
existing entries,
version, and indentation (same notice fires once). - JSONC (comments or trailing commas) → does not touch the file. Prints a one-time notice to stderr asking you to paste the snippet below.
- Already contains a Throughline Monitor task → does nothing (idempotent; this is the common path on every subsequent turn; notice is silent).
For Codex-primary projects, hook stdout is not always surfaced in the chat.
throughline doctor --codex therefore reports the VS Code monitor task status,
its runOn value, and the same Reload Window note in a visible diagnostic.
The generated task uses type: 'shell' with the absolute path to Node and
bin/throughline.mjs. VS Code wraps shell tasks in a PTY (xterm.js) so the
monitor sees isTTY=true, real columns, and resize events. Windows .cmd
shims and missing PATH entries cannot break it because the command is already
an absolute Node binary path.
Opt out: set THROUGHLINE_NO_VSCODE=1 in the environment used by Claude
Code. Delete .vscode/tasks.json (or just the monitor entry) if you want to
stop auto-start for a project that already has one.
Manual snippet for JSONC tasks.json files. If Throughline refused to edit
your tasks.json because it contains comments or trailing commas, add this
entry to the tasks array yourself:
{
"label": "Throughline Monitor",
"type": "shell",
"command": "throughline monitor",
"isBackground": true,
"presentation": {
"reveal": "always",
"panel": "dedicated",
"group": "throughline",
"close": false,
"echo": false,
"focus": false,
"showReuseMessage": false,
"clear": true
},
"runOptions": { "runOn": "folderOpen" },
"problemMatcher": []
}Commands
| Command | What it does |
| ---------------------------------------------- | ------------------------------------------------------------ |
| throughline install | Register Claude user hooks/slash commands, the global Codex UserPromptSubmit/PostToolUse/Stop hooks, the global $throughline Codex skill, and the current VS Code monitor task when applicable |
| throughline install --project | Register Claude hooks/slash commands in this repo only |
| throughline uninstall | Remove Throughline-managed Claude hooks/slash commands, only the Throughline-managed Codex hook, and the $throughline Codex skill |
| throughline monitor [--all] [--session <id>] | Run the multi-session token monitor |
| throughline monitor --diag | Dump TTY/columns/env diagnostics (for debugging monitor render bugs) |
| throughline detail <time> | Retrieve L2 body text and L3 tool I/O for a turn (see below) |
| throughline doctor | Check Node version, hook registration, DB writability, PATH |
| throughline doctor --session <id-prefix> | Diagnose a specific session — detect state/transcript drift, idle vs. stuck |
| throughline doctor --trim --host claude\|codex | Diagnose trim host boundaries, manual procedure, and Codex host primitive blockage |
| throughline doctor --codex | Diagnose Codex primary entry state, captured DB sessions, context-refresh memory contract, new-thread handoff readiness, safe continuation status, and host primitive audit |
| throughline handoff-preview --session <id> | Print a Codex-facing throughline_handoff JSON projection |
| throughline codex-capture --codex-thread-id <id> | Capture active Codex rollout turns into a codex:<thread_id> DB session |
| throughline codex-summarize --session codex:<id> | Summarize captured Codex L2 into L1 with the Codex CLI backend |
| throughline codex-resume --session codex:<id> | Render Codex active-work context from a captured Codex session |
| throughline codex-resume --session codex:<id> --format handoff | Render a concise fresh-thread handoff prompt without mutating the current thread |
| throughline codex-handoff-start --session codex:<id> | Guided start plan for moving handoff memory into a new Codex thread; add --execute to create the thread through app-server, inject developer memory, and open it with --open-host auto\|vscode\|cli\|none; use --print-prompt to include the prompt and --memo-stdin to carry a current-work memo |
| throughline codex-handoff-smoke --session codex:<id> | Read-only validation that the fresh-thread handoff prompt is pasteable before starting a new thread |
| throughline codex-handoff-model-smoke --session codex:<id> | Experimental marker smoke for the handoff prompt. --dry-run checks readiness / command boundary without starting Codex exec; --memo-stdin carries a current-work memo; live codex exec --ephemeral --sandbox read-only requires explicit env opt-in |
| throughline codex-visibility-smoke --session codex:<id> | Experimental Codex app-server marker smoke; injects memory and starts a model turn |
| throughline codex-restore-smoke --codex-thread-id <id> | Experimental read-only app-server restart restore smoke; --inspect-risky-rollout classifies retained rollback text as blocking retained text or quoted/tool-output text; does not prove VS Code restart safety |
| throughline codex-restore-source-audit --codex-thread-id <id> | Read-only local restore-source inventory, including VS Code projection-candidate facts; does not prove VS Code restart safety |
| throughline codex-host-primitive-audit | Read-only Codex app-server schema audit for same-thread rollback non-resurrection primitives and the host-agnostic repair contract |
| throughline codex-vscode-restore-smoke --prepare/--verify --codex-thread-id <id> | Manual VS Code reload/reconnect marker proof protocol |
| throughline codex-vscode-rollback-smoke --verify --codex-thread-id <id> | Manual VS Code rollback non-resurrection verifier |
| throughline codex-threads | List read-only Codex thread id candidates for the current project |
| throughline codex-sidecar-diagnostics | Check codex-sidecar diagnostics status for this project |
| throughline codex-sidecar-dry-run | Print a normalized read-only sidecar request without running the app server |
| throughline trim --dry-run --host codex | Preview Codex same-thread context trim memory and host boundary; does not rollback automatically |
| throughline trim --preflight --host codex | Read/resume the explicit Codex thread and preview any app-server-count rollback adjustment without rollback/inject |
| throughline trim --execute --host codex | Explicit diagnostic Codex current-thread rollback + Throughline DB memory inject; bare $throughline does not run this automatically |
| throughline status | Print DB statistics (sessions, skeletons, bodies, details) |
| throughline --version | Print the installed version |
Slash commands (invoked by the user in Claude Code):
| Command | What it does |
| ------------- | ----------------------------------------------------------------- |
| /tl | Write a handoff baton (explicit inheritance signal across non-/clear boundaries — new chat / VSCode restart) |
| /clear | Built-in Claude Code reset. Throughline's UserPromptSubmit hook also writes a baton so the next session inherits the cleared session's memory |
| /sc-detail <time> | Retrieve L2 body text and L3 tool I/O for a past turn |
Since v0.4.1, both
/clearand/tltyped in the prompt write a baton identifying the current session, so the nextSessionStartdeterministically inherits that exact predecessor. Thesource='clear'auto path remains as a fallback for/cleartriggered outsideUserPromptSubmit(for example via the VSCode extension menu);THROUGHLINE_DISABLE_AUTO_HANDOFF=1only opts out of that fallback.
Hook subcommands (invoked by Claude Code, not by humans):
session-start (SessionStart), process-turn (Stop),
prompt-submit (UserPromptSubmit — detects /tl and /clear and writes a baton).
throughline detail — for AI, not humans
throughline detail is the escape hatch Claude itself uses to pull archived
detail back into the context when an L1 summary isn't enough. The injection
footer explicitly instructs Claude to run this via its Bash tool when a past
turn's tool I/O becomes relevant.
throughline detail 14:23:05 # single timestamp
throughline detail 14:23-14:30 # timestamp rangeOutput groups records by kind: L2 conversation bodies, then L3 tool input/
output, then system messages (hook output), then images. Records are scoped to
the current project's merge chain so Claude only sees turns from its own
project history.
Requirements
- Node.js >= 22.5 (for the built-in
node:sqlitemodule — no native build required, nonpm installof SQLite bindings) - Claude Code with hooks support (
SessionStart,Stop) - Claude Max subscription (for Haiku-based L1 summarization via
claude -p) - Works on Windows, macOS, Linux
Throughline has zero runtime dependencies. The published tarball is just
plain .mjs files.
Data layout
~/.throughline/
├── throughline.db SQLite database (WAL mode)
├── haiku-workdir/ Isolated cwd for Haiku subprocess (recursion guard)
└── state/
└── <session_id>.json Per-session activity state for the monitorSchema v7:
sessions— one row persession_id, withproject_pathandmerged_intoskeletons— L1 one-liners, keyed by(session_id, origin_session_id, turn, role)bodies— L2 verbatim text (user + assistant), same key shapedetails— L3 records withkindcolumn (tool_input/tool_output/system/image/thinking) andsource_idfor idempotent re-processinghandoff_batons— one row perproject_path, withsession_idandcreated_at. Written by theUserPromptSubmithook when the user types/tlor/clear. Consumed and deleted by the nextSessionStartif within the 1-hour TTL. (v8 dropped thememo_textcolumn when memo was retired in v0.4.0.)injection_log— audit trail of injection events
All memory tables carry an origin_session_id so rebonded rows keep their
lineage across a chain of /tl handoffs.
Design principle: no fallback code
Throughline deliberately refuses to swallow unexpected errors.
Silent try { … } catch { /* ignore */ } blocks hide bugs; instead, hooks throw
and exit with a non-zero status so Claude Code surfaces the failure in stderr.
Specifically:
- JSON parse failures →
throw, notcontinue - Missing required fields →
throw new Error(...), notexit(0) - DB transactions → explicit
BEGIN IMMEDIATE/ROLLBACK/ re-throw - Hook entry points wrap
main()with a single.catchthat writesstderrand exits with code 1
The only tolerated silent paths are:
- JSONL per-line parse tolerance (tail partial writes are part of the format spec)
- State-file corruption recovery (files are idempotently regenerated next turn)
See docs/PUBLIC_RELEASE_PLAN.md §0 for the full
rule.
Haiku recursion defense
The default L1 summarization path spawns
claude -p --model claude-haiku-4-5-* as a subprocess. Without precautions this
would recursively fire the same Stop hook on the subprocess and infinite-loop.
Two defenses stack:
- Isolated cwd. The subprocess runs in
~/.throughline/haiku-workdir/, a directory that contains no.claude/settings.json, so project-local hooks are never picked up by the child. - Env var guard. The parent sets
THROUGHLINE_IN_HAIKU_SUBPROCESS=1in the child env. The Stop hook (turn-processor.mjs) exits immediately on line 1 if it sees this variable.
See src/haiku-summarizer.mjs for the implementation.
Troubleshooting
Monitor says 待機中 — アクティブなセッションがありません
No session has touched its state file in the last 15 minutes. Send a message in
Claude Code and the monitor should pick it up within 1 second. If it still does
not, run throughline doctor.
Monitor seems stuck on the same value
Each session row ends with a (Nm ago) stamp. If it keeps growing, the session
is idle — no assistant turn has finished. For a deeper look, run
throughline doctor --session <id-prefix> to compare the state file against
the actual transcript JSONL and flag drift, idle time, or /clear-induced
transcript path staleness.
throughline install wrote to the wrong settings file
By default, Throughline installs to ~/.claude/settings.json (user scope, applies
to all projects). Use --project to scope it to the current directory's
.claude/settings.json instead.
Hooks never fire
Run throughline doctor — it checks Node version, hook registration, DB
writability, and PATH resolution. If the binary is not on PATH, reinstall with
npm install -g throughline.
The most common cause is the npm global bin/ directory is not on PATH.
This happens when you set npm config set prefix ~/.npm-global (the sudoless
recommended setup) but only added the export to ~/.profile, not ~/.bashrc.
VSCode's integrated terminal launches an interactive non-login bash, which
reads .bashrc only — so the export is silently skipped and throughline is
not found. Fix:
# bash: add to ~/.bashrc
if [ -d "$HOME/.npm-global/bin" ] ; then
PATH="$HOME/.npm-global/bin:$PATH"
fiThen reload the shell (source ~/.bashrc) and verify with which throughline.
throughline install itself prints a warning to stderr if PATH resolution
fails at install time, but if you missed it, run throughline doctor again.
Using Throughline across Windows ↔ WSL2 (or Linux ↔ macOS)
Two things to know:
The DB lives under
os.homedir(), so each environment has its own database.C:\Users\<you>\.throughline\throughline.db(Windows) and/home/<you>/.throughline/throughline.db(WSL2) are unrelated. Memory does not roam. To migrate:# From WSL2, copy Windows-side DB into WSL2 home cp /mnt/c/Users/<you>/.throughline/throughline.db ~/.throughline/throughline.dbOr vice versa. Do this with no Claude Code sessions running so WAL/SHM are clean.
WSL2 sees Windows npm shims via
/mnt/c/...AppData/Roaming/npm, which may be earlier on PATH than your WSL2-native npm-global bin. Symptom:which throughlinereturns a/mnt/c/...path even though you rannpm install -g throughlineinside WSL2. Fix: ensure~/.npm-global/binis before the Windows npm path inPATH(the.bashrcsnippet above prepends, so it does this naturally).
.vscode/tasks.json should not be committed to git (recommended)
The Throughline Monitor task that gets auto-generated contains absolute
paths specific to your machine (process.execPath and the install location
of throughline.mjs). For shared repositories, add one of these to
.gitignore:
.vscode/tasks.json # only ignore tasks.json (settings.json etc. stay shared)
.vscode/ # ignore the whole .vscode directoryThroughline detects this and prints a one-time recommendation when it first creates / merges / repairs the file. (You don't have to follow the advice — v0.3.23+ auto-repairs stale absolute paths anyway, so committing is not catastrophic.)
Cross-environment .vscode/tasks.json errors after switching machines
If you commit .vscode/tasks.json to git and pull it on a different machine
(Windows ↔ WSL2, Linux ↔ macOS, etc.), the command and args paths inside
were absolutized to the original machine. Throughline auto-detects this:
on the next hook fire it rewrites just the command / args fields to the
current environment, preserving any label / presentation customization
you made. You'll see a one-time Reload Window notice in Claude Code when
the repair happens.
node:sqlite warning on startup
Node.js prints ExperimentalWarning: SQLite is an experimental feature on stderr.
This is cosmetic — the module is stable enough for production and is used
unchanged here.
Database got corrupted / want a clean slate
Delete ~/.throughline/throughline.db (and the -shm / -wal companion files)
and ~/.throughline/state/*.json. A fresh database with schema v7 is created on
the next hook fire.
New session didn't inherit memory from the previous one
Since v0.4.1, both typed /clear and /tl write a baton, and the auto path
falls back on source='clear' for menu-driven /clear. If inheritance still
did not happen, the most likely cause is one of: (a) the previous session was
never recorded (no Stop hook fired — check throughline status), (b) the
1-hour baton TTL expired before the new session opened, (c) the new session's
project_path (cwd) differs from the previous one, so they live in different
session chains, or (d) you set THROUGHLINE_DISABLE_AUTO_HANDOFF=1 and the
/clear came from the VSCode menu which never reaches UserPromptSubmit.
Memory is still in SQLite — you can retrieve specific turns with
/sc-detail <time>.
Development
git clone https://github.com/kitepon-rgb/Throughline.git
cd Throughline
npm link # Put `throughline` on PATH (dev only)
throughline install --project # Register hooks for this repo only
node --test src/turn-processor.test.mjs src/session-merger.test.mjsRun the monitor directly without a global install:
node src/token-monitor.mjsWhen any Throughline hook fires for the first time in a folder, it
auto-generates .vscode/tasks.json with an absolute path to the monitor
executable for the current machine. The file is gitignored (since v0.4.1)
because the absolute path is per-machine. Reload the VS Code window after
the first generation to pick up the auto-start task.
Design docs
docs/L1_L2_L3_REDESIGN.md— core design spec for the L1/L2/L3 differential layer model (schema v4 base + v5 L3 classification extension). Authoritative for the memory layering rules.docs/INHERITANCE_ON_CLEAR_ONLY.md— design record for the/tlbaton handoff system (schema v6–v7). Explains why the current inheritance is opt-in rather than heuristic.docs/THROUGHLINE_CODEX_DUAL_SUPPORT.md— architecture brief for adding Codex support without replacing the Claude Code hook/slash-command path.docs/throughline-rollback-context-trim-insight.md— design insight for context rollback/trim, including why restored memory must be framed as current work rather than passive history.docs/THROUGHLINE_CODEX_FIRST_ROADMAP.md— current next-phase TODO plan: Codex primary first, Codex rewind-compatible trim next, Claude rewind finalization after that.docs/THROUGHLINE_CODEX_TRIM_IMPLEMENTATION_PLAN.md— historical integrated TODO plan and implementation record for Claude/Codex dual support and rollback trim.docs/PUBLIC_RELEASE_PLAN.md— public release plan, implementation status by version, § 0 fallback rule, and remaining tasks.docs/archive/— superseded design documents kept for historical reference (original CONCEPT, session-linking experiments, pre-publish action list).
License
MIT — see LICENSE.
