throughline 0.3.24 → 0.4.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 (116) hide show
  1. package/.claude/commands/tl.md +6 -21
  2. package/.codex-sidecar.yml +62 -0
  3. package/CHANGELOG.md +632 -0
  4. package/README.ja.md +71 -46
  5. package/README.md +420 -76
  6. package/bin/throughline.mjs +169 -7
  7. package/codex/skills/throughline/SKILL.md +157 -0
  8. package/codex/skills/throughline/agents/openai.yaml +7 -0
  9. package/docs/INHERITANCE_ON_CLEAR_ONLY.md +159 -0
  10. package/docs/L1_L2_L3_REDESIGN.md +415 -0
  11. package/docs/PUBLIC_RELEASE_PLAN.md +185 -0
  12. package/docs/THROUGHLINE_CLEAR_AUTO_HANDOFF_PLAN.md +286 -0
  13. package/docs/THROUGHLINE_CODEX_DUAL_SUPPORT.md +249 -0
  14. package/docs/THROUGHLINE_CODEX_FIRST_ROADMAP.md +555 -0
  15. package/docs/THROUGHLINE_CODEX_MONITOR_IMPLEMENTATION_PLAN.md +220 -0
  16. package/docs/THROUGHLINE_CODEX_TRIM_IMPLEMENTATION_PLAN.md +528 -0
  17. package/docs/THROUGHLINE_CODEX_TRIM_ROLLBACK_FIX_PLAN.md +672 -0
  18. package/docs/archive/CONCEPT.md +476 -0
  19. package/docs/archive/EXPERIMENT.md +371 -0
  20. package/docs/archive/README.md +22 -0
  21. package/docs/archive/SESSION_LINKING_DESIGN.md +231 -0
  22. package/docs/archive/THROUGHLINE_NEXT_STEPS.md +134 -0
  23. package/docs/throughline-codex-trim-rollback-incident-report.md +306 -0
  24. package/docs/throughline-handoff-context.example.json +57 -0
  25. package/docs/throughline-rollback-context-trim-insight.md +455 -0
  26. package/package.json +6 -2
  27. package/src/baton.mjs +17 -45
  28. package/src/baton.test.mjs +4 -41
  29. package/src/cli/codex-capture.mjs +95 -0
  30. package/src/cli/codex-handoff-model-smoke.mjs +292 -0
  31. package/src/cli/codex-handoff-model-smoke.test.mjs +262 -0
  32. package/src/cli/codex-handoff-smoke.mjs +163 -0
  33. package/src/cli/codex-handoff-smoke.test.mjs +149 -0
  34. package/src/cli/codex-handoff-start.mjs +291 -0
  35. package/src/cli/codex-handoff-start.test.mjs +194 -0
  36. package/src/cli/codex-hook.mjs +276 -0
  37. package/src/cli/codex-hook.test.mjs +293 -0
  38. package/src/cli/codex-host-primitive-audit.mjs +110 -0
  39. package/src/cli/codex-host-primitive-audit.test.mjs +75 -0
  40. package/src/cli/codex-restore-smoke.mjs +357 -0
  41. package/src/cli/codex-restore-source-audit.mjs +304 -0
  42. package/src/cli/codex-resume.mjs +138 -0
  43. package/src/cli/codex-rollback-model-visible-smoke.mjs +373 -0
  44. package/src/cli/codex-rollback-model-visible-smoke.test.mjs +255 -0
  45. package/src/cli/codex-sidecar-diagnostics.mjs +48 -0
  46. package/src/cli/codex-sidecar-dry-run.mjs +85 -0
  47. package/src/cli/codex-summarize.mjs +224 -0
  48. package/src/cli/codex-threads.mjs +89 -0
  49. package/src/cli/codex-visibility-smoke.mjs +196 -0
  50. package/src/cli/codex-vscode-restore-smoke.mjs +226 -0
  51. package/src/cli/codex-vscode-rollback-smoke.mjs +114 -0
  52. package/src/cli/doctor.mjs +503 -1
  53. package/src/cli/doctor.test.mjs +542 -3
  54. package/src/cli/handoff-preview.mjs +78 -0
  55. package/src/cli/help.test.mjs +64 -0
  56. package/src/cli/install.mjs +226 -3
  57. package/src/cli/install.test.mjs +205 -4
  58. package/src/cli/trim.mjs +564 -0
  59. package/src/codex-app-server.mjs +1816 -0
  60. package/src/codex-app-server.test.mjs +512 -0
  61. package/src/codex-auto-refresh.mjs +194 -0
  62. package/src/codex-auto-refresh.test.mjs +182 -0
  63. package/src/codex-capture.mjs +235 -0
  64. package/src/codex-capture.test.mjs +393 -0
  65. package/src/codex-handoff-model-smoke.mjs +114 -0
  66. package/src/codex-handoff-model-smoke.test.mjs +89 -0
  67. package/src/codex-handoff-smoke.mjs +124 -0
  68. package/src/codex-handoff-smoke.test.mjs +103 -0
  69. package/src/codex-handoff.mjs +331 -0
  70. package/src/codex-handoff.test.mjs +220 -0
  71. package/src/codex-host-primitive-audit.mjs +374 -0
  72. package/src/codex-host-primitive-audit.test.mjs +208 -0
  73. package/src/codex-restore-smoke.test.mjs +639 -0
  74. package/src/codex-restore-source-audit.mjs +1348 -0
  75. package/src/codex-restore-source-audit.test.mjs +623 -0
  76. package/src/codex-resume.test.mjs +242 -0
  77. package/src/codex-rollout-memory.mjs +711 -0
  78. package/src/codex-rollout-memory.test.mjs +610 -0
  79. package/src/codex-sidecar-cli.test.mjs +75 -0
  80. package/src/codex-sidecar.mjs +246 -0
  81. package/src/codex-sidecar.test.mjs +172 -0
  82. package/src/codex-summarize.test.mjs +143 -0
  83. package/src/codex-thread-identity.mjs +23 -0
  84. package/src/codex-thread-index.mjs +173 -0
  85. package/src/codex-thread-index.test.mjs +164 -0
  86. package/src/codex-usage.mjs +110 -0
  87. package/src/codex-usage.test.mjs +140 -0
  88. package/src/codex-visibility-smoke.test.mjs +222 -0
  89. package/src/codex-vscode-restore-smoke.mjs +206 -0
  90. package/src/codex-vscode-restore-smoke.test.mjs +325 -0
  91. package/src/codex-vscode-rollback-smoke.mjs +90 -0
  92. package/src/codex-vscode-rollback-smoke.test.mjs +290 -0
  93. package/src/db-schema.test.mjs +96 -0
  94. package/src/db.mjs +14 -1
  95. package/src/haiku-summarizer.mjs +267 -26
  96. package/src/haiku-summarizer.test.mjs +282 -0
  97. package/src/handoff-preview.test.mjs +108 -0
  98. package/src/handoff-record.mjs +294 -0
  99. package/src/handoff-record.test.mjs +226 -0
  100. package/src/hook-entrypoints.test.mjs +286 -0
  101. package/src/package-files.test.mjs +19 -0
  102. package/src/prompt-submit.mjs +9 -6
  103. package/src/resume-context.mjs +58 -171
  104. package/src/resume-context.test.mjs +177 -0
  105. package/src/session-start.mjs +85 -26
  106. package/src/state-file.mjs +50 -6
  107. package/src/state-file.test.mjs +50 -0
  108. package/src/token-monitor.mjs +14 -10
  109. package/src/token-monitor.test.mjs +27 -0
  110. package/src/trim-cli.test.mjs +1584 -0
  111. package/src/trim-model.mjs +584 -0
  112. package/src/trim-model.test.mjs +568 -0
  113. package/src/turn-processor.mjs +17 -10
  114. package/src/vscode-task.mjs +33 -10
  115. package/src/vscode-task.test.mjs +19 -9
  116. package/src/cli/save-inflight.mjs +0 -81
@@ -0,0 +1,455 @@
1
+ # Throughline: ロールバックによるコンテキスト整理の気づき
2
+
3
+ ## この文書の位置づけ
4
+
5
+ この文書は **rollback-based context trim の設計メモ** です。
6
+
7
+ 関連文書:
8
+
9
+ | 文書 | 役割 |
10
+ |---|---|
11
+ | [THROUGHLINE_CODEX_FIRST_ROADMAP.md](THROUGHLINE_CODEX_FIRST_ROADMAP.md) | 2026-05-06 以降の次フェーズ計画。Codex primary と Codex Rewind 互換を先行する |
12
+ | [THROUGHLINE_CODEX_TRIM_ROLLBACK_FIX_PLAN.md](THROUGHLINE_CODEX_TRIM_ROLLBACK_FIX_PLAN.md) | 2026-05-06 incident 後の修正計画。2026-05-08 の controlled smoke 後、automatic mutation は再有効化し、restore-safety / host primitive audit は diagnostics として残す |
13
+ | [THROUGHLINE_CODEX_TRIM_IMPLEMENTATION_PLAN.md](THROUGHLINE_CODEX_TRIM_IMPLEMENTATION_PLAN.md) | この気づきと Claude / Codex 両対応計画を統合した旧計画と実装履歴。完了済み根拠として参照する |
14
+ | [THROUGHLINE_CODEX_DUAL_SUPPORT.md](THROUGHLINE_CODEX_DUAL_SUPPORT.md) | Throughline を Claude primary のまま Codex adapter / sidecar に対応させる architecture brief |
15
+
16
+ この文書は「rollback は欠けていた delete primitive かもしれない」という洞察を残すもの。実装時は、未検証の host primitive を本線仕様にせず、次フェーズ計画 [THROUGHLINE_CODEX_FIRST_ROADMAP.md](THROUGHLINE_CODEX_FIRST_ROADMAP.md) の Codex Rewind 互換 Phase で実測してから本線 UX に進む。
17
+
18
+ 2026-05-06 update: Codex app-server の `thread/rollback` / `thread/inject_items` は live host primitive として実測済み。Throughline CLI には明示 `--codex-thread-id` または `THROUGHLINE_CODEX_THREAD_ID` / `CODEX_THREAD_ID` による current-thread identity、rollout/app-server turn count guard、guarded execute が入った。
19
+
20
+ 2026-05-07 correction: VS Code restart / reconnect 後に rollback 済み user prompt が復活したように見える incident が起きたため、Codex rollback / inject は restart-safe な context trim primitive としていったん未証明へ戻した。特に `compacted.replacement_history` など、live app-server read/resume 以外の restore source を検証するまで、`$throughline` と Codex Stop hook auto-refresh は mutation を自動実行しない方針にした。Claude `/rewind` 自動化はまだ有効化しない。
21
+
22
+ 2026-05-08 unblock: その後の切り分けで、incident thread の retained text は app-server response 上では `aggregatedOutput` など quoted/tool-output field に分類され、controlled rollback model-visible smoke は app-server restart 境界と VS Code reload/reconnect 境界の両方で `not-reproduced` だった。これを受け、Codex `trim --execute --host codex` と Stop hook auto-refresh の過剰 blocker は解除する。`compacted.replacement_history` retention、restore-safety risk、host primitive audit は引き続き diagnostics だが、単独では mutation 前 refusal にしない。DB memory 不在と rollout/app-server turn-count 不一致は引き続き mutation 前 blocker。
23
+
24
+ 2026-05-07 host primitive audit: `throughline codex-host-primitive-audit` で installed Codex app-server schema を機械監査した。`thread/rollback` / `thread/inject_items` / `thread/compact/start` / `thread/start` / `thread/fork` / `thread/resume` は存在するが、rollback 済み user text を current-thread の model-visible input へ復活させない deletion / isolation / projection primitive は見つからなかった。`thread/resume(history)` は schema 上 `[UNSTABLE] FOR CODEX CLOUD - DO NOT USE` で、`thread_id` も ignored になるため、Throughline の current-thread repair primitive には採用しない。
25
+
26
+ ## 概要
27
+
28
+ Throughline はもともと、ホスト側の制約に対する回避策として作られた。
29
+
30
+ - Claude Code は、蓄積されたモデル可視コンテキストをユーザーが直接編集できなかった。
31
+ - 長いセッションでは、重い tool I/O がコンテキストの大部分を占める。
32
+ - `/clear` や新規セッションでは、有用な作業記憶まで失われる。
33
+ - Throughline は、有用な記憶を外部DBへ保存し、明示的な baton によって次セッションへ引き継ぐことでこれを解決した。
34
+
35
+ 今日の重要な気づき:
36
+
37
+ > conversation-only rollback が使えるなら、rollback は「欠けていた delete primitive」になる。
38
+
39
+ これにより、Throughline の将来像が変わる可能性がある。
40
+
41
+ 従来:
42
+
43
+ ```text
44
+ 旧セッション
45
+ -> /tl baton
46
+ -> /clear または新規セッション
47
+ -> 次セッションが curated memory を受け取る
48
+ ```
49
+
50
+ 新しい可能性:
51
+
52
+ ```text
53
+ 同一セッション / 同一スレッド
54
+ -> 全turn履歴を Throughline DB に保存
55
+ -> conversation context を開始直後まで rollback
56
+ -> curated L1/L2 memory を再注入
57
+ -> 同じセッション / スレッドで続行
58
+ ```
59
+
60
+ これが成立すると、Throughline は「セッション間ハンドオフツール」から、「モデル可視コンテキストの外部マネージャ」へ進化できる。
61
+
62
+ ## なぜ重要か
63
+
64
+ 元の Throughline 設計は間違っていたわけではない。ホスト側に制約があった。
65
+
66
+ 本質的な問題は「セッションをまたぐ必要があること」ではなく、次の一点だった。
67
+
68
+ ```text
69
+ モデルが見る蓄積コンテキストを、ユーザーが直接編集できない。
70
+ ```
71
+
72
+ Throughline はこの問題を、記憶をホストの外へ逃がすことで解いた。
73
+
74
+ - L1: 古いturnの一行要約
75
+ - L2: 最近の user / assistant テキスト
76
+ - L3: tool I/O、コマンド出力、thinking、system/tool noise などの重い詳細
77
+
78
+ 重要なのは、Throughline が価値ある記憶を SQLite に保持していること。
79
+
80
+ もしホストが conversation-only rollback を提供するなら、Throughline は rollback を使ってモデル可視履歴を削除し、自前DBから必要な記憶だけを戻せる。
81
+
82
+ ## 標準コンパクションのもったいなさ
83
+
84
+ 標準 compaction は便利だが、Throughline の目的とは相性が悪い。
85
+
86
+ Throughline が避けたいのは、重い tool I/O をもう一度モデルに読ませること。
87
+
88
+ 標準 compaction は多くの場合、次のような流れになる。
89
+
90
+ ```text
91
+ 重い蓄積コンテキスト
92
+ -> モデルに送る
93
+ -> モデルが要約する
94
+ -> 軽いコンテキストとして残す
95
+ ```
96
+
97
+ つまり、トークンを節約するためにトークンを燃やす。
98
+
99
+ Throughline では、必要な記憶はすでに構造化して保存されている。
100
+
101
+ - user / assistant text はそのまま保存できる
102
+ - tool I/O は SQLite に退避できる
103
+ - summary は lazy に作れる
104
+ - detail は必要なときだけ取り出せる
105
+
106
+ したがって理想は「現在のコンテキストを要約する」ことではない。
107
+
108
+ 理想はこれ。
109
+
110
+ ```text
111
+ 現在の model-visible context を、モデルに再読させずに削除または置換する。
112
+ ```
113
+
114
+ ## 欲しいプリミティブ
115
+
116
+ 理想のホストAPIは、たとえば次のようなもの。
117
+
118
+ ```text
119
+ thread/context/replace
120
+ ```
121
+
122
+ または:
123
+
124
+ ```text
125
+ thread/context/edit
126
+ ```
127
+
128
+ ほしい意味論:
129
+
130
+ - UI履歴は残す
131
+ - audit log / transcript / rollout record は残す
132
+ - ローカルファイル変更は維持する
133
+ - 次回モデルへ渡す履歴だけを置換する
134
+ - 外部ツールが生成した memory items を受け取れる
135
+ - rollback / restore 用のメタデータを残せる
136
+
137
+ つまり:
138
+
139
+ ```text
140
+ UI履歴は残る。
141
+ 詳細ログも残る。
142
+ Throughline DBも残る。
143
+ 次の model input だけが変わる。
144
+ ```
145
+
146
+ ## Codex で見えたこと
147
+
148
+ Codex には、関連する app-server primitive がいくつかある。
149
+
150
+ Codex CLI / app-server documentation から確認できたもの:
151
+
152
+ - `thread/read`
153
+ - `thread/turns/list`
154
+ - `thread/compact/start`
155
+ - `thread/rollback`
156
+ - `thread/inject_items`
157
+ - `turn/start`
158
+ - `turn/steer`
159
+ - `thread/resume`
160
+ - `thread/fork`
161
+
162
+ 重要な挙動:
163
+
164
+ - `thread/compact/start` は、同一 thread の手動 history compaction を起動する。
165
+ - `thread/inject_items` は、raw Responses API items を loaded thread の model-visible history へ追加する。
166
+ - `thread/rollback` は、最後の N turns を agent の in-memory context から落とし、future resume でも pruned history が見えるよう rollback marker を永続化する。
167
+ - `ThreadRollbackParams` は `threadId` と `numTurns` だけを持つ。
168
+ - rollback の説明では、thread history のみを変更し、ローカルファイル変更は戻さないとされている。
169
+
170
+ つまり Codex の `thread/rollback` は、かなり conversation-only rollback に近い。
171
+
172
+ 2026-05-06 の実測:
173
+
174
+ - Codex CLI `0.128.0-alpha.1` で `stdio://` app-server は newline-delimited JSON request / response で操作できた。
175
+ - `thread/read includeTurns:true` は persisted thread を読める。
176
+ - `thread/rollback` は loaded thread にだけ効く。persisted thread に直接呼ぶと `thread not found`。
177
+ - `thread/resume` 後に `thread/rollback { numTurns: 1 }` を呼ぶと、1 turn の検証 thread は 0 turns になった。
178
+ - rollback 後に `thread/inject_items` へ developer message item を入れ、次の `turn/start` で marker `TL_PHASE6_INJECT_OK` が model-visible になった。
179
+ - rollout JSONL には injected item が developer role の response item として記録された。
180
+
181
+ 残る未検証は、複数 turn の partial rollback、rollback marker の resume 後挙動、ローカルファイル変更が戻らないことの実ファイル付き smoke、UI 表示差分。
182
+
183
+ 参考:
184
+
185
+ - <https://github.com/openai/codex/blob/main/codex-rs/app-server/README.md>
186
+ - <https://pkg.go.dev/github.com/tta-lab/codex-server-go/protocol#ThreadRollbackParams>
187
+
188
+ ## Claude で見えたこと
189
+
190
+ Claude Code にも rollback / rewind mechanism がある。
191
+
192
+ Claude Code の `/rewind` は以下をサポートする。
193
+
194
+ - conversation only restore
195
+ - code only restore
196
+ - both code and conversation restore
197
+ - selected point 以降の summarize
198
+
199
+ つまり Claude には、手動UXとして conversation-only rollback がすでにある。
200
+
201
+ 参考:
202
+
203
+ - <https://code.claude.com/docs/en/checkpointing>
204
+
205
+ 違いは、自動化できる表面にありそう。
206
+
207
+ - Claude は `/rewind` による手動UXが強い。
208
+ - Codex は `thread/rollback` が app-server/API primitive として見えているため、外部ツールから自動化しやすい可能性がある。
209
+
210
+ 概念的な綺麗さは、Claude も Codex も同じ。
211
+
212
+ ## 新しい中核アーキテクチャ
213
+
214
+ 新しいアーキテクチャ:
215
+
216
+ ```text
217
+ host session / thread
218
+ |
219
+ v
220
+ Throughline event / log watcher
221
+ |
222
+ v
223
+ Throughline SQLite DB
224
+ - L1 skeletons
225
+ - L2 bodies
226
+ - L3 details
227
+ |
228
+ v
229
+ conversation-only rollback
230
+ |
231
+ v
232
+ curated memory injection
233
+ |
234
+ v
235
+ same session / thread continues
236
+ ```
237
+
238
+ ホストの session は作業の器になる。
239
+
240
+ Throughline DB は永続記憶になる。
241
+
242
+ rollback は delete operation になる。
243
+
244
+ injection は restore operation になる。
245
+
246
+ ## Codex 候補フロー
247
+
248
+ Codex で考えられる流れ:
249
+
250
+ ```text
251
+ 1. Throughline が Codex log または app-server events から各turnを捕捉する。
252
+ 2. Throughline が thread_id、turn index、timestamp、text、details を記録する。
253
+ 3. ユーザーまたは自動処理が /tl-trim を起動する。
254
+ 4. Throughline が現在 thread の保存済みturn数を数える。
255
+ 5. Throughline が L1/L2/L3 から curated context を組み立てる。
256
+ 6. Codex app-server に以下を送る。
257
+ thread/rollback { threadId, numTurns: saved_turn_count }
258
+ 7. Codex の model-visible history が pruned される。
259
+ 8. Throughline が以下を送る。
260
+ thread/inject_items { threadId, items: curated_memory_items }
261
+ 9. ユーザーは同じ Codex thread で続行する。
262
+ ```
263
+
264
+ 現行実装では、Codex Stop hook 後の 90% automatic refresh は guarded rollback / inject
265
+ mutation を試行する。明示 CLI の `throughline trim --execute --host codex --codex-thread-id <id>`
266
+ も env gate なしで実行する。明示 Codex thread identity、injectable Throughline DB memory、
267
+ rollout/app-server turn count guard は live mutation の最低条件であり、durable success は
268
+ post-execute rollout evidence で `execute-durable-verified` として別判定する。
269
+
270
+ 重要な考え:
271
+
272
+ ```text
273
+ Throughline は毎turnを記録しているため、安全に rollback できる最大turn数を自分で知っている。
274
+ ```
275
+
276
+ ただし、L1 / L2 を戻すだけでは足りない。
277
+
278
+ 初期 Throughline で実際に落とし穴になったのは、「DB から L1 / L2 は確かに引き継がれるが、モデルがそれを **今やっている作業** として認識しない」ことだった。現行 `/tl` はこの問題を、旧セッションの Claude 自身に in-flight memo(次の一手 / 方針 / 未解決 / TODO)を書かせ、その memo を resume context の先頭に置くことで解消している。
279
+
280
+ ただし、memo を先頭に置くこと自体が唯一のスマートな解ではない。Claude / Codex の通常 context では、任意の text に「これは今取り込み中」という隠し属性が付くわけではなく、role / instruction authority、現在 turn への近さ、section boundary、metadata、最新 user request との接続によって、モデルが作業文脈として読む。したがって Throughline は、rollback 後に注入する curated memory を「過去ログ」ではなく「現在タスクに使う作業コンテキスト」として明示し、冒頭と末尾の両方に読み方を置く必要がある。
281
+
282
+ `/tl-trim` でも同じ制約を維持する。rollback 後に注入する curated memory は、L1 / L2 / L3 references だけでなく、current-work memo、active work thread framing、後続行優先 / supersession rule を含める。これを省くと、trim 後のモデルは「過去ログは読めるが、作業の続きとして走れない」状態に戻る。
283
+
284
+ ## Claude 候補フロー
285
+
286
+ Claude で考えられる流れ:
287
+
288
+ ```text
289
+ 1. Throughline が hooks / transcript processing で毎turnを捕捉する。
290
+ 2. ユーザーが trim operation を起動する。
291
+ 3. Claude conversation を conversation-only rewind で戻す。
292
+ 4. Throughline が curated context を再注入する。
293
+ 5. ユーザーは同じ Claude session で続行する。
294
+ ```
295
+
296
+ 未解決なのは、Claude の conversation-only rewind を外部ツールから綺麗に自動化できるか。
297
+
298
+ できない場合、UX は手動寄りになる。
299
+
300
+ ```text
301
+ /rewind conversation only
302
+ /tl restore
303
+ ```
304
+
305
+ または Throughline がこの手順をユーザーに案内する。
306
+
307
+ ## Throughline がすでに重要データを持っている理由
308
+
309
+ Throughline は毎turnを捕捉する。
310
+
311
+ したがって次の情報を知っている。
312
+
313
+ - current session id
314
+ - current thread id
315
+ - captured turn count
316
+ - どのturnが L1 / L2 / L3 か
317
+ - どの details が退避済みか
318
+ - 安全に rollback できる最大深度
319
+ - 再開に必要な curated memory
320
+
321
+ これは rollback-based trimming に必要な情報そのもの。
322
+
323
+ rollback 後にホストがすべてを覚えている必要はない。Throughline が覚えている。
324
+
325
+ ## 設計の変化
326
+
327
+ 旧アイデンティティ:
328
+
329
+ ```text
330
+ Throughline は、セッション間の明示的ハンドオフシステム。
331
+ ```
332
+
333
+ 新しい可能性:
334
+
335
+ ```text
336
+ Throughline は、model-visible context の外部マネージャ。
337
+ ```
338
+
339
+ 旧中核操作:
340
+
341
+ ```text
342
+ memory を保存する
343
+ -> clear / new session
344
+ -> memory を注入する
345
+ ```
346
+
347
+ 新中核操作:
348
+
349
+ ```text
350
+ memory を保存する
351
+ -> conversation を rollback する
352
+ -> memory を注入する
353
+ ```
354
+
355
+ これは大きなUX改善になる。
356
+
357
+ - 新規セッションが不要
358
+ - 通常ケースでは session baton が不要
359
+ - 標準 compaction によるトークン消費が不要
360
+ - current session / thread に閉じるため、誤継承が起きにくい
361
+ - 詳細記憶は必要なときだけ取得できる
362
+
363
+ ## 重要な注意点
364
+
365
+ まだ検証が必要なこと。
366
+
367
+ Codex:
368
+
369
+ - 複数 turn thread で `thread/rollback` の partial `numTurns` が期待通りに効くか。
370
+ - `thread/rollback` は thread 内の全turn数と同じ `numTurns` を受け入れるか。単一 turn の full rollback は成功済み。
371
+ - `thread/inject_items` は次の model-visible context に確実に入るか。developer message item では成功済み。
372
+ - `thread/inject_items` が受け付ける Responses API item 形式の許容範囲は何か。developer message item は成功済み。
373
+ - rollback 後、UI履歴はどう見えるか。消えるのか、marker付きで残るのか。
374
+ - rollback marker は `thread/resume` 後も期待通りに効くか。
375
+ - full rollback が token accounting を壊したり、不要な auto-compaction を誘発しないか。
376
+ - 実行中turnでも可能か、それとも idle 状態でのみ可能か。
377
+
378
+ Claude:
379
+
380
+ - `/rewind` conversation-only を自動化できるか。
381
+ - VS Code extension でも十分な制御ができるか、それとも CLI が必要か。
382
+ - 新規セッションなしで Throughline memory を再注入できるか。
383
+ - UI automation に頼らず、安全で反復可能な操作にできるか。
384
+
385
+ ## コマンド候補
386
+
387
+ 将来のコマンド名候補:
388
+
389
+ ```text
390
+ /tl-trim
391
+ /tl-prune
392
+ /tl-rollback
393
+ /tl-repack
394
+ /tl-context
395
+ ```
396
+
397
+ 想定する意味:
398
+
399
+ ```text
400
+ /tl-trim
401
+ 現在状態を Throughline DB に保存する。
402
+ model-visible conversation を安全な範囲まで rollback する。
403
+ curated memory を注入する。
404
+ 同じ session / thread で続行する。
405
+ ```
406
+
407
+ バリエーション候補:
408
+
409
+ ```text
410
+ /tl-trim --keep-recent 20
411
+ /tl-trim --all
412
+ /tl-trim --dry-run
413
+ /tl-trim --detail-on-demand
414
+ /tl-trim --no-summary
415
+ ```
416
+
417
+ ## 次の作業
418
+
419
+ 推奨する次の検証:
420
+
421
+ 1. 小さな Codex app-server integration harness を Throughline 側に作る。
422
+ 2. test thread を開始し、数turn実行する。
423
+ 3. turn を最小Throughline風DBまたはJSONへ捕捉する。
424
+ 4. `thread/rollback` を partial `numTurns` で呼ぶ。
425
+ 5. `thread/rollback` を full `numTurns` で呼ぶ。
426
+ 6. rollback 後に `thread/read includeTurns:true` を確認する。
427
+ 7. rollback 後の rollout JSONL を確認する。
428
+ 8. `thread/inject_items` に simple curated memory item を渡す。
429
+ 9. 新しいturnを開始し、モデルが injected memory を見ているか確認する。
430
+ 10. rollback + injection 後の resume 挙動を確認する。
431
+
432
+ 成功条件:
433
+
434
+ ```text
435
+ full または near-full rollback が動く。
436
+ ローカルファイル変更は維持される。
437
+ injected curated memory が次の model turn から見える。
438
+ Throughline が対象 Codex thread を明示的に識別でき、誤 thread を rollback しない。
439
+ 標準 compaction は不要。
440
+ 同じ thread / session で続行できる。
441
+ ```
442
+
443
+ これが通るなら、Throughline は session handoff を超えて進化できる。
444
+
445
+ ## 一行の気づき
446
+
447
+ ```text
448
+ Rollback is the missing delete primitive.
449
+ ```
450
+
451
+ 日本語で言うなら:
452
+
453
+ ```text
454
+ ロールバックは、欠けていた「削除」プリミティブだった。
455
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "throughline",
3
- "version": "0.3.24",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Claude Code hooks plugin for structured context compression (/clear-safe persistent memory)",
6
6
  "keywords": [
@@ -21,12 +21,16 @@
21
21
  "files": [
22
22
  "bin/",
23
23
  "src/",
24
+ "codex/skills/",
24
25
  ".claude/commands/",
26
+ ".codex-sidecar.yml",
27
+ "docs/",
28
+ "CHANGELOG.md",
25
29
  "README.md",
26
30
  "LICENSE"
27
31
  ],
28
32
  "scripts": {
29
- "test": "node --test src/*.test.mjs"
33
+ "test": "node --test src/*.test.mjs src/cli/*.test.mjs"
30
34
  },
31
35
  "engines": {
32
36
  "node": ">=22.5"
package/src/baton.mjs CHANGED
@@ -1,19 +1,20 @@
1
1
  /**
2
2
  * baton.mjs — 引き継ぎバトン管理
3
3
  *
4
- * バトン方式の設計 (docs/INHERITANCE_ON_CLEAR_ONLY.md):
5
- * - ユーザーが旧セッションで /tl スラッシュコマンドを打つ UserPromptSubmit hook が
4
+ * バトン方式の設計 (docs/THROUGHLINE_CLEAR_AUTO_HANDOFF_PLAN.md):
5
+ * - 新仕様では `/clear` 自動引継ぎがデフォルト ON。バトンは「/clear 自動引継ぎを
6
+ * 使わずに明示的に引き継ぎたい」ユーザーのための逃げ道。
7
+ * - ユーザーが旧セッションで `/tl` スラッシュコマンドを打つ → UserPromptSubmit hook が
6
8
  * baton テーブルに (project_path, session_id, created_at) を INSERT OR REPLACE
7
- * - 新セッションの SessionStart hook が baton を消費:
8
- * TTL 1 時間以内 かつ session_id が自分自身でない → 前任として merge
9
- * 期限切れ or 自己指名 破棄
10
- * - 消費は atomic (BEGIN IMMEDIATE トランザクション内で SELECT + DELETE)
9
+ * - 新セッションの SessionStart hook が baton を atomic に消費 (BEGIN IMMEDIATE 内で
10
+ * SELECT + DELETE)。TTL 1 時間以内なら前任として merge、超過は破棄。
11
+ * - 注入する curated memory は L1 + L2 + L3 refs のみ (memo / thinking なし)。
11
12
  *
12
- * なぜバトン方式か:
13
- * - VSCode 拡張では SessionStart payload の source が /clear 後も "startup" に潰される
14
- * ため source 値だけで /clear を識別できない (GitHub issue #49937)
15
- * - 時間差ヒューリスティック (案 D) は誤爆の可能性があり、ユーザー明示の意思表示を
16
- * 引き継ぎ発火の唯一の条件とする方が決定論的
13
+ * 履歴: もともと VSCode 拡張で SessionStart payload の source が /clear 後も
14
+ * "startup" に潰される問題 (#49937) に対する明示意思マーカーとして導入。
15
+ * 2026-05-08 時点で Claude Code 2.1.128 で source='clear' reliable
16
+ * なったため auto path 中心の設計に変わったが、明示意思の signal として
17
+ * baton 仕組み自体は残す。詳細は docs/THROUGHLINE_CLEAR_AUTO_HANDOFF_PLAN.md。
17
18
  */
18
19
 
19
20
  /**
@@ -24,9 +25,7 @@ export const BATON_TTL_MS = 60 * 60 * 1000; // 1 時間
24
25
 
25
26
  /**
26
27
  * 現在セッション (= /tl を発動したセッション) を次回 SessionStart で merge 対象に指名する。
27
- * 同 project_path の既存バトンがあれば session_id / created_at のみ上書き。
28
- * v7 で追加された memo_text は保持する(連続した /tl → save-inflight の順番で
29
- * 呼ばれた場合に、再度 /tl を打った時点で古い memo が消えないようにする)。
28
+ * 同 project_path の既存バトンがあれば session_id / created_at を上書き。
30
29
  *
31
30
  * @param {import('node:sqlite').DatabaseSync} db
32
31
  * @param {{ projectPath: string, sessionId: string, now?: number }} params
@@ -41,43 +40,17 @@ export function writeBaton(db, { projectPath, sessionId, now = Date.now() }) {
41
40
  ).run(projectPath, sessionId, now);
42
41
  }
43
42
 
44
- /**
45
- * 既存バトンの memo_text を更新する。バトンが存在しない場合は NOOP。
46
- * /tl 発動後、現行セッションの Claude が `throughline save-inflight` CLI 経由で
47
- * 呼び出す。memo_text は Markdown 形式の「次の一手 / 現在の方針 / 未解決 /
48
- * 進行中 TODO」をまとめたテキスト。
49
- *
50
- * Windows 互換: ドライブレター(`C:` / `c:`)やパス区切りの差異で
51
- * /tl 書き込み時と save-inflight 呼び出し時の project_path が一致しない
52
- * ケースがあるため、SQLite の COLLATE NOCASE で大小無視で照合する。
53
- *
54
- * @param {import('node:sqlite').DatabaseSync} db
55
- * @param {{ projectPath: string, memoText: string, now?: number }} params
56
- * @returns {{ updated: boolean }}
57
- */
58
- export function updateBatonMemo(db, { projectPath, memoText }) {
59
- const result = db
60
- .prepare(
61
- `UPDATE handoff_batons SET memo_text = ? WHERE project_path = ? COLLATE NOCASE`,
62
- )
63
- .run(memoText, projectPath);
64
- return { updated: (result.changes ?? 0) > 0 };
65
- }
66
-
67
43
  /**
68
44
  * 同 project_path のバトンを読み出して削除する (atomic)。
69
45
  *
70
46
  * 戻り値:
71
- * - { sessionId, ageMs, memoText } : バトン存在 かつ TTL 以内
72
- * - { sessionId: null, skipReason: 'expired', ageMs } : TTL 超過で破棄
47
+ * - { sessionId, ageMs } : バトン存在 かつ TTL 以内
48
+ * - { sessionId: null, skipReason: 'expired', ageMs } : TTL 超過で破棄
73
49
  * - { sessionId: null, skipReason: 'missing' } : バトン無し
74
50
  *
75
- * memoText は /tl 後に save-inflight で書き込まれた in-flight メモ。
76
- * 未保存なら null。
77
- *
78
51
  * @param {import('node:sqlite').DatabaseSync} db
79
52
  * @param {{ projectPath: string, now?: number, ttlMs?: number }} params
80
- * @returns {{ sessionId: string | null, ageMs?: number, memoText?: string | null, skipReason?: 'expired' | 'missing' }}
53
+ * @returns {{ sessionId: string | null, ageMs?: number, skipReason?: 'expired' | 'missing' }}
81
54
  */
82
55
  export function consumeBaton(db, { projectPath, now = Date.now(), ttlMs = BATON_TTL_MS }) {
83
56
  db.exec('BEGIN IMMEDIATE');
@@ -85,7 +58,7 @@ export function consumeBaton(db, { projectPath, now = Date.now(), ttlMs = BATON_
85
58
  // Windows 互換: ドライブレターの大小差を吸収するため COLLATE NOCASE
86
59
  const row = db
87
60
  .prepare(
88
- `SELECT session_id, created_at, memo_text FROM handoff_batons WHERE project_path = ? COLLATE NOCASE`,
61
+ `SELECT session_id, created_at FROM handoff_batons WHERE project_path = ? COLLATE NOCASE`,
89
62
  )
90
63
  .get(projectPath);
91
64
 
@@ -108,7 +81,6 @@ export function consumeBaton(db, { projectPath, now = Date.now(), ttlMs = BATON_
108
81
  return {
109
82
  sessionId: row.session_id,
110
83
  ageMs,
111
- memoText: row.memo_text ?? null,
112
84
  };
113
85
  } catch (err) {
114
86
  try {
@@ -1,7 +1,7 @@
1
1
  import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { DatabaseSync } from 'node:sqlite';
4
- import { writeBaton, consumeBaton, updateBatonMemo, BATON_TTL_MS } from './baton.mjs';
4
+ import { writeBaton, consumeBaton, BATON_TTL_MS } from './baton.mjs';
5
5
 
6
6
  function makeDb() {
7
7
  const db = new DatabaseSync(':memory:');
@@ -9,8 +9,7 @@ function makeDb() {
9
9
  CREATE TABLE handoff_batons (
10
10
  project_path TEXT PRIMARY KEY,
11
11
  session_id TEXT NOT NULL,
12
- created_at INTEGER NOT NULL,
13
- memo_text TEXT
12
+ created_at INTEGER NOT NULL
14
13
  );
15
14
  `);
16
15
  return db;
@@ -99,46 +98,10 @@ test('consumeBaton: scopes per project_path (does not cross-consume)', () => {
99
98
  assert.equal(rows.length, 1);
100
99
  });
101
100
 
102
- test('updateBatonMemo: writes memo_text into existing baton', () => {
103
- const db = makeDb();
104
- writeBaton(db, { projectPath: '/proj', sessionId: 'S1', now: 1000 });
105
- const result = updateBatonMemo(db, { projectPath: '/proj', memoText: '次の一手: X' });
106
- assert.equal(result.updated, true);
107
- const row = db.prepare('SELECT memo_text FROM handoff_batons').get();
108
- assert.equal(row.memo_text, '次の一手: X');
109
- });
110
-
111
- test('updateBatonMemo: is a NOOP when no baton exists (no throw)', () => {
112
- const db = makeDb();
113
- const result = updateBatonMemo(db, { projectPath: '/missing', memoText: 'hello' });
114
- assert.equal(result.updated, false);
115
- });
116
-
117
- test('writeBaton: preserves memo_text when same project_path is re-batoned', () => {
118
- const db = makeDb();
119
- writeBaton(db, { projectPath: '/proj', sessionId: 'S1', now: 1000 });
120
- updateBatonMemo(db, { projectPath: '/proj', memoText: 'preserved memo' });
121
- // 同じ project_path に再度 /tl を打っても memo は残る
122
- writeBaton(db, { projectPath: '/proj', sessionId: 'S2', now: 2000 });
123
- const row = db.prepare('SELECT * FROM handoff_batons').get();
124
- assert.equal(row.session_id, 'S2');
125
- assert.equal(row.created_at, 2000);
126
- assert.equal(row.memo_text, 'preserved memo');
127
- });
128
-
129
- test('consumeBaton: returns memoText when set', () => {
130
- const db = makeDb();
131
- writeBaton(db, { projectPath: '/proj', sessionId: 'S1', now: 1000 });
132
- updateBatonMemo(db, { projectPath: '/proj', memoText: '中断メモ' });
133
- const result = consumeBaton(db, { projectPath: '/proj', now: 1000 + 1000 });
134
- assert.equal(result.sessionId, 'S1');
135
- assert.equal(result.memoText, '中断メモ');
136
- });
137
-
138
- test('consumeBaton: returns memoText=null when not set', () => {
101
+ test('consumeBaton: returns no memoText property in v8 (memo column dropped)', () => {
139
102
  const db = makeDb();
140
103
  writeBaton(db, { projectPath: '/proj', sessionId: 'S1', now: 1000 });
141
104
  const result = consumeBaton(db, { projectPath: '/proj', now: 1000 + 1000 });
142
105
  assert.equal(result.sessionId, 'S1');
143
- assert.equal(result.memoText, null);
106
+ assert.equal('memoText' in result, false, 'memoText property should be removed in v8');
144
107
  });