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.
Files changed (72) hide show
  1. package/CHANGELOG.md +101 -1
  2. package/README.md +86 -28
  3. package/bin/throughline.mjs +20 -0
  4. package/docs/00_overview.md +12 -0
  5. package/docs/02_clear_auto_handoff_plan.md +47 -13
  6. package/docs/04_public_release_plan.md +1 -0
  7. package/docs/14_observer_completed_turn_feed_plan.md +290 -0
  8. package/docs/BUGHUB_RUNTIME_ERROR_STORE_PLAN.md +9 -3
  9. package/docs/adr/0002-observer-claude-completion-receipt.md +42 -0
  10. package/docs/adr/0003-observer-completed-chain-cursor.md +34 -0
  11. package/docs/adr/0004-observer-db-pair-projection.md +71 -0
  12. package/docs/adr/0005-observer-read-pagination.md +51 -0
  13. package/docs/adr/0006-observer-page-offset-proof.md +33 -0
  14. package/docs/adr/0007-observer-read-cli-contract.md +61 -0
  15. package/docs/adr/0008-observer-wait-deadline-cancel.md +81 -0
  16. package/docs/adr/0009-observer-integration-regression-and-docs.md +37 -0
  17. package/docs/adr/0010-observer-o1-phase-acceptance.md +49 -0
  18. package/docs/adr/0011-observer-o1-control-lane-reconciliation.md +34 -0
  19. package/docs/adr/0012-claude-stop-transcript-flush-barrier.md +32 -0
  20. package/docs/adr/0013-observer-read-busy-writer-gate.md +46 -0
  21. package/docs/adr/0014-two-phase-handoff-ghost-baton.md +112 -0
  22. package/docs/adr/0015-l1-summarizer-model-effort-ratio.md +81 -0
  23. package/docs/adr/0016-push-pull-recall-injection.md +93 -0
  24. package/package.json +1 -1
  25. package/rag/01-hooks/hook-stdout-10k-persisted-output.md +65 -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 +1 -0
  34. package/src/cli/factory-diagnostics.test.mjs +6 -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/cli/recall.mjs +279 -0
  40. package/src/cli/recall.test.mjs +269 -0
  41. package/src/codex-rollout-memory.mjs +13 -0
  42. package/src/codex-rollout-memory.test.mjs +27 -0
  43. package/src/codex-thread-index.mjs +1 -1
  44. package/src/codex-thread-index.test.mjs +18 -0
  45. package/src/completed-turn-receipts.mjs +374 -0
  46. package/src/completed-turn-receipts.test.mjs +186 -0
  47. package/src/db-schema.test.mjs +9 -2
  48. package/src/db.mjs +20 -1
  49. package/src/decision-log.mjs +24 -0
  50. package/src/haiku-summarizer.mjs +93 -16
  51. package/src/haiku-summarizer.test.mjs +118 -9
  52. package/src/handoff-executor.mjs +161 -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 +337 -60
  63. package/src/resume-context.test.mjs +228 -1
  64. package/src/runtime-error-store.mjs +2 -1
  65. package/src/runtime-error-store.test.mjs +1 -1
  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 +142 -0
  72. package/src/windows-acl-test-helper.mjs +2 -2
@@ -0,0 +1,81 @@
1
+ # ADR 0008: observer-waitはmonotonic deadlineの最終再確認と明示cancelを持つ
2
+
3
+ 日付: 2026-07-15
4
+
5
+ ## Context
6
+
7
+ Observerは完了turnが無い間、最大一時間Throughlineを待つ。DB/WAL/mtimeは完了証拠ではなく、
8
+ Claude receiptまたはCodex `task_complete`から再構成したcompleted cursorだけがchangedを決める。
9
+ deadline到達時に再確認せずtimeoutすると境界直前の完了を取りこぼし、cancel時にtimerやsignal listenerを
10
+ 残すと次のwaitへ副作用が漏れる。wall clockの変更で一時間を過不足させてもならない。
11
+
12
+ この契約はControlのDecision証拠に使うため、追記可能な計画書ではなく不変ADRとして置く。
13
+
14
+ ## Decision
15
+
16
+ 1. Libraryは`waitForObserverTurnChange`を公開し、`projectPath`、必須`afterCursor`、
17
+ `timeoutSeconds`(既定3600、1以上3600以下)、`AbortSignal`と既存host read optionsを受ける。
18
+ test用のclock/sleep/resolver injectionは非公開または明示的なdependency引数としてよいが、公開CLI
19
+ optionにはしない。
20
+ 2. 呼出直後に一度completed-only cursorを再計算する。after cursorが無効なら`resync_required`、
21
+ cross-host tieなら`ambiguous_parent`、cursorが変化済みなら待たず`changed`を返す。
22
+ 3. 変化が無い時だけ短いintervalで再計算する。deadlineはmonotonic clockで固定し、wall clockの進退を
23
+ duration判定へ使わない。DB transaction、file handle、host固有index snapshotをsleep中に保持しない。
24
+ 4. 各wake後はtimeout判定より先にcompleted cursorを再計算する。deadline以上でも最後の一回がchanged、
25
+ `resync_required`、`ambiguous_parent`ならその状態を返し、最後までunchangedの時だけ`timeout`を返す。
26
+ deadline後に追加のpoll cycleは開始しない。
27
+ 5. wait wireは`throughline.observer_wait.v1`とし、次のshapeだけを返す。raw project path、本文、session/
28
+ thread/origin ID、host index pathを含めない。
29
+
30
+ ```json
31
+ {
32
+ "schema": "throughline.observer_wait.v1",
33
+ "status": "changed",
34
+ "afterCursor": "tlc1....",
35
+ "throughCursor": "tlc1...."
36
+ }
37
+ ```
38
+
39
+ | status | throughCursor |
40
+ |---|---|
41
+ | `changed` | 呼出seriesの新しいcompleted cursor |
42
+ | `timeout` | 入力`afterCursor`と同値 |
43
+ | `resync_required` | `null` |
44
+ | `ambiguous_parent` | `null` |
45
+
46
+ 6. AbortSignalが呼出前または待機中にabortされたら成功wireを返さず、固定codeを持つcancel errorで終了する。
47
+ signalと完了が同じwakeで競合した時は、abort観測後に新しい成功pollを開始しない。pending timerを解除し、
48
+ Libraryは呼出後にlistener/handleを保持しない。
49
+ 7. 公開CLIは次とし、`--project`、`--after-cursor`、`--json`を必須にする。未知/重複option、値欠落、
50
+ 範囲外timeoutを拒否する。`--timeout-seconds`既定は3600である。
51
+
52
+ ```text
53
+ throughline observer-wait --project <absolute-existing-directory>
54
+ --after-cursor <opaque> [--timeout-seconds <1..3600>] --json
55
+ ```
56
+
57
+ 8. 4種の既知状態はstdout単一行JSON、stderr空、exit 0とする。hard failureはstdout空、stderr単一行の
58
+ 固定error JSON、exit 1とし、例外本文やpath/cursor/hash/raw identityを転記しない。
59
+
60
+ | 条件 | code | message |
61
+ |---|---|---|
62
+ | CLI構文/timeout形式 | `E_OBSERVER_WAIT_ARGS` | `invalid observer-wait arguments` |
63
+ | projectのhard input拒否 | `E_OBSERVER_WAIT_INPUT` | `observer wait input is invalid` |
64
+ | signal/parent disconnect | `E_OBSERVER_WAIT_CANCELLED` | `observer wait was cancelled` |
65
+ | その他の内部失敗 | `E_OBSERVER_WAIT_INTERNAL` | `observer wait failed` |
66
+
67
+ 9. CLIは`SIGINT`、`SIGTERM`、Node IPCの`disconnect`を一つのAbortControllerへ写す。通常の子processが
68
+ IPC無しで親だけ終了する場合も一時間孤児化しないよう、起動時`ppid`を保存し、poll interval以下の
69
+ 周期で`process.ppid`変化または`process.kill(parentPid, 0)`の`ESRCH`を検出してabortする。`EPERM`は
70
+ 親不在の証拠にしない。終了時にsignal listenerと親watch timerを必ず外す。stdout/stderrのpipe断は
71
+ 成功扱いにせず、`EPIPE`を握りつぶして別経路へfallbackしない。
72
+ 10. cursorのversion、project、prefix、rollback不一致はLibrary既定どおり`resync_required`でexit 0とする。
73
+ waitはDB本文freshnessを待たず、`changed`後の`observer-read`が`projection_pending`を裁定する。
74
+
75
+ ## Consequences
76
+
77
+ - 呼出前に完了済みなら即時changed、待機中なら次poll、deadline境界なら最終再確認で回収できる。
78
+ - timeout、resync、曖昧親、cancelを混同せず、Observerは保存cursorを安全に据え置ける。
79
+ - 一時間waitでもtransactionやtimer leakを残さず、read側のDB freshness責務を重複しない。
80
+ - OSがSIGKILLした場合のcleanupや親死活監視daemonは非目標であり、通常signal/IPC/pipe契約を越えて
81
+ 成功を偽装しない。
@@ -0,0 +1,37 @@
1
+ # ADR 0009: Observer統合の関連回帰と公開文書を受け入れる
2
+
3
+ 日付: 2026-07-15
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ Observer向けcompleted-turn feedは、追加したread/wait境界だけでなく、既存のClaude Stop hook、Codex
12
+ capture、auditor-context、token monitorとの互換を維持する必要がある。また、実装済みの公開CLI契約を
13
+ README、AI向け正典、docs overview、CHANGELOGへ同期し、DB直接監視をfallbackとして案内してはならない。
14
+
15
+ ## Decision
16
+
17
+ 1. 次の関連gateをO1統合回帰として受け入れる。
18
+
19
+ ```text
20
+ node --import ./src/test-env.mjs --test \
21
+ src/hook-entrypoints.test.mjs src/codex-capture.test.mjs \
22
+ src/auditor-context.test.mjs src/cli/auditor-context.test.mjs \
23
+ src/cli/codex-hook.test.mjs src/token-monitor.test.mjs
24
+ ```
25
+
26
+ 結果は130件成功、失敗・skip・cancel・todo各0、実行時間807.298msだった。
27
+ 2. commit `fb558d7`のREADME、CLAUDE.md、`docs/00_overview.md`、CHANGELOG同期を受け入れる。
28
+ 3. 文書は`observer-read`/`observer-wait`をJSON-only CLIとして説明し、opaque cursor、read/wait状態、
29
+ `projection_pending`、最大3600秒wait、Claude receiptとCodex `task_complete`のcompleted境界を記録する。
30
+ 4. ThroughlineはMCP serverを所有せず、ObserverへDB、WAL、rolloutの直接監視fallbackを案内しない。
31
+ 5. 未公開、Phase full regression、pack gate、独立監査は完了扱いせず、次のO1 gateへ残す。
32
+
33
+ ## Consequences
34
+
35
+ - O1の関連回帰と文書同期TODOを完了できる。
36
+ - full `npm test`、`npm pack --dry-run --json`、独立監査はPhase完了時に一度だけ実行する。
37
+ - このADRはTask `observer-feed-doc-sync`のfinalization Decisionとして使い、追記可能なplanは使わない。
@@ -0,0 +1,49 @@
1
+ # ADR 0010: Observer O1 Phaseを監査修正込みで受け入れる
2
+
3
+ 日付: 2026-07-15
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ Observer向けcompleted-turn feedのPhase O1は、Claude receiptとCodex `task_complete`から
12
+ host-neutralなcompleted chainを構築し、rollback、prefix差替え、pagination、deadline、cancel、
13
+ privacyの境界を維持する。Phase完了候補でfull regressionと独立監査を一度ずつ実施したところ、
14
+ 監査は二つの実欠陥を発見し、監査時点の製品判定をFAILEDとした。
15
+
16
+ 1. Claude Stopが複数の過去turnを一括backfillした時、最後のpairしかreceiptへ公開しない。
17
+ 2. Codex project resolverがPOSIX pathまで小文字化し、caseだけ異なるprojectを誤照合し得る。
18
+
19
+ ## Decision
20
+
21
+ 1. 独立監査の製品判定FAILEDをControl revision 67でrejectとして保持する。監査の指摘を
22
+ 成功扱いへ書き換えず、同じTODOへの独立監査も反復しない。
23
+ 2. P1はcommit `02a809f`で、backfillが返す全logical turn numberを時系列でClaude receiptへ
24
+ publishする。receipt storeのsame-pair冪等性により、DBだけ回収済みだった過去pairも穴埋めする。
25
+ 3. P2はcommit `88fafaf`で、POSIX pathのcaseを保持し、Windows drive pathだけを従来どおり
26
+ case-insensitiveに扱う。
27
+ 4. P1/P2修正後のfocused gateを受け入れる。
28
+
29
+ ```text
30
+ node --import ./src/test-env.mjs --test \
31
+ src/turn-backfill.test.mjs src/hook-entrypoints.test.mjs \
32
+ src/codex-thread-index.test.mjs
33
+ ```
34
+
35
+ 結果は28件成功、失敗・skip・cancel・todo各0、実行時間693.58925msだった。
36
+ 5. Phase完了候補のfull `npm test`は監査前HEAD `c5d6f2d`で一度実施し、661件中660件成功、
37
+ 失敗0、Windows限定1件skip、cancel・todo各0、実行時間32983.729625msだった。
38
+ 監査後の変更範囲は上記focused gateで全て再検証した。fullを現HEADで再実行したとは扱わない。
39
+ 6. 修正後HEADの`npm pack --dry-run --json`は`throughline@0.6.3`、entryCount 190、shasum
40
+ `0d27e31c334f1f1941c27383e7f2a61f20c2e370`で成功した。`git diff --check`も成功した。
41
+ 7. Phase O1の実装・関連回帰・文書同期・full/pack・独立監査とその二指摘の修正を完了とする。
42
+ publish、registry、実端末展開は別Waveであり、このDecisionには含めない。
43
+
44
+ ## Consequences
45
+
46
+ - Claude receiptとCodex `task_complete`のcompleted chainは、複数turn回収とPOSIX case境界を含めて
47
+ Observerのread/wait契約へ渡せる。
48
+ - full regressionを細かな修正ごとに反復せず、監査後deltaをfocused gateで検証した証拠が残る。
49
+ - このADRをTask `observer-feed-phase-audit`、Phase gate、Control finalizationの不変Decision証拠に使う。
@@ -0,0 +1,34 @@
1
+ # ADR 0011: Observer O1 Controlのclosure laneを補正する
2
+
3
+ 日付: 2026-07-15
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ Control `observer-feed-20260715`のPhase gateは、O1の主要実装Taskが完了したrevision 51の後、
12
+ revision 52で`behavior-preserving`として宣言された。したがって、このgateが対象にするのは
13
+ 文書同期、full/pack、独立監査、最終統合からなるclosure laneであり、先行するO1実装全体を
14
+ behavior-preservingと再分類するものではない。
15
+
16
+ 独立監査後、P1/P2の挙動修正がclosure中に必要になった。元gateを改変したり
17
+ `behavior_change=not-applicable`のまま修正を隠したりしてはならない。
18
+
19
+ ## Decision
20
+
21
+ 1. P1/P2の修正受入はcorrective Control `observer-feed-o1-audit-fixes-20260715`が所有する。
22
+ 2. corrective Controlは`behavior-change` laneで、二つの修正受入Task、focused gate、
23
+ [ADR 0010](0010-observer-o1-phase-acceptance.md)をDecision証拠としてrevision 15でfinalize済みである。
24
+ 3. 元ControlのPhase gateはrevision 52以降のclosure統合だけを対象とし、
25
+ `behavior_change=not-applicable`はこのclosure laneに対してのみ記録する。
26
+ 4. 元Controlの独立監査FAILEDとworker rejectは保持し、corrective Controlのfinalizationを受けて
27
+ Phase O1全体を最終受入する。
28
+ 5. Phase gateを実装後に追加した事実は消さない。今後のPhaseは実装前にgateを宣言する。
29
+
30
+ ## Consequences
31
+
32
+ - 挙動修正をbehavior-preservingへ偽装せず、二つのControlの所有境界が明示される。
33
+ - 元Controlはclosure laneとして完了でき、O1の実装・監査・修正証拠を一つのDecision鎖へ統合できる。
34
+ - このADRを元ControlのPhase gateとControl finalizationの不変Decision証拠に使う。
@@ -0,0 +1,32 @@
1
+ # ADR 0012: Claude Stop receiptの前にtranscript flush barrierを置く
2
+
3
+ 日付: 2026-07-16
4
+
5
+ ## Status
6
+
7
+ Accepted for implementation。Observer queue 19eを保持し、次のlive turnより先に修理する。
8
+
9
+ ## Context
10
+
11
+ 実Claude turnはassistant `end_turn`、Stop hook 4本・hook error 0、Throughline state更新まで成立したが、
12
+ hook実行時の最新sessionにはuser/assistant bodyがなくcompletion receiptも作られなかった。同じtranscriptを
13
+ turn後に現行parserで読むとlatest logical groupは1件だった。Claudeのasync Stop hookがfinal assistant行の
14
+ 永続化可視化より先に一度だけtranscriptを読み、`lastTurnNumber === null`で正常no-opしたflush raceである。
15
+
16
+ ## Decision
17
+
18
+ 1. Stop payloadに非空`last_assistant_message`がある場合、latest user groupの非junk assistant本文が
19
+ その値と一致するまで短いbounded intervalでtranscriptを再読する。
20
+ 2. `last_assistant_message`はcompletion identity/flush barrierにだけ使い、L2本文やreceipt digestの
21
+ ソースにはしない。本文は従来どおりtranscriptからDBへcommitしたpairだけを使う。
22
+ 3. latest user groupを必須にし、過去の同文assistantや前turnを一致として採用しない。
23
+ 4. deadlineまで一致しなければ明示errorと`HOOK_PROCESS_TURN_FAILED`を返し、completionなしへ丸めない。
24
+ 5. markerを持たない旧Claude hostは既存one-shot transcript parser契約を維持する。
25
+ 6. Claude Stop hookの`async: true`、DB schema、receipt wire、Observer cursorは変更しない。
26
+
27
+ ## Acceptance
28
+
29
+ - user行だけの状態でhookを開始し、assistant行を遅延appendしても同じ実行でbody pairとreceiptを作る。
30
+ - 過去turnのassistantがcurrent markerと同文でも、latest userが未完なら待機し、誤ったpairをpublishしない。
31
+ - marker不一致のdeadlineはexplicit failureになり、runtime error ownerは一回だけ記録する。
32
+ - 通常同期flush、markerなし旧payload、複数turn backfillの既存挙動を維持する。
@@ -0,0 +1,46 @@
1
+ # ADR 0013: Observer readは同時writerをboundedに待つ
2
+
3
+ 日付: 2026-07-16
4
+
5
+ ## Status
6
+
7
+ Accepted。
8
+
9
+ ## Context
10
+
11
+ queue 19eの実Codexで、2件目の`task_complete`とThroughline captureは成功した一方、同時に走った
12
+ Observer production callerの`observer-read`が一度nonzeroとなり`E_THROUGHLINE_EXEC`で終了した。
13
+ 直後の同じ公開readは2件のcompleted turnを返した。campaign-private driverでも同じ書込み瞬間の
14
+ 単発nonzeroを観測しており、caller固有の失敗ではない。
15
+
16
+ completed feedはDB lagを`projection_pending`として扱うが、SQLite lockをstale成功へ丸めてはならない。
17
+ 一方、通常のStop captureとreadの短い競合を即時hard failureにすると、正規の継続監視が不安定になる。
18
+
19
+ ## Decision
20
+
21
+ 1. `readCompletedPairProjection`のread-only SQLite connectionだけにbounded busy waitを設定する。
22
+ 2. lock解消後は同じDBの整合したsnapshotを読み、pair不足は従来どおり`projection_pending`にする。
23
+ 3. busy上限超過、schema不一致、project不一致、その他I/Oは従来どおりhard failureにする。
24
+ 4. CLI再spawn、別DB、古い本文、cursor進行へのfallbackは追加しない。
25
+ 5. Spotter向け`readAuditorContext`の既存lock failure契約は変更しない。
26
+
27
+ ## Acceptance
28
+
29
+ - 別processがexclusive writer lockを保持中でも、上限内に解放すればcompleted projectionが同じpairを返す。
30
+ - 上限を越えるlockとその他hard failureはnonzero契約を維持する。
31
+ - Observer read/wait、auditor-context、Codex captureの関連gateを一度通す。
32
+ - 修理済みcandidateでqueue 19e Codex 2-cycle liveを再確認する。
33
+
34
+ 実装gate(2026-07-16):
35
+
36
+ - focused `src/auditor-context.test.mjs`: 修正前15/16、修正後16/16 PASS。
37
+ - related Observer read/wait、auditor、receipt、Codex hook/capture: 78/78 PASS。
38
+ - 変更source/testの構文検査、新規ADR lint、`git diff --check`: PASS。
39
+
40
+ Live acceptance(2026-07-16):
41
+
42
+ - 修理済みcandidateのqueue 19e実Codex r11で、親completed-turn 2件と同じObserver generationの
43
+ completed cycle 2件を確認した。
44
+ - 初回cycle後65秒超の継続、pending reservation/cycle/model operation残留なしを確認した。
45
+ - caller SIGINTはcancelled/exit 130、managed app-serverと親app-serverはterminalとなった。
46
+ - projectは空のまま。raw ID、prompt、model output、credentialは保存していない。
@@ -0,0 +1,112 @@
1
+ # ADR 0014: 二相ハンドオフ — 幽霊SessionStartによるバトン奪取の排除と注入10k予算
2
+
3
+ 日付: 2026-07-17
4
+
5
+ ## Status
6
+
7
+ Accepted(実装済み・live acceptance 済み — 本ADR末尾の記録参照)。
8
+
9
+ ## Context
10
+
11
+ ### Incident: バトンが幽霊セッションに奪われ、実セッションが記憶ゼロで始まる
12
+
13
+ 2026-07-17、dotagentsで同日2回(10:33 JST / 16:39 JST)、`/tl` バトンによる引き継ぎが
14
+ 沈黙して失敗した。`~/.throughline/logs/inheritance-decision.log` と DB の実測:
15
+
16
+ - Claude Code は同一 project_path に対し短時間(実測 315ms / 488ms 差)に複数の
17
+ SessionStart hook を発火させることがある。うち一部は **transcript を一度も生成せず、
18
+ sessions 行の updated_at が誕生時刻のまま二度と動かない「幽霊セッション」** になる。
19
+ - 旧実装は SessionStart で `consumeBaton`(BEGIN IMMEDIATE の SELECT+DELETE)→ merge →
20
+ stdout 注入を行っていたため、**先着した幽霊がバトンを消費して前任の全記憶
21
+ (skeletons 16件 / bodies 84件等)を吸い込み**、数百ms後の実セッションは
22
+ `baton_skip_reason: "missing"` で記憶ゼロで始まった。
23
+ - 全期間実測: 同一projectで2秒以内の SessionStart ペアは 3,222 イベント中 56 ペア
24
+ (= 近接ダブル発火は日常的)。バトン存在時に近接ペアが起きた 2 回は 2 回とも幽霊が勝った。
25
+ - 2026-05 の auto path でも、受け取り側 transcript が存在しない merge が 6 件あり、
26
+ 幽霊 twin(source='clear' の二重発火)が前任に選ばれた形と整合する。
27
+
28
+ **SessionStart 時点では実体と幽霊を判別する情報が原理的に存在しない。**
29
+ 実測で、本物のセッションの transcript ファイルも SessionStart hook 発火の約 461ms
30
+ **後** に作られる(payload の `transcript_path` は「これから作られる予定のパス」)。
31
+ したがって「消費前に transcript 実在を検査する」案は本物も全部弾いて成立しない。
32
+
33
+ ### 第二の欠陥: hook stdout 注入は10k超で file 化され、モデル可視が先頭2KBに劣化する
34
+
35
+ 対策検証中の実測(Claude Code 2.1.211、tracer 実験 + 全 transcript 掃引):
36
+
37
+ - SessionStart / UserPromptSubmit の hook stdout は約 10,000 字を超えると
38
+ `<persisted-output>`(保存ファイルパス + 先頭 2KB preview)に置換され、
39
+ **モデルに inline で見えるのは先頭 2KB だけ** になる。
40
+ 9,501 字は inline 通過、15,286 字は file 化を確認。
41
+ - 実運用の SessionStart 注入で 10k 超だった 12 件(2026-06-28 / v2.1.195 以降の全件)は
42
+ **12 件全部が劣化していた**(例: 64,148 字 emit → 可視 2,054 字)。
43
+ L1+L2 本体はモデルに読まれておらず、ヘッダ + 現在地アンカーが偶然 2KB 内に
44
+ 収まっていたため引き継ぎが機能している風に見えていた。
45
+
46
+ ## Decision
47
+
48
+ ### 二相ハンドオフ(merge・注入を「実体の証明」まで遅延する)
49
+
50
+ 1. **SessionStart(第一相)は intent 登録のみ**。sessions INSERT と
51
+ `pending_handoffs`(schema v9)への登録だけを行い、consumeBaton / merge / 注入を
52
+ 一切しない。auto path(source='clear')の前任はこの時点で解決して
53
+ `auto_predecessor_id` に凍結する。
54
+ 2. **最初の UserPromptSubmit(第二相)が consume + merge + 注入を行う**。
55
+ プロンプト到達 = セッション実在の証明であり、幽霊は構造上ここに到達できない。
56
+ pending 行の consume は BEGIN IMMEDIATE で atomic(1 セッション 1 回)。
57
+ 3. **baton 適格性はセッション誕生時刻基準**: `age = pending.created_at - baton.created_at`
58
+ が `0 ≤ age ≤ TTL(1h)` のときだけ消費。負 age(自分の誕生後に書かれたバトン)は
59
+ **削除せず残置**(`future_baton`)— 走行中セッションの横取りと、multi-window で
60
+ 本来の後継のバトンを先食いする穴を同時に塞ぐ。TTL の意味論
61
+ 「/tl から新セッション開始までの猶予」は消費が遅延しても保存される。
62
+ 4. **auto path の前任候補に transcript 実在フィルタ**(導出パス or state file)。
63
+ 幽霊 twin は transcript を持たないため前任に選ばれない(2026-05 型の再発防止)。
64
+ 実前任は /clear 前に活動していた実体なので必ず transcript を持つ。
65
+ 5. **注入は10k予算内レンダリング** (`buildBudgetedResumeContext`, 上限 9,500 字):
66
+ ヘッダ + 現在地アンカーは常に全文、L1 → L2 の順に新しい側から予算まで詰め、
67
+ 省略した行数は**注入文内に明示**し decision log にも記録する(黙って切らない)。
68
+ 最新 L2 行が単体で予算超過なら切り詰めて `throughline detail` 参照を付す。
69
+ 6. 幽霊の pending 行は誰にも consume されず無害に残る(数百バイト/行)。
70
+ TTL ベースの GC は**入れない** — 長時間 idle 後の初回プロンプトから引き継ぎを
71
+ silent に奪う fallback になるため。
72
+ 7. 判定ログは `phase: 'session-start' | 'prompt-submit'` の2種を同じ
73
+ inheritance-decision.log に記録する(本 incident の一次証拠となった実績を保つ)。
74
+
75
+ ### 廃止・撤去
76
+
77
+ - SessionStart の注入経路と、それにぶら下がっていた Phase 0-2 spike /
78
+ Phase 0-6 `initialUserMessage` テスト分岐(docs/10 で両 no-go 確定済みの実験残骸)。
79
+ - 「UserPromptSubmit は注入しない」規約 — 理由だった「SessionStart との二重注入」が
80
+ SessionStart 注入の廃止で消滅したため、注入責務ごと UserPromptSubmit へ移す。
81
+
82
+ ## Acceptance
83
+
84
+ 実装 gate(2026-07-17、このMacで実施済み):
85
+
86
+ - `npm test` 680/681 PASS(1 skip は既存の win32 契約 skip)。
87
+ - 新規回帰テスト: 幽霊先着 SessionStart がバトンを奪えず、実セッションの初回プロンプトが
88
+ 記憶を受け取る(incident 再現形)/ 走行中セッションが future baton を奪えない /
89
+ bornAt 基準 TTL / pending consume の1回性 / 予算レンダリングの省略告知・切り詰め。
90
+ - 10k 実測: 一時 UserPromptSubmit hook + `claude -p`(Haiku)の tracer 実験で、
91
+ 11,953 字 stdout の 10k 境界後 tracer がモデル不可視、`<persisted-output>` 化を確認。
92
+
93
+ Live acceptance(2026-07-17、このMacで dev 版 global install 後に実施):
94
+
95
+ - [x] 実 Claude Code(headless、実 hooks 経由)で、記憶セッション → `/tl` → 新セッション
96
+ 初回プロンプトの引き継ぎを確認。後継が合言葉を即答し、decision log に
97
+ `phase=session-start`(pending_registered)→ `phase=prompt-submit`
98
+ (`triggered_path=baton`, `merged=true`, injection 2,095 字・省略ゼロ)が刻まれた。
99
+ DB は初回 hook 発火で v9 へ migrate(session `8452e936`、前任 `c8b485bd`)。
100
+ - [x] 自己バトン食いの不在を実機確認: `/tl` を打った resume セッション自身の pending は
101
+ `baton missing` で消化され(消費が baton 書込より先)、バトンは後継まで残った。
102
+ `baton_age_ms` は誕生時刻基準(21,200ms)で記録された。
103
+ - [x] 幽霊2体(c4f05b96 / 90cb0c0e)の記憶回収済み(skeletons 30 / bodies 166 /
104
+ details 2,247 を実セッション `9fb15563` へ。DB バックアップ
105
+ `throughline.db.backup-20260717-ghost-recovery` 取得済み)。
106
+ - [ ] multi-window での近接 SessionStart 実機再現は未実施(幽霊の発生タイミングを
107
+ 任意に誘発できないため。回帰は subprocess テストの incident 再現形で担保)。
108
+
109
+ ## 関連
110
+
111
+ - 幽霊 SessionStart 自体は Claude Code 側の挙動(upstream 報告は別トラック)。
112
+ - 10k persisted-output は third-party 仕様として rag/01-hooks に実測記録。
@@ -0,0 +1,81 @@
1
+ # ADR 0015: L1 要約の既定を gpt-5.6-luna / effort low / 削減割合 1/5 にする
2
+
3
+ 日付: 2026-07-17
4
+
5
+ ## Status
6
+
7
+ Accepted(実装済み・全テスト green。実運用での要約品質の継続観察は運用に委ねる)。
8
+
9
+ ## Context
10
+
11
+ - オーナー方針: 裏方で動く AI は Codex に寄せる。GPT-5.6 系列(sol / terra / luna)の
12
+ 登場を受け、コスト効率を含めてモデルを選定する。要約の目標量は文字数指定ではなく
13
+ **削減割合**で決めたい(割合はオーナーが設定で動かせること)。
14
+ - 現行実装の問題: `summarizeWithCodexCli` は isolation のため `--ignore-user-config` で
15
+ `codex exec` を呼ぶが、これにより `~/.codex/config.toml` のモデル選択も読まれず、
16
+ **CLI 内蔵デフォルトで走っていた**(明示 `-m` なし)。
17
+ - 基準線: 現行 Claude Haiku 経路は削減割合 1/5(`l2Text.length / 5` を「約N文字」に
18
+ 換算してプロンプトへ渡す割合ベース。docs の「L1 = one-liner」記述は実装と乖離)。
19
+
20
+ ## 実測評価(2026-07-17、このMac)
21
+
22
+ 方法: 実 DB の L2 ターンをソースに、本番と同型のプロンプト・`codex exec` flag で要約を
23
+ 生成。**要約を見る前に**各ソースから要点チェックリスト(固有名詞・数値・因果・決定・
24
+ 未解決)を作成し、要約ごとの「要点拾い数」で採点。原文に無い内容の混入(捏造)は別枠で
25
+ 全数検査。判定者は親セッション(単一判定者、n は下記のとおり=既定値選定には十分、
26
+ 論文品質ではない)。
27
+
28
+ ### Round 1: モデル × 削減割合(5ソース × 7構成 = 35ラン)
29
+
30
+ | 構成 | 拾い率 | 備考 |
31
+ |---|---|---|
32
+ | Haiku@1/5(現行基準線) | 26/35 = 74%(timeout 1件込み。完走分のみ 93%) | **5本中1本が90秒 timeout で完全失敗**。本番はリトライ後 raw L2 fallback = 実質無圧縮 |
33
+ | luna@1/5 | 32/35 = 91% | 6〜16秒 |
34
+ | luna@1/10 | 80% / luna@1/15 | 64% |
35
+ | terra@1/5 | 32.5/35 = 93% | luna+2%のために上位量を払う価値なし |
36
+ | terra@1/10 | 73% / terra@1/15 | 60% |
37
+
38
+ **結論1: 圧縮を 1/5 より強めると要点が死ぬ(93%→73〜80%)。モデルを賢くしても
39
+ 救えない=情報量の物理であり、既定割合は 1/5 のまま。**
40
+
41
+ ### Round 2: luna の effort 比較(8ソース × {none, low, medium} × 2反復 = 48ラン)
42
+
43
+ ソースは別プロジェクト由来3本(Caveat 技術報告 / OpenCClaw 指示書散文 / 6.2k字
44
+ subagent 報告)を追加。`minimal` は luna 非対応(400 error。サポートは
45
+ none/low/medium/high/xhigh)のため none を下端とした。
46
+
47
+ | effort | 拾い率 | 中央値レイテンシ | 特徴的な欠落 |
48
+ |---|---|---|---|
49
+ | none | 103/116 = 89% | 11.8s | **物語の現在地を落とす**(親の裁定で再開済み→「裁定待ち」と書く ×2、設計矛盾の同型性、「効果検証はこれから」) |
50
+ | **low** | **107.5/116 = 93%** | 12.4s | 最もバランス。反復ブレ ±1〜2項目 |
51
+ | medium | 107.5/116 = 93% | 16.3s | low と同点。**事故の白状を2回とも落とした**(整理しすぎて都合の悪い脱線を刈る傾向) |
52
+
53
+ 捏造は Round 1+2 の全 82 有効ランで 0 件。
54
+
55
+ **結論2: low が既定。medium は同点で 1.4 倍遅くコスト高。none は事実の羅列は拾えるが
56
+ 「誰が何を決めて今どこか」を落とし、再開用 L1 として痛い欠落をする。**
57
+
58
+ ## Decision
59
+
60
+ 1. **既定: `gpt-5.6-luna` / `model_reasoning_effort="low"` / 削減割合 0.2 (=1/5)**。
61
+ 2. `summarizeWithCodexCli` は明示 `-m` / `-c model_reasoning_effort=...` を渡す
62
+ (`--ignore-user-config` によるモデル未指定の穴を修理)。
63
+ 3. env で設定可能: `THROUGHLINE_L1_MODEL` / `THROUGHLINE_L1_EFFORT` /
64
+ `THROUGHLINE_L1_RATIO`(割合形式 0 < r ≤ 1)。**不正な RATIO は黙って既定へ
65
+ 落とさず explicit error**(フォールバック禁止原則)。
66
+ 4. claude-primary の backend 順序を **codex-sidecar → Codex CLI (luna@low) → Haiku →
67
+ raw L2** にする。各段の失敗理由は結果 (`sidecarReason` / `codexCliReason`) に記録
68
+ する宣言済み fallback。codex-primary は従来どおり Codex CLI 一本で explicit error。
69
+ (実測で Haiku は timeout 完全失敗があり、luna は同等品質・半分以下のレイテンシ・
70
+ 失敗ゼロのため、Haiku より先に置く。)
71
+ 5. sidecar `summarize-l1` preset のモデルは codex-sidecar(別repo)の所有。本 ADR では
72
+ 変更しない。
73
+ 6. docs の「L1 = one-liner」記述は「割合ベース(既定 1/5)」へ修正する。
74
+
75
+ ## Acceptance
76
+
77
+ - `npm test` 684/685 PASS(1 skip は既存 win32 契約)。
78
+ - 新規テスト: 明示 `-m`/effort 引数、env 上書き、RATIO のプロンプト反映、不正 RATIO の
79
+ explicit error、claude-primary の sidecar→codex-cli→haiku 梯子。
80
+ - 評価の生データ: 実測スクリプト・全要約・採点はセッション scratchpad(使い捨て)。
81
+ 要旨は本 ADR の表が正本。
@@ -0,0 +1,93 @@
1
+ # ADR 0016: 注入の push/pull 再設計 — 現在地 + 入るだけ L2 の push と recall CLI による pull
2
+
3
+ - Status: accepted (2026-07-18 オーナー裁定)
4
+ - 関連: [ADR 0014](0014-two-phase-handoff-ghost-baton.md)(9,500 字注入予算)、
5
+ [ADR 0015](0015-l1-summarizer-model-effort-ratio.md)(L1 要約体制)
6
+
7
+ ## 問題
8
+
9
+ 9,500 字の注入予算(hook stdout ~10k で `<persisted-output>` file 化、ADR 0014)の中で、
10
+ 旧 `buildBudgetedResumeContext` は **L1 を先に詰めてから L2 を詰めて**いた。実測
11
+ (このMacのDB、L2 1 ターン中央値 ~800 字・平均 ~2,750 字)では L2 は 5〜10 ターンしか
12
+ 入らず、しかも L1 生成は `L2_WINDOW = 20` より古いターンにしか走らない(Stop 毎 1 件の
13
+ 遅延生成)ため、「予算落ちした 5〜20 ターン前」が **L2 も注入されず L1 も未生成**の
14
+ 記憶空白帯になっていた。省略告知は時刻列挙のみで、取り出しは 1 ターンずつ
15
+ `throughline detail` を叩くしかなかった。
16
+
17
+ ## 検討して不採用にした案
18
+
19
+ 1. **固定 N ターン + per-role 切り詰めで L2 を保証し、残りに L1 充填**:
20
+ L1 生成閾値を N に連動させる案。pull 設計に移った時点で、注入 L1 が pull の L2×残り
21
+ と同じターンを二重に運ぶ矛盾が出て破棄(会話での敵対的整理)。固定 N 自体も
22
+ 「軽い会話で予算を設計的に遊ばせる」ため破棄。
23
+ 2. **multi-hook による 10k 突破**: 実測で**可能**と確認した(下記「実測」)が、hook の
24
+ 構造的想定(1 本 = 1 context string)に無い使い方であり、attachment 順序も非決定の
25
+ ため不採用。
26
+ 3. **全文ログのファイル書き出し + Read 誘導**: DB が正本なのに派生ファイルの寿命管理が
27
+ 発生する。過去ターンのレコードは不変なので、スナップショット性は DB 直参照でも
28
+ 担保できる。CLI 経由の DB 直参照に統一して不採用。
29
+ 4. **生 SQL をモデルに案内**: schema 講義で注入が太り、origin フィルタ等の誤クエリ事故面
30
+ が開く。`detail` と同じ「DB 直参照だがモデルにはコマンド 1 発」の流儀で不採用。
31
+
32
+ ## 決定
33
+
34
+ push は「現在地」に徹し、過去は pull に出す。
35
+
36
+ - **push(注入、9,500 字内)**: ヘッダ + 現在地アンカー + 案内セクション
37
+ (固定部として最優先予約・無条件表示)+ 残り全予算に **L2 を新しい順で
38
+ 丸ごと入るターンだけ全文**(ターン単位の原子。固定 N なし・断片詰めなし・
39
+ ターン境界の自然な端数は許容)。**L1 は注入しない**。
40
+ 最新ターンが単体で予算超過する場合だけ切り詰めて入れる(従来規則の維持)。
41
+ - **pull(新 CLI `throughline recall` = read-only DB 直参照)**:
42
+ - `recall --l2 --session <id> --before <ISO8601 ms> --last <N>` —
43
+ 境界より古いターンを新しい側から N 件、L2 全文(注入と同じ行文法 + L3 suffix)
44
+ - `recall --l1 --session <id> --before <ISO8601 ms> --skip <N>` —
45
+ --l2 の担当分を飛ばした先の全ターン一覧。**L1 要約があれば要約、無ければ
46
+ 「未要約」と明示**して detail 誘導。冒頭に「全 M ターン / 要約済み K」を正直に表示
47
+ - 一点掘りは従来どおり `throughline detail <時刻>`(L2+L3)
48
+ - **`L2_WINDOW = 20` は据え置き**。20 の意味が「push(入るだけ)+ pull(残り)」の
49
+ 合計窓に再定義されるだけで、L1 要約ペース・Codex 側・schema への変更なし。
50
+
51
+ ### 間抜け防止 — 機械用境界の完全焼き込み(refuter 敵対的検証 2026-07-18)
52
+
53
+ 実装前に refuter による敵対的検証を通し、real 指摘 6 件を全て設計へ反映した:
54
+
55
+ | 指摘 | 対処 |
56
+ |---|---|
57
+ | HH:MM:SS 境界は「当日」解決(sc-detail の `timeToUnixRange`)で深夜跨ぎに壊れる | `--before` は ISO 8601 完全日時(ms 精度)。表示用 HH:MM:SS と機械用境界を分離 |
58
+ | 秒切り捨てで同秒行の境界包含が未定義 | 境界は strict less-than の ms 比較で規定 |
59
+ | 20 ターン窓はクエリ時再計算のため、新セッションのターン追記で窓がスライドし古い側が黙って欠落 | `--last <残り件数>` も注入時に焼き込み、recall 側の窓再計算を全廃 |
60
+ | L1 は遅延生成でバックログがあり「全 N ターンの要約」が虚偽になる。未要約ターンがどの取っ手からも見えない | `--l1` は全ターン一覧(要約 or 未要約明示)。件数は「全 M / 要約済み K」形式 |
61
+ | 既定 session 解決(cwd 系)は Codex 併走・複数ウィンドウで非決定 | `--session` も焼き込み。recall は既定解決を持たない |
62
+ | `getDb()` は mkdir + read-write open + migration を行い read-only 契約に反する | recall は `DatabaseSync(path, { readOnly: true })` + 存在チェック。DB を作成しない |
63
+
64
+ 併せて、予算詰めを行(role)単位からターン単位の原子に変更した(行単位だと同一ターンの
65
+ assistant 行だけ入り user 行が pull 側に半身で現れ、境界の算術が濁るため)。
66
+
67
+ ## 実測
68
+
69
+ ### 新設計のレンダリング(2026-07-18、実 DB 191 ターンセッション)
70
+
71
+ - totalChars 9,158 / 9,500、L2 9 ターン注入、残り 11 ターン、窓外 171 ターン
72
+ (要約済み 124 / 未要約 47)
73
+ - 焼き込まれた案内コマンドをそのまま実行して、`--l2` が境界ぴったりから 11 ターン
74
+ (33k 字)、`--l1` が全 171 ターン一覧(118k 字、`--last` で部分取得可)を返すことを確認
75
+
76
+ ### multi-hook 10k 突破の実測(2026-07-18、Claude Code 2.1.211 / 不採用)
77
+
78
+ - 同一 UserPromptSubmit に hook を 3 本登録 → それぞれ独立の `hook_success` attachment
79
+ になり 9,000 字 × 3 = 27,000 字が全部モデル可視 inline
80
+ - 1 本だけ 12k にすると**その 1 本だけ**が `<persisted-output>` 化(隣の 9k は無傷)
81
+ = 10k 判定は per context string
82
+ - 5 本 × 9k = 45,000 字でも全部可視。合算上限は 45k まで観測されず
83
+ - **attachment の並び順は登録順と一致しない**(並列実行のため非決定)
84
+ - 詳細は [rag/01-hooks/hook-stdout-10k-persisted-output.md](../../rag/01-hooks/hook-stdout-10k-persisted-output.md)
85
+
86
+ ## 帰結
87
+
88
+ - 「5〜20 ターン前の空白帯」は消滅: 窓内の非注入分は `recall --l2` が verbatim で、
89
+ 窓外は `recall --l1` が全ターン(未要約含む)で必ず到達可能
90
+ - push は会話密度に自動適応(中央値 7〜8 ターン、軽い会話なら 10 ターン超、重くて 2〜3)
91
+ - コンテキスト衛生は維持: pull はモデルが必要と判断した時だけ発生する
92
+ - `handoff-executor` の injection stats は
93
+ `injected_l2_turns / remaining_l2_turns / older_turns / older_summarized` に更新
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "throughline",
3
- "version": "0.6.3",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Claude Code hooks plugin for structured context compression (/clear-safe persistent memory)",
6
6
  "keywords": [