throughline 0.6.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -1
- package/README.md +86 -28
- package/bin/throughline.mjs +20 -0
- package/docs/00_overview.md +12 -0
- package/docs/02_clear_auto_handoff_plan.md +47 -13
- package/docs/04_public_release_plan.md +1 -0
- package/docs/14_observer_completed_turn_feed_plan.md +290 -0
- package/docs/BUGHUB_RUNTIME_ERROR_STORE_PLAN.md +9 -3
- package/docs/adr/0002-observer-claude-completion-receipt.md +42 -0
- package/docs/adr/0003-observer-completed-chain-cursor.md +34 -0
- package/docs/adr/0004-observer-db-pair-projection.md +71 -0
- package/docs/adr/0005-observer-read-pagination.md +51 -0
- package/docs/adr/0006-observer-page-offset-proof.md +33 -0
- package/docs/adr/0007-observer-read-cli-contract.md +61 -0
- package/docs/adr/0008-observer-wait-deadline-cancel.md +81 -0
- package/docs/adr/0009-observer-integration-regression-and-docs.md +37 -0
- package/docs/adr/0010-observer-o1-phase-acceptance.md +49 -0
- package/docs/adr/0011-observer-o1-control-lane-reconciliation.md +34 -0
- package/docs/adr/0012-claude-stop-transcript-flush-barrier.md +32 -0
- package/docs/adr/0013-observer-read-busy-writer-gate.md +46 -0
- package/docs/adr/0014-two-phase-handoff-ghost-baton.md +112 -0
- package/docs/adr/0015-l1-summarizer-model-effort-ratio.md +81 -0
- package/docs/adr/0016-push-pull-recall-injection.md +93 -0
- package/package.json +1 -1
- package/rag/01-hooks/hook-stdout-10k-persisted-output.md +65 -0
- package/rag/INDEX.md +4 -0
- package/src/auditor-context.mjs +92 -11
- package/src/auditor-context.test.mjs +116 -1
- package/src/baton.mjs +27 -7
- package/src/baton.test.mjs +44 -0
- package/src/body-digest.mjs +9 -0
- package/src/cli/auditor-context.test.mjs +1 -1
- package/src/cli/factory-diagnostics.mjs +1 -0
- package/src/cli/factory-diagnostics.test.mjs +6 -2
- package/src/cli/observer-read.mjs +73 -0
- package/src/cli/observer-read.test.mjs +93 -0
- package/src/cli/observer-wait.mjs +123 -0
- package/src/cli/observer-wait.test.mjs +167 -0
- package/src/cli/recall.mjs +279 -0
- package/src/cli/recall.test.mjs +269 -0
- package/src/codex-rollout-memory.mjs +13 -0
- package/src/codex-rollout-memory.test.mjs +27 -0
- package/src/codex-thread-index.mjs +1 -1
- package/src/codex-thread-index.test.mjs +18 -0
- package/src/completed-turn-receipts.mjs +374 -0
- package/src/completed-turn-receipts.test.mjs +186 -0
- package/src/db-schema.test.mjs +9 -2
- package/src/db.mjs +20 -1
- package/src/decision-log.mjs +24 -0
- package/src/haiku-summarizer.mjs +93 -16
- package/src/haiku-summarizer.test.mjs +118 -9
- package/src/handoff-executor.mjs +161 -0
- package/src/hook-entrypoints.test.mjs +192 -12
- package/src/observer-codex-projection.test.mjs +49 -0
- package/src/observer-turn-feed.mjs +392 -0
- package/src/observer-turn-feed.test.mjs +339 -0
- package/src/observer-turn-wait.mjs +102 -0
- package/src/observer-turn-wait.test.mjs +122 -0
- package/src/pending-handoff.mjs +96 -0
- package/src/pending-handoff.test.mjs +107 -0
- package/src/prompt-submit.mjs +46 -1
- package/src/resume-context.mjs +337 -60
- package/src/resume-context.test.mjs +228 -1
- package/src/runtime-error-store.mjs +2 -1
- package/src/runtime-error-store.test.mjs +1 -1
- package/src/session-start.mjs +70 -233
- package/src/transcript-reader.mjs +32 -0
- package/src/turn-backfill.mjs +3 -2
- package/src/turn-backfill.test.mjs +10 -4
- package/src/turn-processor.mjs +90 -1
- package/src/turn-processor.test.mjs +142 -0
- package/src/windows-acl-test-helper.mjs +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,103 @@ shipped to npm but were not individually tagged on GitHub.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.8.0] — 2026-07-18
|
|
14
|
+
|
|
15
|
+
### Changed (breaking behavior)
|
|
16
|
+
|
|
17
|
+
- **Injection is now push/pull, and L1 is no longer injected (ADR 0016).** The
|
|
18
|
+
budgeted resume context (9,500 chars) is rebuilt as: header +
|
|
19
|
+
current-position anchor + an always-shown retrieval-guide section as the
|
|
20
|
+
fixed part, then the **entire remaining budget is filled with L2 turns in
|
|
21
|
+
full**, newest-first, turn-atomically (a user+assistant pair goes in whole
|
|
22
|
+
or not at all — no fixed N, no fragment packing). L1 summaries are no longer
|
|
23
|
+
injected; older memory is pulled on demand instead. The guide section bakes
|
|
24
|
+
in the exact session id, ISO-millisecond boundary (strict less-than) and
|
|
25
|
+
turn counts at injection time, so the pull side never recomputes the window.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **`throughline recall --l2|--l1` (ADR 0016).** Read-only pull commands the
|
|
30
|
+
injected guide section points at. `recall --l2 --session <id> --before
|
|
31
|
+
<ISO ms> --last <N>` returns the N turns of full L2 bodies older than the
|
|
32
|
+
boundary, in the same line grammar as the injection (including L3 inline
|
|
33
|
+
suffixes). `recall --l1 ... --skip <N>` lists every turn older than the
|
|
34
|
+
`--l2` range with its L1 summary, honestly marking unsummarized turns
|
|
35
|
+
("全 M ターン / 要約済み K") and always pointing at `throughline detail
|
|
36
|
+
<time>` for full text. The DB is opened read-only; a missing DB is an
|
|
37
|
+
explicit error and is never created or migrated.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **Windows ACL scripts get a 15s timeout (was 3s).** On windows-latest CI
|
|
42
|
+
runners a cold PowerShell start was measured at 3.0–3.2s, so the 3s
|
|
43
|
+
`spawnSync` cap killed the ACL apply/verify scripts of the completed-turn
|
|
44
|
+
receipt store and the runtime error store and surfaced as a flaky
|
|
45
|
+
"Windows owner-only ACL verification failed" (2 consecutive runs, including
|
|
46
|
+
a docs-only commit). The explicit hard-failure contract is unchanged; only
|
|
47
|
+
the cap was raised.
|
|
48
|
+
|
|
49
|
+
## [0.7.0] — 2026-07-17
|
|
50
|
+
|
|
51
|
+
### Changed (breaking behavior)
|
|
52
|
+
|
|
53
|
+
- **Two-phase handoff (ADR 0014).** Claude Code can fire multiple
|
|
54
|
+
`SessionStart` hooks for the same project within a few hundred ms, and some
|
|
55
|
+
of them never materialize into a real session (no transcript is ever
|
|
56
|
+
written). Such a "ghost" could consume the handoff baton first and silently
|
|
57
|
+
swallow the predecessor's memory while the real session started empty
|
|
58
|
+
(observed twice on 2026-07-17; upstream report:
|
|
59
|
+
anthropics/claude-code#78455). `SessionStart` now only registers a pending
|
|
60
|
+
intent (new schema v9 table `pending_handoffs`); the merge and the context
|
|
61
|
+
injection happen at the session's **first `UserPromptSubmit`** — a prompt is
|
|
62
|
+
proof the session is real, and a ghost never submits one. Baton eligibility
|
|
63
|
+
is measured against the consuming session's birth time (`0 <= birth −
|
|
64
|
+
baton_write <= 1h TTL`); a baton written after the session was born is left
|
|
65
|
+
in place for its true successor instead of being stolen by a running
|
|
66
|
+
session. The auto path (`source='clear'`) freezes its predecessor choice at
|
|
67
|
+
`SessionStart` and skips transcript-less (ghost) candidates.
|
|
68
|
+
- **Injection is budgeted to 9,500 chars (ADR 0014).** Hook stdout larger than
|
|
69
|
+
~10,000 chars is silently persisted to a file by Claude Code and the model
|
|
70
|
+
only sees the first 2KB (measured: 9,501 chars pass inline, 15,286 get
|
|
71
|
+
persisted; every >10k injection since v2.1.195 was degraded this way).
|
|
72
|
+
The resume context now always fits inline: header + current-position anchor
|
|
73
|
+
are kept in full, then L1 and L2 fill newest-first. Dropped L2 rows are
|
|
74
|
+
announced inside the injection with their `[time role]` references so the
|
|
75
|
+
model can retrieve any of them via `throughline detail`.
|
|
76
|
+
- **L1 summarization backend and ratio are configurable (ADR 0015).** The
|
|
77
|
+
Claude-primary backend order is now `codex-sidecar` (when configured) →
|
|
78
|
+
Codex CLI (default `gpt-5.6-luna`, reasoning effort `low`, chosen by a
|
|
79
|
+
measured 83-run evaluation) → Claude Haiku → raw L2, with every fallback
|
|
80
|
+
step recording its reason. The compression target is a ratio (default 0.2 =
|
|
81
|
+
1/5 of the source turn). Overrides: `THROUGHLINE_L1_MODEL`,
|
|
82
|
+
`THROUGHLINE_L1_EFFORT`, `THROUGHLINE_L1_RATIO` (invalid ratio values are an
|
|
83
|
+
explicit error, not a silent default). The Codex CLI invocation now passes
|
|
84
|
+
an explicit `-m`; previously `--ignore-user-config` silently ran the CLI's
|
|
85
|
+
built-in default model.
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
|
|
89
|
+
- Schema v9: `pending_handoffs` table (session_id PK, project_path, source,
|
|
90
|
+
auto_predecessor_id, created_at). Rows belonging to ghost sessions are never
|
|
91
|
+
consumed and stay behind harmlessly.
|
|
92
|
+
- The inheritance decision log now records both phases
|
|
93
|
+
(`phase: 'session-start' | 'prompt-submit'`) including injection size and
|
|
94
|
+
dropped-row counts.
|
|
95
|
+
- First npm release to include the JSON-only completed-turn Observer CLI
|
|
96
|
+
boundary: `throughline observer-read` (opaque-cursor pages) and
|
|
97
|
+
`throughline observer-wait` (bounded wait up to 3600s). The completed feed
|
|
98
|
+
uses Throughline-owned Claude Stop receipts and Codex rollout
|
|
99
|
+
`task_complete` records; stale DB projection is reported as
|
|
100
|
+
`projection_pending` without bodies (ADR 0002–0013).
|
|
101
|
+
|
|
102
|
+
### Fixed
|
|
103
|
+
|
|
104
|
+
- Claude Stop waits for the transcript flush barrier before backfilling
|
|
105
|
+
(ADR 0012), and Observer reads wait out transient SQLite writer locks with a
|
|
106
|
+
bounded busy wait instead of failing hard (ADR 0013).
|
|
107
|
+
|
|
108
|
+
## [0.6.3] — 2026-07-14
|
|
109
|
+
|
|
13
110
|
### Fixed
|
|
14
111
|
|
|
15
112
|
- `throughline factory-diagnostics --json` now reports the Codex hook summary
|
|
@@ -1002,7 +1099,10 @@ two attempts, instrument first instead of patching again.
|
|
|
1002
1099
|
|
|
1003
1100
|
---
|
|
1004
1101
|
|
|
1005
|
-
[Unreleased]: https://github.com/kitepon-rgb/Throughline/compare/v0.
|
|
1102
|
+
[Unreleased]: https://github.com/kitepon-rgb/Throughline/compare/v0.8.0...HEAD
|
|
1103
|
+
[0.8.0]: https://github.com/kitepon-rgb/Throughline/compare/v0.7.0...v0.8.0
|
|
1104
|
+
[0.7.0]: https://github.com/kitepon-rgb/Throughline/compare/v0.6.3...v0.7.0
|
|
1105
|
+
[0.6.3]: https://github.com/kitepon-rgb/Throughline/compare/v0.6.2...v0.6.3
|
|
1006
1106
|
[0.6.2]: https://github.com/kitepon-rgb/Throughline/compare/v0.6.1...v0.6.2
|
|
1007
1107
|
[0.3.22]: https://github.com/kitepon-rgb/Throughline/releases/tag/v0.3.22
|
|
1008
1108
|
[0.3.21]: https://github.com/kitepon-rgb/Throughline/compare/v0.3.19...v0.3.21
|
package/README.md
CHANGED
|
@@ -109,7 +109,7 @@ Anthropic API usage from the transcript JSONL (no `length / 4` heuristics).
|
|
|
109
109
|
|
|
110
110
|
---
|
|
111
111
|
|
|
112
|
-
## Three-layer memory model (schema
|
|
112
|
+
## Three-layer memory model (schema v9)
|
|
113
113
|
|
|
114
114
|
```mermaid
|
|
115
115
|
flowchart LR
|
|
@@ -119,7 +119,7 @@ flowchart LR
|
|
|
119
119
|
H --> L3[("L3 · details<br/>tool I/O · thinking")]
|
|
120
120
|
H -. "async<br/>Haiku" .-> L1[("L1 · skeletons<br/>one-liners")]
|
|
121
121
|
|
|
122
|
-
L2 -- "recent 20 turns" --> S["Next
|
|
122
|
+
L2 -- "recent 20 turns" --> S["Next session's first prompt<br/>injection"]
|
|
123
123
|
L1 -- "older turns" --> S
|
|
124
124
|
L3 -. "on demand · throughline detail" .-> S
|
|
125
125
|
|
|
@@ -152,12 +152,17 @@ injects it as plain text:
|
|
|
152
152
|
- L3 stays in SQLite and is retrieved on demand via `/sc-detail <time>`
|
|
153
153
|
|
|
154
154
|
L1 summaries are generated lazily: for sessions that stay under 20 turns, no
|
|
155
|
-
external summarizer is invoked.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
155
|
+
external summarizer is invoked. Summaries target a **compression ratio**
|
|
156
|
+
(default 1/5 of the source turn, configurable via `THROUGHLINE_L1_RATIO`;
|
|
157
|
+
invalid values are an explicit error, not a silent default). In the
|
|
158
|
+
Claude-primary path the backend order is: `codex-sidecar` (when explicitly
|
|
159
|
+
configured for the `summarize-l1` preset) → **Codex CLI** (default
|
|
160
|
+
`gpt-5.6-luna` at reasoning effort `low`, chosen by measured evaluation —
|
|
161
|
+
see ADR 0015; override via `THROUGHLINE_L1_MODEL` / `THROUGHLINE_L1_EFFORT`)
|
|
162
|
+
→ **Claude Haiku 4.5** via a subprocess (`claude -p`), reusing your Claude
|
|
163
|
+
Max login — no API key required. Each fallback step records its reason.
|
|
164
|
+
For Codex-primary capture, the L1 backend is the Codex CLI only; failures are
|
|
165
|
+
explicit and do not fall back to Claude Haiku or raw L2.
|
|
161
166
|
|
|
162
167
|
All three layers (L1/L2/L3) have working write paths as of schema v5.
|
|
163
168
|
`/sc-detail HH:MM:SS` returns user/assistant text (L2) plus a kind-grouped view
|
|
@@ -177,13 +182,14 @@ flowchart LR
|
|
|
177
182
|
U["User types<br/>/clear or /tl"] -->|UserPromptSubmit| W["writeBaton<br/>(session_id + TTL 1h)"]
|
|
178
183
|
W --> B[("handoff_batons<br/>SQLite")]
|
|
179
184
|
M["VSCode menu<br/>clear"] -->|no UserPromptSubmit| X["no baton"]
|
|
180
|
-
NS["Next SessionStart"] -->
|
|
185
|
+
NS["Next SessionStart<br/>(registers intent only)"] --> FP["First user prompt<br/>(proof the session is real)"]
|
|
186
|
+
FP --> C{"baton<br/>present?"}
|
|
181
187
|
B -.-> C
|
|
182
188
|
X -.-> C
|
|
183
189
|
C -->|yes| P1["baton path<br/>(primary)<br/>merge that exact predecessor"]
|
|
184
|
-
C -->|no, source='clear'| P2["auto path<br/>(fallback)<br/>
|
|
190
|
+
C -->|no, source='clear'| P2["auto path<br/>(fallback)<br/>predecessor frozen at SessionStart"]
|
|
185
191
|
C -->|no, source!='clear'| P3["fresh session<br/>no merge"]
|
|
186
|
-
P1 --> INJ["inject L1 + L2 + L3 refs"]
|
|
192
|
+
P1 --> INJ["inject L1 + L2 + L3 refs<br/>(budgeted ≤ 9,500 chars)"]
|
|
187
193
|
P2 --> INJ
|
|
188
194
|
|
|
189
195
|
classDef primary fill:#7c5cff,stroke:#1a1f2e,color:#fff
|
|
@@ -198,9 +204,20 @@ flowchart LR
|
|
|
198
204
|
|
|
199
205
|
When the user types `/clear` or `/tl` in the prompt, the `UserPromptSubmit`
|
|
200
206
|
hook writes a handoff baton with **that session's `session_id`** into the
|
|
201
|
-
`handoff_batons` table. The next
|
|
202
|
-
|
|
203
|
-
|
|
207
|
+
`handoff_batons` table. The next new session consumes the baton **at its
|
|
208
|
+
first user prompt** (eligibility: the session must have been born within the
|
|
209
|
+
1-hour TTL after the baton was written) and merges that exact predecessor's
|
|
210
|
+
memory, regardless of the `source` value.
|
|
211
|
+
|
|
212
|
+
Why the first prompt and not `SessionStart` itself: Claude Code can fire
|
|
213
|
+
multiple `SessionStart` hooks for the same project within a few hundred
|
|
214
|
+
milliseconds, and some of them never materialize into a real session (no
|
|
215
|
+
transcript is ever written). At `SessionStart` time a real session and such a
|
|
216
|
+
"ghost" are indistinguishable — even a real session's transcript file appears
|
|
217
|
+
only ~0.5s **after** the hook fires. A ghost that consumed the baton first
|
|
218
|
+
would silently swallow the predecessor's memory while the real session
|
|
219
|
+
started empty. Deferring consumption to the first user prompt closes this:
|
|
220
|
+
a ghost never submits a prompt, so it can never take the baton (ADR 0014).
|
|
204
221
|
|
|
205
222
|
This path is deterministic: it names the predecessor by id rather than
|
|
206
223
|
guessing, so multi-window scenarios where "most recently updated session"
|
|
@@ -216,8 +233,10 @@ typed /tl: Session A → /tl → (new chat / restart) → Session B (consu
|
|
|
216
233
|
Since Claude Code 2.1.128, the SessionStart hook receives `source='clear'`
|
|
217
234
|
reliably after `/clear`. When no baton is present (for example because the
|
|
218
235
|
`/clear` was triggered by the VSCode extension's menu and never reached
|
|
219
|
-
`UserPromptSubmit`), Throughline
|
|
220
|
-
|
|
236
|
+
`UserPromptSubmit`), Throughline resolves the most recent unmerged session
|
|
237
|
+
for the same project **at `SessionStart` time** (freezing that choice, and
|
|
238
|
+
skipping candidates that have no transcript — i.e. ghosts) and performs the
|
|
239
|
+
merge + injection at the session's first user prompt.
|
|
221
240
|
|
|
222
241
|
Set `THROUGHLINE_DISABLE_AUTO_HANDOFF=1` in your environment to opt out of
|
|
223
242
|
this fallback. **The env var only affects the fallback**; typed `/clear` and
|
|
@@ -226,15 +245,23 @@ this fallback. **The env var only affects the fallback**; typed `/clear` and
|
|
|
226
245
|
|
|
227
246
|
### What gets injected
|
|
228
247
|
|
|
229
|
-
Both paths inject the **same** curated memory:
|
|
248
|
+
Both paths inject the **same** curated memory (push/pull design, ADR 0016):
|
|
230
249
|
|
|
231
250
|
- A **"現在地 (latest exchange)"** anchor (added in v0.4.12) re-surfaces the
|
|
232
251
|
most recent user directive and the most recent assistant turn directly under
|
|
233
252
|
the header, each truncated to 600 characters
|
|
234
|
-
-
|
|
235
|
-
|
|
253
|
+
- A **pull guidance section** (always present) with ready-to-run
|
|
254
|
+
`throughline recall` commands — session id, an ISO-ms boundary, and turn
|
|
255
|
+
counts are baked in at injection time
|
|
256
|
+
- L2 verbatim: as many of the most recent turns as fit the ~9,500-char
|
|
257
|
+
injection budget, packed whole-turn (typically 7–8 turns; more for light
|
|
258
|
+
conversations). **L1 summaries are not injected** — the rest of the
|
|
259
|
+
20-turn window is retrieved verbatim via `throughline recall --l2`, and
|
|
260
|
+
everything older via `throughline recall --l1` (summarized turns show
|
|
261
|
+
their L1 line; unsummarized ones are listed explicitly with a
|
|
262
|
+
`throughline detail` pointer)
|
|
236
263
|
- L3 references (`throughline detail <time>` retrieval commands, attached
|
|
237
|
-
inline to each
|
|
264
|
+
inline to each L2 row; bodies stay in SQLite)
|
|
238
265
|
|
|
239
266
|
The injection is reframed as **"resuming an interrupted task"** rather than
|
|
240
267
|
"reading past logs". The L2 verbatim already contains the last assistant
|
|
@@ -367,7 +394,8 @@ turns are `0`, there is no current trim saving under the active keep-recent
|
|
|
367
394
|
setting.
|
|
368
395
|
|
|
369
396
|
Claude-side rewind UI itself is not driven by Throughline. The auto-handoff
|
|
370
|
-
flow is `/clear` → new
|
|
397
|
+
flow is `/clear` → new session → automatic injection of curated memory at the
|
|
398
|
+
session's first user prompt.
|
|
371
399
|
Throughline does not invoke `/rewind` or any Claude Code internal command.
|
|
372
400
|
|
|
373
401
|
Codex-primary setup has an installed Stop hook after global
|
|
@@ -643,7 +671,7 @@ Example output:
|
|
|
643
671
|
does not delay Claude's reply from reaching you. L1 Haiku summarization
|
|
644
672
|
(`claude -p` subprocess + inference, seconds to tens of seconds) would
|
|
645
673
|
otherwise stall the user-facing response of every turn; since L1 is only
|
|
646
|
-
needed for the *next* session's
|
|
674
|
+
needed for the *next* session's injection, there is no reason
|
|
647
675
|
to block the current turn on it. Existing installs need
|
|
648
676
|
`throughline uninstall && throughline install` to promote the flag (the
|
|
649
677
|
dedup logic skips entries that match by command string).
|
|
@@ -716,13 +744,15 @@ entry to the `tasks` array yourself:
|
|
|
716
744
|
|
|
717
745
|
## Commands
|
|
718
746
|
|
|
719
|
-
**v0.6.3
|
|
747
|
+
**v0.6.3 は2026-07-14に公開済みです。** factory diagnostics は Codex の managed
|
|
720
748
|
`UserPromptSubmit` / `PostToolUse` / `Stop` hooks がすべて canonical `ready` の場合に
|
|
721
749
|
Codex readiness を `ready` と返す。Claude connector は未検査のまま明示的に
|
|
722
750
|
`unverified` とし、Codex-only overall を妨げない。local runtime error
|
|
723
751
|
aggregate は collection が既定OFFで、canonical dotagents config の
|
|
724
752
|
`collection.enabled: true` を明示した場合だけ収集します。いずれも network I/O は
|
|
725
|
-
|
|
753
|
+
行いません。`throughline@0.6.3`、tag / GitHub Release、公開 CI run
|
|
754
|
+
`29284655280`(9/9 green)を確認済みです。npm registry artifact の shasum は
|
|
755
|
+
`4f3fcd2598a75f026358dae7f3eb3165242b580b` です。
|
|
726
756
|
|
|
727
757
|
| Command | What it does |
|
|
728
758
|
| ---------------------------------------------- | ------------------------------------------------------------ |
|
|
@@ -732,6 +762,9 @@ aggregate は collection が既定OFFで、canonical dotagents config の
|
|
|
732
762
|
| `throughline monitor [--all] [--session <id>]` | Run the multi-session token monitor |
|
|
733
763
|
| `throughline monitor --diag` | Dump TTY/columns/env diagnostics (for debugging monitor render bugs) |
|
|
734
764
|
| `throughline detail <time>` | Retrieve L2 body text and L3 tool I/O for a turn (see below) |
|
|
765
|
+
| `throughline recall --l2\|--l1 --session <id> --before <ISO> ...` | Pull older memory referenced by the injection's guidance section (read-only; the exact command is baked into each injection) |
|
|
766
|
+
| `throughline observer-read --project <absolute-directory> --json` | Read one completed-turn Observer page through the JSON-only public boundary |
|
|
767
|
+
| `throughline observer-wait --project <absolute-directory> --after-cursor <opaque> [--timeout-seconds 3600] --json` | Wait up to 3600 seconds for a completed-turn Observer cursor change |
|
|
735
768
|
| `throughline doctor` | Check Node version, hook registration, DB writability, PATH |
|
|
736
769
|
| `throughline doctor --session <id-prefix>` | Diagnose a specific session — detect state/transcript drift, idle vs. stuck |
|
|
737
770
|
| `throughline doctor --trim --host claude\|codex` | Diagnose trim host boundaries, manual procedure, and Codex host primitive blockage |
|
|
@@ -775,8 +808,9 @@ Slash commands (invoked by the user in Claude Code):
|
|
|
775
808
|
| `/sc-detail <time>` | Retrieve L2 body text and L3 tool I/O for a past turn |
|
|
776
809
|
|
|
777
810
|
> Since v0.4.1, both `/clear` and `/tl` typed in the prompt write a baton
|
|
778
|
-
> identifying the current session, so the next
|
|
779
|
-
> inherits that exact predecessor
|
|
811
|
+
> identifying the current session, so the next new session deterministically
|
|
812
|
+
> inherits that exact predecessor (merge + injection happen at that session's
|
|
813
|
+
> first user prompt — see ADR 0014). The `source='clear'` auto path remains as a
|
|
780
814
|
> fallback for `/clear` triggered outside `UserPromptSubmit` (for example via
|
|
781
815
|
> the VSCode extension menu); `THROUGHLINE_DISABLE_AUTO_HANDOFF=1` only opts
|
|
782
816
|
> out of that fallback.
|
|
@@ -785,6 +819,29 @@ Hook subcommands (invoked by Claude Code, not by humans):
|
|
|
785
819
|
`session-start` (SessionStart), `process-turn` (Stop),
|
|
786
820
|
`prompt-submit` (UserPromptSubmit — detects `/tl` and `/clear` and writes a baton).
|
|
787
821
|
|
|
822
|
+
### Observer completed-turn feed (development)
|
|
823
|
+
|
|
824
|
+
`observer-read` and `observer-wait` are JSON-only, read-only CLI boundaries for
|
|
825
|
+
the separate Observer product; Throughline does not add an MCP server or grant
|
|
826
|
+
Observer access to its DB, WAL, or rollout files. Pass an existing absolute
|
|
827
|
+
project directory. The returned `throughline.observer_cursor.v1` cursor is
|
|
828
|
+
opaque and bounded: callers may store and return it, but must not decode or
|
|
829
|
+
modify it.
|
|
830
|
+
|
|
831
|
+
`observer-read` returns a completed-only `snapshot`, `delta`,
|
|
832
|
+
`thread_switched`, or `host_switched` page. A stale or invalid cursor returns
|
|
833
|
+
`resync_required`; a completed source whose DB pair projection is not yet fresh
|
|
834
|
+
returns `projection_pending` without bodies. Pagination is bound to the exact
|
|
835
|
+
project, after cursor, and fixed through cursor, so a newly completed turn is
|
|
836
|
+
collected by the next wait instead of being mixed into an in-progress page.
|
|
837
|
+
|
|
838
|
+
`observer-wait` returns one of four successful states: `changed`, `timeout`,
|
|
839
|
+
`resync_required`, or `ambiguous_parent`. `timeout` preserves the input cursor.
|
|
840
|
+
The default and maximum `--timeout-seconds` value is 3600. Claude completion is
|
|
841
|
+
derived from Throughline's private Stop receipt; Codex completion is derived
|
|
842
|
+
only from that host's rollout `task_complete`. The CLI never recommends DB
|
|
843
|
+
polling as a fallback.
|
|
844
|
+
|
|
788
845
|
### `throughline detail` — for AI, not humans
|
|
789
846
|
|
|
790
847
|
`throughline detail` is the escape hatch Claude itself uses to pull archived
|
|
@@ -862,7 +919,8 @@ Schema v8:
|
|
|
862
919
|
- `skeletons` — L1 one-liners, keyed by `(session_id, origin_session_id, turn, role)`
|
|
863
920
|
- `bodies` — L2 verbatim text (user + assistant), same key shape
|
|
864
921
|
- `details` — L3 records with `kind` column (`tool_input` / `tool_output` / `system` / `image` / `thinking`) and `source_id` for idempotent re-processing
|
|
865
|
-
- `handoff_batons` — one row per `project_path`, with `session_id` and `created_at`. Written by the `UserPromptSubmit` hook when the user types `/tl` or `/clear`. Consumed and deleted
|
|
922
|
+
- `handoff_batons` — one row per `project_path`, with `session_id` and `created_at`. Written by the `UserPromptSubmit` hook when the user types `/tl` or `/clear`. Consumed and deleted at the next new session's **first user prompt**, if that session was born within the 1-hour TTL. (v8 dropped the `memo_text` column when memo was retired in v0.4.0.)
|
|
923
|
+
- `pending_handoffs` — one row per newborn session (`session_id` PK, `project_path`, `source`, `auto_predecessor_id`, `created_at`). Registered by `SessionStart`, consumed exactly once by the session's first `UserPromptSubmit`. Rows belonging to ghost sessions are never consumed and stay behind harmlessly (a few hundred bytes each). Added in v9 (ADR 0014).
|
|
866
924
|
- `injection_log` — audit trail of injection events
|
|
867
925
|
|
|
868
926
|
All memory tables carry an `origin_session_id` so rebonded rows keep their
|
|
@@ -1014,7 +1072,7 @@ unchanged here.
|
|
|
1014
1072
|
|
|
1015
1073
|
**Database got corrupted / want a clean slate**
|
|
1016
1074
|
Delete `~/.throughline/throughline.db` (and the `-shm` / `-wal` companion files)
|
|
1017
|
-
and `~/.throughline/state/*.json`. A fresh database with schema
|
|
1075
|
+
and `~/.throughline/state/*.json`. A fresh database with schema v9 is created on
|
|
1018
1076
|
the next hook fire.
|
|
1019
1077
|
|
|
1020
1078
|
**New session didn't inherit memory from the previous one**
|
package/bin/throughline.mjs
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* throughline process-turn # Stop hook (Claude Code から呼ばれる)
|
|
10
10
|
* throughline session-start # SessionStart hook (Claude Code から呼ばれる)
|
|
11
11
|
* throughline detail <時刻> # L2+L3 詳細取得 (Claude が Bash 経由で呼ぶ想定)
|
|
12
|
+
* throughline recall --l2|--l1 # 注入案内から辿る pull 用 read-only 記憶取得
|
|
12
13
|
* throughline handoff-preview # Codex-facing throughline_handoff JSON preview
|
|
13
14
|
* throughline auditor-context --json # Read-only bounded auditor context JSON
|
|
14
15
|
* throughline factory-diagnostics --json # Native factory read-only readiness JSON
|
|
@@ -66,6 +67,11 @@ switch (cmd) {
|
|
|
66
67
|
case 'detail':
|
|
67
68
|
(await import('../src/sc-detail.mjs')).run(rest);
|
|
68
69
|
break;
|
|
70
|
+
case 'recall': {
|
|
71
|
+
const exitCode = (await import('../src/cli/recall.mjs')).run(rest);
|
|
72
|
+
process.exitCode = exitCode;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
69
75
|
case 'handoff-preview':
|
|
70
76
|
await (await import('../src/cli/handoff-preview.mjs')).run(rest);
|
|
71
77
|
break;
|
|
@@ -74,6 +80,16 @@ switch (cmd) {
|
|
|
74
80
|
if (exitCode !== 0) process.exitCode = exitCode;
|
|
75
81
|
break;
|
|
76
82
|
}
|
|
83
|
+
case 'observer-read': {
|
|
84
|
+
const exitCode = (await import('../src/cli/observer-read.mjs')).run(rest);
|
|
85
|
+
if (exitCode !== 0) process.exitCode = exitCode;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case 'observer-wait': {
|
|
89
|
+
const exitCode = await (await import('../src/cli/observer-wait.mjs')).run(rest);
|
|
90
|
+
if (exitCode !== 0) process.exitCode = exitCode;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
77
93
|
case 'factory-diagnostics': {
|
|
78
94
|
const exitCode = (await import('../src/cli/factory-diagnostics.mjs')).run(rest);
|
|
79
95
|
if (exitCode !== 0) process.exitCode = exitCode;
|
|
@@ -184,6 +200,10 @@ Usage:
|
|
|
184
200
|
Read only bounded completed user/assistant context
|
|
185
201
|
for an auditor; requires either --host plus --transcript,
|
|
186
202
|
or explicit pair identity/hashes; always requires --json
|
|
203
|
+
throughline observer-read --project <absolute-directory> --json
|
|
204
|
+
Read one JSON-only completed-turn Observer page
|
|
205
|
+
throughline observer-wait --project <absolute-directory> --after-cursor <opaque> --json
|
|
206
|
+
Wait for a completed-turn Observer cursor change
|
|
187
207
|
throughline factory-diagnostics --json
|
|
188
208
|
Read-only native factory readiness JSON. Never emits
|
|
189
209
|
session/prompt bodies, secrets, absolute paths, or raw state
|
package/docs/00_overview.md
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
| [10_transcript_injection_plan.md](10_transcript_injection_plan.md) | transcript injection 検証計画と v0.5 実機結果 |
|
|
19
19
|
| [11_codex_monitor_implementation_plan.md](11_codex_monitor_implementation_plan.md) | Codex monitor 対応の実装記録 |
|
|
20
20
|
| [13_native_factory_diagnostics_plan.md](13_native_factory_diagnostics_plan.md) | native factory read-only readiness 診断の実装記録 |
|
|
21
|
+
| [14_observer_completed_turn_feed_plan.md](14_observer_completed_turn_feed_plan.md) | Observer向けcompleted-only read / wait CLIのactive計画。CLI・opaque cursor・pagination・最大3600秒waitは実装済み、公開/full regression gateは継続中 |
|
|
21
22
|
| [BUGHUB_RUNTIME_ERROR_STORE_PLAN.md](BUGHUB_RUNTIME_ERROR_STORE_PLAN.md) | local runtime error aggregate store の契約と実装 TODO |
|
|
22
23
|
|
|
23
24
|
## Supporting Records
|
|
@@ -29,6 +30,17 @@
|
|
|
29
30
|
| [archive/](archive/) | 破棄または履歴扱いの旧設計 |
|
|
30
31
|
| [../rag/INDEX.md](../rag/INDEX.md) | 外部仕様・調査の再利用棚 |
|
|
31
32
|
|
|
33
|
+
現行ADR:
|
|
34
|
+
|
|
35
|
+
- [ADR 0001](adr/0001-claude-primary-codex-adapter.md): Claude primaryを維持し、Codexをadapterとして追加する。
|
|
36
|
+
- [ADR 0002](adr/0002-observer-claude-completion-receipt.md): Claude completed turnはThroughline所有のStop receiptで固定する。
|
|
37
|
+
- [ADR 0003](adr/0003-observer-completed-chain-cursor.md): Observer cursorをhost固有のcompleted pair chainとprefix検証へ束縛する。
|
|
38
|
+
|
|
39
|
+
Observerの公開境界は`throughline observer-read`/`throughline observer-wait`のJSON-only CLIである。
|
|
40
|
+
ThroughlineはClaude Stop receiptとCodex rolloutの`task_complete`だけからcompleted cursorを構築し、
|
|
41
|
+
ObserverがDB、WAL、rolloutを直接監視するfallbackは持たない。waitは最大3600秒で、`changed`、`timeout`、
|
|
42
|
+
`resync_required`、`ambiguous_parent`を返す。
|
|
43
|
+
|
|
32
44
|
## Entrypoints
|
|
33
45
|
|
|
34
46
|
- [../CLAUDE.md](../CLAUDE.md): AI 作業者向けの正本。
|
|
@@ -17,6 +17,14 @@ A 案 (= /clear で自動引継ぎ + /tl は逃げ道として残す + /tl-trim
|
|
|
17
17
|
> `THROUGHLINE_DISABLE_AUTO_HANDOFF=1` は **fallback path のみに作用** する
|
|
18
18
|
> ようになった (typed `/clear` / `/tl` には効かない)。
|
|
19
19
|
|
|
20
|
+
> **2026-07-18 (ADR 0016) update**: 注入の中身を push/pull 二段に再設計した。
|
|
21
|
+
> push (9,500 字) はヘッダ + 現在地アンカー + 案内セクション + **L2 をターン原子で
|
|
22
|
+
> 入るだけ全文**(L1 は注入しない)。窓 20 ターンの残りは `throughline recall --l2`、
|
|
23
|
+
> それより古い全ターンは `recall --l1`(要約 or 未要約明示)で pull する。範囲・境界
|
|
24
|
+
> (ISO ms)・件数・session は注入時に案内コマンドへ焼き込み、recall 側は窓を再計算
|
|
25
|
+
> しない。正典は [ADR 0016](adr/0016-push-pull-recall-injection.md)。本書内の
|
|
26
|
+
> 「L1 + L2 を注入する」旧記述はこの update で読み替えること。
|
|
27
|
+
|
|
20
28
|
---
|
|
21
29
|
|
|
22
30
|
## 1. 確定した事実 (実機検証済み)
|
|
@@ -67,33 +75,53 @@ source: [code.claude.com/docs/en/hooks](https://code.claude.com/docs/en/hooks)
|
|
|
67
75
|
|
|
68
76
|
## 2. 採用する理想設計
|
|
69
77
|
|
|
70
|
-
### 2.1 引継ぎ発火条件 (2
|
|
78
|
+
### 2.1 引継ぎ発火条件 (2 経路 × 二相、ADR 0014 で二相化)
|
|
71
79
|
|
|
72
80
|
| 経路 | 条件 | 起動 |
|
|
73
81
|
|---|---|---|
|
|
74
|
-
| **baton path (primary)** | `handoff_batons`
|
|
75
|
-
| **auto path (fallback)** | baton 不在 + `source='clear'` + env `THROUGHLINE_DISABLE_AUTO_HANDOFF` が `'1'` でない |
|
|
82
|
+
| **baton path (primary)** | `handoff_batons` テーブルに「セッション誕生時刻基準で TTL (1 時間) 内」の baton あり (= ユーザーが `/tl` または `/clear` を打った) | `source` 値関係なく確定的に引継ぎ |
|
|
83
|
+
| **auto path (fallback)** | baton 不在 + `source='clear'` + env `THROUGHLINE_DISABLE_AUTO_HANDOFF` が `'1'` でない | SessionStart 時点で凍結した前任 (transcript 実在フィルタ付き heuristic) へ引継ぎ |
|
|
84
|
+
|
|
85
|
+
**二相化の理由 (ADR 0014)**: Claude Code は同一 project に短時間で複数の SessionStart を
|
|
86
|
+
発火させることがあり、一部は transcript を生成しない幽霊になる。SessionStart 時点では
|
|
87
|
+
実体と幽霊を判別できない (本物の transcript も hook より数百 ms 遅れて作られる) ため、
|
|
88
|
+
merge・注入は「実体の証明」= 最初の UserPromptSubmit まで遅延する。
|
|
89
|
+
2026-07-17 に幽霊がバトンを先取りして実セッションが記憶ゼロで始まる incident が
|
|
90
|
+
同日 2 回発生した (詳細・実測は [ADR 0014](adr/0014-two-phase-handoff-ghost-baton.md))。
|
|
76
91
|
|
|
77
92
|
判定ロジック (擬似コード):
|
|
78
93
|
|
|
79
94
|
```
|
|
80
95
|
on UserPromptSubmit(prompt, session_id, project_path):
|
|
96
|
+
// ---- 第二相: 初回プロンプト = 実体の証明 ----
|
|
97
|
+
pending = consumePendingHandoff(session_id) // atomic、1 セッション 1 回
|
|
98
|
+
if pending:
|
|
99
|
+
baton = consumeBaton(project_path, bornAt = pending.created_at)
|
|
100
|
+
// age = bornAt - baton.created_at。0 ≤ age ≤ TTL のみ消費。
|
|
101
|
+
// age < 0 (自分の誕生後に書かれた baton) は本来の後継のため残置
|
|
102
|
+
if baton.sessionId:
|
|
103
|
+
merge + inject(budgeted_memory_from(baton.sessionId)) // baton path
|
|
104
|
+
elif pending.auto_predecessor_id:
|
|
105
|
+
merge + inject(budgeted_memory_from(pending.auto_predecessor_id)) // auto path
|
|
106
|
+
// ---- 従来のバトン書き込み (第二相の後) ----
|
|
81
107
|
if isBatonCommand(prompt) or isClearCommand(prompt):
|
|
82
|
-
writeBaton(project_path, session_id, now)
|
|
108
|
+
writeBaton(project_path, session_id, now)
|
|
83
109
|
|
|
84
110
|
on SessionStart(source, session_id, project_path):
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
inject(curated_memory_from(baton.sessionId)) // baton path (primary, env 関係なく発火)
|
|
88
|
-
return
|
|
111
|
+
// ---- 第一相: intent 登録のみ。merge も注入もしない ----
|
|
112
|
+
auto_predecessor = null
|
|
89
113
|
if source == 'clear' and env.THROUGHLINE_DISABLE_AUTO_HANDOFF != '1':
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// 何もしない
|
|
114
|
+
auto_predecessor = findLatestClaudePredecessor(project_path, session_id)
|
|
115
|
+
// transcript 実在フィルタ付き: 幽霊 twin (transcript 無し) を前任に選ばない
|
|
116
|
+
registerPendingHandoff(session_id, project_path, source, auto_predecessor)
|
|
94
117
|
```
|
|
95
118
|
|
|
96
|
-
|
|
119
|
+
baton 消費が auto 判定より先発なので「両方同時成立」は構造上発生しない。typed `/clear` も
|
|
120
|
+
UserPromptSubmit hook で baton を書くため、通常はほぼ常に baton path が走る。auto path は
|
|
121
|
+
VSCode 拡張のメニュー由来 `/clear` のように UserPromptSubmit に届かない経路のためのフォールバック。
|
|
122
|
+
幽霊セッションはプロンプトを発火しないため第二相に到達できず、pending 行 (数百バイト) が
|
|
123
|
+
無害に残るだけになる。TTL ベースの pending GC は置かない (長時間 idle 後の初回プロンプトから
|
|
124
|
+
引継ぎを silent に奪う fallback になるため)。
|
|
97
125
|
|
|
98
126
|
### 2.2 注入内容: 現在地アンカー + L1 + L2 + L3 refs (baton/auto どちらの経路でも同一)
|
|
99
127
|
|
|
@@ -105,6 +133,12 @@ on SessionStart(source, session_id, project_path):
|
|
|
105
133
|
- **L3 references** (= `throughline detail <時刻>` の取り出しコマンド一覧、各 L1/L2 行末尾の inline suffix として集約)
|
|
106
134
|
- Continuation Instruction (= 「これは過去ログではなく現在進行中の作業」と明示)
|
|
107
135
|
|
|
136
|
+
**注入予算 (ADR 0014)**: hook stdout は約 10,000 字超で `<persisted-output>`
|
|
137
|
+
(ファイルパス + 先頭 2KB preview) に file 化され、モデル可視が先頭 2KB に劣化する
|
|
138
|
+
(実測: 9,501 字 inline 通過 / 15,286 字 file 化。v2.1.195 以降の 10k 超注入 12/12 が劣化)。
|
|
139
|
+
注入は `buildBudgetedResumeContext` (上限 9,500 字) で行い、ヘッダ + アンカーは常に全文、
|
|
140
|
+
L1 → L2 の順に新しい側から予算まで詰める。省略行数は注入文内と decision log に明示する。
|
|
141
|
+
|
|
108
142
|
含めない (= 削除):
|
|
109
143
|
- 中断直前の in-flight memo (memo セクション)
|
|
110
144
|
- 中断直前の thinking (extended thinking セクション)
|
|
@@ -139,6 +139,7 @@ schema v4 で PostToolUse (`capture-tool`) は廃止、L2/L3 は Stop 内で一
|
|
|
139
139
|
| **npm 公開 (v0.4.7): Codex monitor direct discovery + 80% auto-refresh** | 2026-05-09 Codex Stop hook auto-refresh の verified usage threshold を 90% から 80% に変更し、Codex native auto-compact より先に Throughline DB memory refresh を試行する。estimate usage / estimated context window では mutation しない。`throughline monitor` は `~/.throughline/state` に加えて `~/.codex/sessions/**/rollout-*.jsonl` を直接 discovery し、Throughline state が未生成の現在 Codex thread も表示する。既存 state がある場合は state の usage snapshot を保持しつつ discovered rollout path / mtime を合流する。Codex 表示 ID は `codex:01` ではなく raw thread id 先頭 8 桁 (`019e085c`) にした。Codex open turn の transient `output_tokens` は token count に overlay するが、モデル欄の `live+<tokens>` marker は表示しない |
|
|
140
140
|
| **npm 公開 (v0.6.1): Spotter auditor context projection** | `throughline auditor-context`を追加。exact session/projectと最新完了L2 pairのorigin/turn/hash freshnessを検査し、fresh時だけbounded user/assistant本文をversioned JSONで返す。DBはread-onlyで開き、作成・migration・WAL書き込みをしない。Spotter側はproject opt-inでのみ利用する。580 tests、CI 6/6、pack scan、registry global install / doctorを通過し、npm / tag / GitHub Releaseを2026-07-13に公開した |
|
|
141
141
|
| **npm 公開 (v0.6.2): factory diagnostics + local runtime error store** | `throughline factory-diagnostics --json` と `throughline runtime-errors ... --json` を追加。collection は canonical dotagents config の `collection.enabled: true` が明示された時だけ有効で既定OFF、store は network I/O を行わない。公開commit `e6ce6e3`、CI `29238704750`、npm `latest`、tag / GitHub Release、registry由来隔離installと診断snapshotを2026-07-13に確認した |
|
|
142
|
+
| **npm 公開 (v0.6.3): Codex diagnostics ready 集約 + Windows bounded observer** | 3 managed Codex hooks が canonical `ready` のとき hook summary / Codex connector を `ready` とし、未検査 Claude connector は明示 `unverified` のまま Codex-only overall から除外する。Windows runtime-error observer は、既存 state の使用前検証と新規 file の ACL 付与・read-back を維持しつつ、同一 mutation 内の重複 PowerShell ACL 検証を除いた。公開commit `fc83ddf`、CI `29284655280`(9/9 green)、npm `throughline@0.6.3`、tag / GitHub Release、npm shasum `4f3fcd2598a75f026358dae7f3eb3165242b580b` を2026-07-14に確認した |
|
|
142
143
|
| **未リリース: Codex current-session 75% trigger** | Codex 自動発火を token-monitor に依存させず、global install が Codex `UserPromptSubmit` / `PostToolUse` hooks も登録する。hook は当該 Codex session の rollout `token_count` を直接読み、verified 75% 以上なら同じ user turn または tool loop 継続前に `$throughline` workflow 実行指示を `additionalContext` で注入する。Stop hook の guarded auto-refresh は残す。`~/.codex/config.toml` は旧 `codex_hooks = true` に加えて現行 `hooks = true` も有効化する |
|
|
143
144
|
| **グローバル E2E 検証** | 2026-04-17 別ディレクトリから `throughline doctor` 全緑を確認 |
|
|
144
145
|
|