throughline 0.6.2 → 0.7.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +79 -4
  2. package/README.md +77 -25
  3. package/bin/throughline.mjs +14 -0
  4. package/docs/00_overview.md +12 -0
  5. package/docs/02_clear_auto_handoff_plan.md +39 -13
  6. package/docs/04_public_release_plan.md +2 -1
  7. package/docs/13_native_factory_diagnostics_plan.md +4 -2
  8. package/docs/14_observer_completed_turn_feed_plan.md +290 -0
  9. package/docs/BUGHUB_RUNTIME_ERROR_STORE_PLAN.md +31 -4
  10. package/docs/adr/0002-observer-claude-completion-receipt.md +42 -0
  11. package/docs/adr/0003-observer-completed-chain-cursor.md +34 -0
  12. package/docs/adr/0004-observer-db-pair-projection.md +71 -0
  13. package/docs/adr/0005-observer-read-pagination.md +51 -0
  14. package/docs/adr/0006-observer-page-offset-proof.md +33 -0
  15. package/docs/adr/0007-observer-read-cli-contract.md +61 -0
  16. package/docs/adr/0008-observer-wait-deadline-cancel.md +81 -0
  17. package/docs/adr/0009-observer-integration-regression-and-docs.md +37 -0
  18. package/docs/adr/0010-observer-o1-phase-acceptance.md +49 -0
  19. package/docs/adr/0011-observer-o1-control-lane-reconciliation.md +34 -0
  20. package/docs/adr/0012-claude-stop-transcript-flush-barrier.md +32 -0
  21. package/docs/adr/0013-observer-read-busy-writer-gate.md +46 -0
  22. package/docs/adr/0014-two-phase-handoff-ghost-baton.md +112 -0
  23. package/docs/adr/0015-l1-summarizer-model-effort-ratio.md +81 -0
  24. package/package.json +1 -1
  25. package/rag/01-hooks/hook-stdout-10k-persisted-output.md +48 -0
  26. package/rag/INDEX.md +4 -0
  27. package/src/auditor-context.mjs +92 -11
  28. package/src/auditor-context.test.mjs +116 -1
  29. package/src/baton.mjs +27 -7
  30. package/src/baton.test.mjs +44 -0
  31. package/src/body-digest.mjs +9 -0
  32. package/src/cli/auditor-context.test.mjs +1 -1
  33. package/src/cli/factory-diagnostics.mjs +5 -2
  34. package/src/cli/factory-diagnostics.test.mjs +31 -2
  35. package/src/cli/observer-read.mjs +73 -0
  36. package/src/cli/observer-read.test.mjs +93 -0
  37. package/src/cli/observer-wait.mjs +123 -0
  38. package/src/cli/observer-wait.test.mjs +167 -0
  39. package/src/codex-rollout-memory.mjs +13 -0
  40. package/src/codex-rollout-memory.test.mjs +27 -0
  41. package/src/codex-thread-index.mjs +1 -1
  42. package/src/codex-thread-index.test.mjs +18 -0
  43. package/src/completed-turn-receipts.mjs +373 -0
  44. package/src/completed-turn-receipts.test.mjs +186 -0
  45. package/src/db-schema.test.mjs +9 -2
  46. package/src/db.mjs +20 -1
  47. package/src/decision-log.mjs +24 -0
  48. package/src/factory-diagnostics.mjs +0 -1
  49. package/src/factory-diagnostics.test.mjs +18 -0
  50. package/src/haiku-summarizer.mjs +93 -16
  51. package/src/haiku-summarizer.test.mjs +118 -9
  52. package/src/handoff-executor.mjs +159 -0
  53. package/src/hook-entrypoints.test.mjs +192 -12
  54. package/src/observer-codex-projection.test.mjs +49 -0
  55. package/src/observer-turn-feed.mjs +392 -0
  56. package/src/observer-turn-feed.test.mjs +339 -0
  57. package/src/observer-turn-wait.mjs +102 -0
  58. package/src/observer-turn-wait.test.mjs +122 -0
  59. package/src/pending-handoff.mjs +96 -0
  60. package/src/pending-handoff.test.mjs +107 -0
  61. package/src/prompt-submit.mjs +46 -1
  62. package/src/resume-context.mjs +226 -62
  63. package/src/resume-context.test.mjs +134 -1
  64. package/src/runtime-error-store.mjs +74 -32
  65. package/src/runtime-error-store.test.mjs +51 -3
  66. package/src/session-start.mjs +70 -233
  67. package/src/transcript-reader.mjs +32 -0
  68. package/src/turn-backfill.mjs +3 -2
  69. package/src/turn-backfill.test.mjs +10 -4
  70. package/src/turn-processor.mjs +90 -1
  71. package/src/turn-processor.test.mjs +141 -0
  72. package/src/windows-acl-test-helper.mjs +29 -0
package/CHANGELOG.md CHANGED
@@ -10,7 +10,82 @@ shipped to npm but were not individually tagged on GitHub.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
- ## [0.6.2] — release candidate (pending)
13
+ ## [0.7.0] — 2026-07-17
14
+
15
+ ### Changed (breaking behavior)
16
+
17
+ - **Two-phase handoff (ADR 0014).** Claude Code can fire multiple
18
+ `SessionStart` hooks for the same project within a few hundred ms, and some
19
+ of them never materialize into a real session (no transcript is ever
20
+ written). Such a "ghost" could consume the handoff baton first and silently
21
+ swallow the predecessor's memory while the real session started empty
22
+ (observed twice on 2026-07-17; upstream report:
23
+ anthropics/claude-code#78455). `SessionStart` now only registers a pending
24
+ intent (new schema v9 table `pending_handoffs`); the merge and the context
25
+ injection happen at the session's **first `UserPromptSubmit`** — a prompt is
26
+ proof the session is real, and a ghost never submits one. Baton eligibility
27
+ is measured against the consuming session's birth time (`0 <= birth −
28
+ baton_write <= 1h TTL`); a baton written after the session was born is left
29
+ in place for its true successor instead of being stolen by a running
30
+ session. The auto path (`source='clear'`) freezes its predecessor choice at
31
+ `SessionStart` and skips transcript-less (ghost) candidates.
32
+ - **Injection is budgeted to 9,500 chars (ADR 0014).** Hook stdout larger than
33
+ ~10,000 chars is silently persisted to a file by Claude Code and the model
34
+ only sees the first 2KB (measured: 9,501 chars pass inline, 15,286 get
35
+ persisted; every >10k injection since v2.1.195 was degraded this way).
36
+ The resume context now always fits inline: header + current-position anchor
37
+ are kept in full, then L1 and L2 fill newest-first. Dropped L2 rows are
38
+ announced inside the injection with their `[time role]` references so the
39
+ model can retrieve any of them via `throughline detail`.
40
+ - **L1 summarization backend and ratio are configurable (ADR 0015).** The
41
+ Claude-primary backend order is now `codex-sidecar` (when configured) →
42
+ Codex CLI (default `gpt-5.6-luna`, reasoning effort `low`, chosen by a
43
+ measured 83-run evaluation) → Claude Haiku → raw L2, with every fallback
44
+ step recording its reason. The compression target is a ratio (default 0.2 =
45
+ 1/5 of the source turn). Overrides: `THROUGHLINE_L1_MODEL`,
46
+ `THROUGHLINE_L1_EFFORT`, `THROUGHLINE_L1_RATIO` (invalid ratio values are an
47
+ explicit error, not a silent default). The Codex CLI invocation now passes
48
+ an explicit `-m`; previously `--ignore-user-config` silently ran the CLI's
49
+ built-in default model.
50
+
51
+ ### Added
52
+
53
+ - Schema v9: `pending_handoffs` table (session_id PK, project_path, source,
54
+ auto_predecessor_id, created_at). Rows belonging to ghost sessions are never
55
+ consumed and stay behind harmlessly.
56
+ - The inheritance decision log now records both phases
57
+ (`phase: 'session-start' | 'prompt-submit'`) including injection size and
58
+ dropped-row counts.
59
+ - First npm release to include the JSON-only completed-turn Observer CLI
60
+ boundary: `throughline observer-read` (opaque-cursor pages) and
61
+ `throughline observer-wait` (bounded wait up to 3600s). The completed feed
62
+ uses Throughline-owned Claude Stop receipts and Codex rollout
63
+ `task_complete` records; stale DB projection is reported as
64
+ `projection_pending` without bodies (ADR 0002–0013).
65
+
66
+ ### Fixed
67
+
68
+ - Claude Stop waits for the transcript flush barrier before backfilling
69
+ (ADR 0012), and Observer reads wait out transient SQLite writer locks with a
70
+ bounded busy wait instead of failing hard (ADR 0013).
71
+
72
+ ## [0.6.3] — 2026-07-14
73
+
74
+ ### Fixed
75
+
76
+ - `throughline factory-diagnostics --json` now reports the Codex hook summary
77
+ as `ready` when all three canonical managed hooks are ready. The Codex-only
78
+ overall aggregate no longer treats the separately exposed, uninspected
79
+ Claude connector as a blocking `unverified` state. The Claude connector
80
+ remains explicitly `unverified`; diagnostic output remains read-only and
81
+ privacy-safe.
82
+ - Windows runtime-error mutations no longer repeat identical PowerShell ACL
83
+ verification inside one bounded observation. Existing lock/store files are
84
+ still verified before use, new temporary files receive an exact
85
+ current-SID-only ACL before atomic replacement, and the five-second hook
86
+ observer deadline is unchanged.
87
+
88
+ ## [0.6.2] — 2026-07-13
14
89
 
15
90
  ### Added
16
91
 
@@ -22,9 +97,9 @@ shipped to npm but were not individually tagged on GitHub.
22
97
  owner-private atomic store with count, first/last seen, resolve/reopen,
23
98
  monotonic cursor/ack, retention that preserves unacknowledged records, and
24
99
  bounded `throughline runtime-errors ... --json` snapshot/diagnostics APIs.
25
- - Release candidate only: collection remains disabled by default and the local
26
- store sends no network traffic. Publication, tag, CI, and registry-install
27
- verification are pending.
100
+ - Collection remains disabled by default and the local store sends no network
101
+ traffic. Public commit `e6ce6e3`, CI `29238704750`, npm `latest`, tag / GitHub
102
+ Release, and a registry-derived isolated install were verified.
28
103
  - Raised the Node.js floor to 22.13, where `node:sqlite` is available without
29
104
  an experimental command-line flag; CI now exercises that exact minimum.
30
105
 
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 v8)
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 SessionStart<br/>injection"]
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. In the current Claude-primary path, Throughline uses **Claude Haiku 4.5**
156
- via a subprocess (`claude -p --model claude-haiku-4-5-*`), reusing your Claude
157
- Max login no API key required. When `codex-sidecar` is explicitly configured
158
- for the `summarize-l1` preset, Throughline can use that instead.
159
- For Codex-primary capture, the L1 backend is the Codex CLI; failures are explicit
160
- and do not fall back to Claude Haiku or raw L2.
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"] --> C{"baton<br/>present?"}
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/>findLatestClaudePredecessor"]
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 `SessionStart` (within the 1-hour TTL)
202
- consumes the baton and merges that exact predecessor's memory into the new
203
- session, regardless of the `source` value.
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 falls back to `findLatestClaudePredecessor`
220
- to pick the most recent unmerged session for the same project and merges it.
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
@@ -367,7 +386,8 @@ turns are `0`, there is no current trim saving under the active keep-recent
367
386
  setting.
368
387
 
369
388
  Claude-side rewind UI itself is not driven by Throughline. The auto-handoff
370
- flow is `/clear` → new SessionStart → automatic injection of curated memory.
389
+ flow is `/clear` → new session → automatic injection of curated memory at the
390
+ session's first user prompt.
371
391
  Throughline does not invoke `/rewind` or any Claude Code internal command.
372
392
 
373
393
  Codex-primary setup has an installed Stop hook after global
@@ -643,7 +663,7 @@ Example output:
643
663
  does not delay Claude's reply from reaching you. L1 Haiku summarization
644
664
  (`claude -p` subprocess + inference, seconds to tens of seconds) would
645
665
  otherwise stall the user-facing response of every turn; since L1 is only
646
- needed for the *next* session's SessionStart injection, there is no reason
666
+ needed for the *next* session's injection, there is no reason
647
667
  to block the current turn on it. Existing installs need
648
668
  `throughline uninstall && throughline install` to promote the flag (the
649
669
  dedup logic skips entries that match by command string).
@@ -716,10 +736,15 @@ entry to the `tasks` array yourself:
716
736
 
717
737
  ## Commands
718
738
 
719
- **v0.6.2release candidate(公開待ち)です。** factory diagnostics local
720
- runtime error aggregate collection が既定OFFで、canonical dotagents config の
739
+ **v0.6.32026-07-14に公開済みです。** factory diagnostics Codex の managed
740
+ `UserPromptSubmit` / `PostToolUse` / `Stop` hooks がすべて canonical `ready` の場合に
741
+ Codex readiness を `ready` と返す。Claude connector は未検査のまま明示的に
742
+ `unverified` とし、Codex-only overall を妨げない。local runtime error
743
+ aggregate は collection が既定OFFで、canonical dotagents config の
721
744
  `collection.enabled: true` を明示した場合だけ収集します。いずれも network I/O は
722
- 行いません。npm公開、tagCI、registry由来installの確認は未完了です。
745
+ 行いません。`throughline@0.6.3`、tag / GitHub Release、公開 CI run
746
+ `29284655280`(9/9 green)を確認済みです。npm registry artifact の shasum は
747
+ `4f3fcd2598a75f026358dae7f3eb3165242b580b` です。
723
748
 
724
749
  | Command | What it does |
725
750
  | ---------------------------------------------- | ------------------------------------------------------------ |
@@ -729,6 +754,8 @@ runtime error aggregate は collection が既定OFFで、canonical dotagents con
729
754
  | `throughline monitor [--all] [--session <id>]` | Run the multi-session token monitor |
730
755
  | `throughline monitor --diag` | Dump TTY/columns/env diagnostics (for debugging monitor render bugs) |
731
756
  | `throughline detail <time>` | Retrieve L2 body text and L3 tool I/O for a turn (see below) |
757
+ | `throughline observer-read --project <absolute-directory> --json` | Read one completed-turn Observer page through the JSON-only public boundary |
758
+ | `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 |
732
759
  | `throughline doctor` | Check Node version, hook registration, DB writability, PATH |
733
760
  | `throughline doctor --session <id-prefix>` | Diagnose a specific session — detect state/transcript drift, idle vs. stuck |
734
761
  | `throughline doctor --trim --host claude\|codex` | Diagnose trim host boundaries, manual procedure, and Codex host primitive blockage |
@@ -772,8 +799,9 @@ Slash commands (invoked by the user in Claude Code):
772
799
  | `/sc-detail <time>` | Retrieve L2 body text and L3 tool I/O for a past turn |
773
800
 
774
801
  > Since v0.4.1, both `/clear` and `/tl` typed in the prompt write a baton
775
- > identifying the current session, so the next `SessionStart` deterministically
776
- > inherits that exact predecessor. The `source='clear'` auto path remains as a
802
+ > identifying the current session, so the next new session deterministically
803
+ > inherits that exact predecessor (merge + injection happen at that session's
804
+ > first user prompt — see ADR 0014). The `source='clear'` auto path remains as a
777
805
  > fallback for `/clear` triggered outside `UserPromptSubmit` (for example via
778
806
  > the VSCode extension menu); `THROUGHLINE_DISABLE_AUTO_HANDOFF=1` only opts
779
807
  > out of that fallback.
@@ -782,6 +810,29 @@ Hook subcommands (invoked by Claude Code, not by humans):
782
810
  `session-start` (SessionStart), `process-turn` (Stop),
783
811
  `prompt-submit` (UserPromptSubmit — detects `/tl` and `/clear` and writes a baton).
784
812
 
813
+ ### Observer completed-turn feed (development)
814
+
815
+ `observer-read` and `observer-wait` are JSON-only, read-only CLI boundaries for
816
+ the separate Observer product; Throughline does not add an MCP server or grant
817
+ Observer access to its DB, WAL, or rollout files. Pass an existing absolute
818
+ project directory. The returned `throughline.observer_cursor.v1` cursor is
819
+ opaque and bounded: callers may store and return it, but must not decode or
820
+ modify it.
821
+
822
+ `observer-read` returns a completed-only `snapshot`, `delta`,
823
+ `thread_switched`, or `host_switched` page. A stale or invalid cursor returns
824
+ `resync_required`; a completed source whose DB pair projection is not yet fresh
825
+ returns `projection_pending` without bodies. Pagination is bound to the exact
826
+ project, after cursor, and fixed through cursor, so a newly completed turn is
827
+ collected by the next wait instead of being mixed into an in-progress page.
828
+
829
+ `observer-wait` returns one of four successful states: `changed`, `timeout`,
830
+ `resync_required`, or `ambiguous_parent`. `timeout` preserves the input cursor.
831
+ The default and maximum `--timeout-seconds` value is 3600. Claude completion is
832
+ derived from Throughline's private Stop receipt; Codex completion is derived
833
+ only from that host's rollout `task_complete`. The CLI never recommends DB
834
+ polling as a fallback.
835
+
785
836
  ### `throughline detail` — for AI, not humans
786
837
 
787
838
  `throughline detail` is the escape hatch Claude itself uses to pull archived
@@ -859,7 +910,8 @@ Schema v8:
859
910
  - `skeletons` — L1 one-liners, keyed by `(session_id, origin_session_id, turn, role)`
860
911
  - `bodies` — L2 verbatim text (user + assistant), same key shape
861
912
  - `details` — L3 records with `kind` column (`tool_input` / `tool_output` / `system` / `image` / `thinking`) and `source_id` for idempotent re-processing
862
- - `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 by the next `SessionStart` if within the 1-hour TTL. (v8 dropped the `memo_text` column when memo was retired in v0.4.0.)
913
+ - `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.)
914
+ - `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).
863
915
  - `injection_log` — audit trail of injection events
864
916
 
865
917
  All memory tables carry an `origin_session_id` so rebonded rows keep their
@@ -1011,7 +1063,7 @@ unchanged here.
1011
1063
 
1012
1064
  **Database got corrupted / want a clean slate**
1013
1065
  Delete `~/.throughline/throughline.db` (and the `-shm` / `-wal` companion files)
1014
- and `~/.throughline/state/*.json`. A fresh database with schema v8 is created on
1066
+ and `~/.throughline/state/*.json`. A fresh database with schema v9 is created on
1015
1067
  the next hook fire.
1016
1068
 
1017
1069
  **New session didn't inherit memory from the previous one**
@@ -74,6 +74,16 @@ switch (cmd) {
74
74
  if (exitCode !== 0) process.exitCode = exitCode;
75
75
  break;
76
76
  }
77
+ case 'observer-read': {
78
+ const exitCode = (await import('../src/cli/observer-read.mjs')).run(rest);
79
+ if (exitCode !== 0) process.exitCode = exitCode;
80
+ break;
81
+ }
82
+ case 'observer-wait': {
83
+ const exitCode = await (await import('../src/cli/observer-wait.mjs')).run(rest);
84
+ if (exitCode !== 0) process.exitCode = exitCode;
85
+ break;
86
+ }
77
87
  case 'factory-diagnostics': {
78
88
  const exitCode = (await import('../src/cli/factory-diagnostics.mjs')).run(rest);
79
89
  if (exitCode !== 0) process.exitCode = exitCode;
@@ -184,6 +194,10 @@ Usage:
184
194
  Read only bounded completed user/assistant context
185
195
  for an auditor; requires either --host plus --transcript,
186
196
  or explicit pair identity/hashes; always requires --json
197
+ throughline observer-read --project <absolute-directory> --json
198
+ Read one JSON-only completed-turn Observer page
199
+ throughline observer-wait --project <absolute-directory> --after-cursor <opaque> --json
200
+ Wait for a completed-turn Observer cursor change
187
201
  throughline factory-diagnostics --json
188
202
  Read-only native factory readiness JSON. Never emits
189
203
  session/prompt bodies, secrets, absolute paths, or raw state
@@ -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 作業者向けの正本。
@@ -67,33 +67,53 @@ source: [code.claude.com/docs/en/hooks](https://code.claude.com/docs/en/hooks)
67
67
 
68
68
  ## 2. 採用する理想設計
69
69
 
70
- ### 2.1 引継ぎ発火条件 (2 経路、v0.4.1 baton primary に変更)
70
+ ### 2.1 引継ぎ発火条件 (2 経路 × 二相、ADR 0014 で二相化)
71
71
 
72
72
  | 経路 | 条件 | 起動 |
73
73
  |---|---|---|
74
- | **baton path (primary)** | `handoff_batons` テーブルに TTL (1 時間) baton あり (= ユーザーが `/tl` または `/clear` を打った) | `source` 値関係なく確定的に引継ぎ |
75
- | **auto path (fallback)** | baton 不在 + `source='clear'` + env `THROUGHLINE_DISABLE_AUTO_HANDOFF` が `'1'` でない | `findLatestClaudePredecessor` heuristic で引継ぎ |
74
+ | **baton path (primary)** | `handoff_batons` テーブルに「セッション誕生時刻基準で TTL (1 時間) 内」の baton あり (= ユーザーが `/tl` または `/clear` を打った) | `source` 値関係なく確定的に引継ぎ |
75
+ | **auto path (fallback)** | baton 不在 + `source='clear'` + env `THROUGHLINE_DISABLE_AUTO_HANDOFF` が `'1'` でない | SessionStart 時点で凍結した前任 (transcript 実在フィルタ付き heuristic) へ引継ぎ |
76
+
77
+ **二相化の理由 (ADR 0014)**: Claude Code は同一 project に短時間で複数の SessionStart を
78
+ 発火させることがあり、一部は transcript を生成しない幽霊になる。SessionStart 時点では
79
+ 実体と幽霊を判別できない (本物の transcript も hook より数百 ms 遅れて作られる) ため、
80
+ merge・注入は「実体の証明」= 最初の UserPromptSubmit まで遅延する。
81
+ 2026-07-17 に幽霊がバトンを先取りして実セッションが記憶ゼロで始まる incident が
82
+ 同日 2 回発生した (詳細・実測は [ADR 0014](adr/0014-two-phase-handoff-ghost-baton.md))。
76
83
 
77
84
  判定ロジック (擬似コード):
78
85
 
79
86
  ```
80
87
  on UserPromptSubmit(prompt, session_id, project_path):
88
+ // ---- 第二相: 初回プロンプト = 実体の証明 ----
89
+ pending = consumePendingHandoff(session_id) // atomic、1 セッション 1 回
90
+ if pending:
91
+ baton = consumeBaton(project_path, bornAt = pending.created_at)
92
+ // age = bornAt - baton.created_at。0 ≤ age ≤ TTL のみ消費。
93
+ // age < 0 (自分の誕生後に書かれた baton) は本来の後継のため残置
94
+ if baton.sessionId:
95
+ merge + inject(budgeted_memory_from(baton.sessionId)) // baton path
96
+ elif pending.auto_predecessor_id:
97
+ merge + inject(budgeted_memory_from(pending.auto_predecessor_id)) // auto path
98
+ // ---- 従来のバトン書き込み (第二相の後) ----
81
99
  if isBatonCommand(prompt) or isClearCommand(prompt):
82
- writeBaton(project_path, session_id, now) // typed /clear / /tl が確定的に baton を書く
100
+ writeBaton(project_path, session_id, now)
83
101
 
84
102
  on SessionStart(source, session_id, project_path):
85
- baton = consumeBaton(project_path) // atomic SELECT + DELETE, TTL 超過は sessionId=null で返る
86
- if baton.sessionId:
87
- inject(curated_memory_from(baton.sessionId)) // baton path (primary, env 関係なく発火)
88
- return
103
+ // ---- 第一相: intent 登録のみ。merge も注入もしない ----
104
+ auto_predecessor = null
89
105
  if source == 'clear' and env.THROUGHLINE_DISABLE_AUTO_HANDOFF != '1':
90
- predecessor = findLatestClaudePredecessor(project_path, session_id)
91
- inject(curated_memory_from(predecessor)) // auto path (fallback)
92
- return
93
- // 何もしない
106
+ auto_predecessor = findLatestClaudePredecessor(project_path, session_id)
107
+ // transcript 実在フィルタ付き: 幽霊 twin (transcript 無し) を前任に選ばない
108
+ registerPendingHandoff(session_id, project_path, source, auto_predecessor)
94
109
  ```
95
110
 
96
- `consumeBaton` が先発なので「両方同時成立」は構造上発生しない (= baton ありなら baton 経路、無ければ source 判定)。typed `/clear` も UserPromptSubmit hook で baton を書くため、通常はほぼ常に baton path が走る。auto path は VSCode 拡張のメニュー由来 `/clear` のように UserPromptSubmit に届かない経路のためのフォールバック。
111
+ baton 消費が auto 判定より先発なので「両方同時成立」は構造上発生しない。typed `/clear` も
112
+ UserPromptSubmit hook で baton を書くため、通常はほぼ常に baton path が走る。auto path は
113
+ VSCode 拡張のメニュー由来 `/clear` のように UserPromptSubmit に届かない経路のためのフォールバック。
114
+ 幽霊セッションはプロンプトを発火しないため第二相に到達できず、pending 行 (数百バイト) が
115
+ 無害に残るだけになる。TTL ベースの pending GC は置かない (長時間 idle 後の初回プロンプトから
116
+ 引継ぎを silent に奪う fallback になるため)。
97
117
 
98
118
  ### 2.2 注入内容: 現在地アンカー + L1 + L2 + L3 refs (baton/auto どちらの経路でも同一)
99
119
 
@@ -105,6 +125,12 @@ on SessionStart(source, session_id, project_path):
105
125
  - **L3 references** (= `throughline detail <時刻>` の取り出しコマンド一覧、各 L1/L2 行末尾の inline suffix として集約)
106
126
  - Continuation Instruction (= 「これは過去ログではなく現在進行中の作業」と明示)
107
127
 
128
+ **注入予算 (ADR 0014)**: hook stdout は約 10,000 字超で `<persisted-output>`
129
+ (ファイルパス + 先頭 2KB preview) に file 化され、モデル可視が先頭 2KB に劣化する
130
+ (実測: 9,501 字 inline 通過 / 15,286 字 file 化。v2.1.195 以降の 10k 超注入 12/12 が劣化)。
131
+ 注入は `buildBudgetedResumeContext` (上限 9,500 字) で行い、ヘッダ + アンカーは常に全文、
132
+ L1 → L2 の順に新しい側から予算まで詰める。省略行数は注入文内と decision log に明示する。
133
+
108
134
  含めない (= 削除):
109
135
  - 中断直前の in-flight memo (memo セクション)
110
136
  - 中断直前の thinking (extended thinking セクション)
@@ -138,7 +138,8 @@ schema v4 で PostToolUse (`capture-tool`) は廃止、L2/L3 は Stop 内で一
138
138
  | **npm 公開 (v0.4.1): typed `/clear` も baton を書く + 2 経路の優先順位入れ替え** | 2026-05-09 `/clear` を UserPromptSubmit hook で検出した時点で当該セッションの `session_id` を `handoff_batons` に書き込み、次 SessionStart が確定的にそのセッションを引き継ぐ。これで multi-window で「最新更新セッション ≠ /clear したセッション」になるシナリオで `findLatestClaudePredecessor` heuristic が誤った前任を選ぶ問題を解消。2 経路の優先順位を **baton path = primary、auto path = fallback** に変更 (auto path は VSCode 拡張メニュー由来など UserPromptSubmit に届かない経路のフォールバック)。`THROUGHLINE_DISABLE_AUTO_HANDOFF=1` は fallback path のみに作用するようになった (typed `/clear` / `/tl` は env と無関係に発火する)。あわせて `.vscode/tasks.json` を git 追跡から外し (gitignore)、`ensureMonitorTaskFile` が hook 発火ごとに絶対パスを書き換える挙動による別環境での dirty diff を解消。`src/prompt-submit.test.mjs` を新設し、`isClearCommand` / `isBatonCommand` 判定 14 件と subprocess+DB 実体テスト 3 件を追加。詳細は [CHANGELOG.md](../CHANGELOG.md) |
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
- | **v0.6.2 release candidate(公開待ち): 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 を行わない。npm公開、tag、CI、registry由来installの確認はこの時点で未実施であり、公開済みとは扱わない |
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
 
@@ -23,8 +23,10 @@ session の本文、秘密、絶対パス、例外詳細を出力しない。
23
23
  検証する。handoff memoryは一致確認済みproject/threadだけを数える。
24
24
  - restore capabilityとlive smoke evidenceを分離する。未実行smokeは`unverified`だが、
25
25
  capability readinessを恒常的に失敗扱いにはしない。
26
- - Codex hookはcanonical shapeを検査するが、trust実火は機械検証不能なので`unverified`に
27
- 留める。Claude connectorはこの単位では未検査として明示`unverified`にする。
26
+ - Codex hook canonical shape を検査し、3 managed event がすべて `ready` なら hook
27
+ summary も `ready` とする。trust 実火はこの read-only projection の判定対象外であり、
28
+ Claude connector はこの単位では未検査として明示 `unverified` にする。ただし後者は
29
+ Codex-only overall を阻害しない。
28
30
 
29
31
  ## 実施 TODO
30
32