throughline 0.12.8 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -1
- package/README.ja.md +20 -0
- package/README.md +20 -0
- package/bin/throughline.mjs +10 -1
- package/docs/02_clear_auto_handoff_plan.md +2 -1
- package/docs/05_codex_first_roadmap.md +2 -0
- package/docs/adr/0031-serialized-schema-migration.md +43 -0
- package/docs/adr/0032-claude-compact-continuation.md +88 -0
- package/package.json +1 -1
- package/rag/01-hooks/raw/claude-compact-hooks-extract.md +56 -0
- package/rag/INDEX.md +4 -0
- package/src/claude-auto-handoff-config.mjs +38 -0
- package/src/claude-auto-handoff.mjs +207 -0
- package/src/claude-auto-handoff.test.mjs +308 -0
- package/src/claude-pre-compact.mjs +55 -0
- package/src/cli/auto-handoff.mjs +43 -5
- package/src/cli/install.mjs +28 -0
- package/src/cli/install.test.mjs +6 -0
- package/src/db-contention.test.mjs +27 -0
- package/src/db.mjs +62 -10
- package/src/hook-entrypoints.test.mjs +3 -1
- package/src/hook-failure-log.mjs +17 -1
- package/src/hook-failure-log.test.mjs +36 -2
- package/src/hosts/cursor.test.mjs +10 -0
- package/src/hosts/index.mjs +9 -1
- package/src/resume-context.mjs +101 -5
- package/src/resume-context.test.mjs +72 -0
- package/src/runtime-error-store.mjs +6 -0
- package/src/session-start.mjs +33 -1
- package/src/transcript-reader.mjs +58 -2
- package/src/transcript-reader.test.mjs +90 -0
- package/src/turn-backfill.mjs +7 -2
- package/src/turn-backfill.test.mjs +58 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,38 @@ shipped to npm but were not individually tagged on GitHub.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.13.0] — 2026-10-04
|
|
14
|
+
|
|
15
|
+
### 追加
|
|
16
|
+
|
|
17
|
+
- Claude Codeの自動継続([ADR 0032](docs/adr/0032-claude-compact-continuation.md))。`throughline auto-handoff enable --host claude [--project <path>]`で有効にすると、自動圧縮の直後に、圧縮前の記録を同じ会話へ注入する。注入するのは、作業途中のユーザー依頼と圧縮直前の発言、直近の会話の原文(上限9,500字)。モデルは追加の入力なしで作業を続ける。既定は無効。手動`/compact`は対象外。
|
|
18
|
+
- Claude Codeは、hookから作業を止めることも、新しい会話へ指示を送ることもできない。Codexのように新しいタスクへは切り替えず、hostが続ける同じ会話へ記憶を渡す。
|
|
19
|
+
- `throughline install`は、Claudeの`PreCompact`フック(`throughline pre-compact`)を登録する。自動継続が無効の時は、判定を記録して抜ける。このフックは圧縮を止めない。
|
|
20
|
+
- `auto-handoff`に`--host claude`を足す。使えるのは`enable`・`disable`・`status`。`--host`を省いた時は今までどおりCodex。
|
|
21
|
+
|
|
22
|
+
### 修正
|
|
23
|
+
|
|
24
|
+
- 圧縮をまたいだClaudeのターンが、複数のターンに割れて保存されていた不具合を直す。Claude Codeは圧縮の直後に、要約を本文に持つ`user`行(`isCompactSummary: true`)をtranscriptへ書く。0.12.9以前はこの行をユーザーの発言として数え、要約をユーザー発言としてL2へ保存していた。3回圧縮した実機のターンは4ターンとして保存された。圧縮より前のtool入出力はL3に入らなかった。
|
|
25
|
+
- 修正後は、元の依頼と最終回答の1ターンとして保存し、圧縮より前のtool入出力も同じターンのL3へ入れる。Observer feedの`turn_start`は元の依頼のものになる(今までは要約行のターンとして`unknown`)。修正より前に保存した行は変えない。
|
|
26
|
+
|
|
27
|
+
### 確認した範囲
|
|
28
|
+
|
|
29
|
+
- LinuxのClaude Code 2.1.289(Haiku 4.5)。`claude -p`と対話画面のそれぞれで、自動圧縮2回をまたいで作業が完了し、圧縮のたびに注入が届いた。9,099字の注入はfile化されずに届いた。
|
|
30
|
+
- macOS・Windows、VS Code拡張・Desktop、subagentの中の圧縮は確かめていない。
|
|
31
|
+
|
|
32
|
+
## [0.12.9] — 2026-10-04
|
|
33
|
+
|
|
34
|
+
### 修正
|
|
35
|
+
|
|
36
|
+
- 新しいDBを複数のhookが同時に開くと、どれかが`no such table: judgments`か`database is locked`で落ちる不具合を直す([ADR 0031](docs/adr/0031-serialized-schema-migration.md))。WindowsのCursorの会話では同じhookが2本ほぼ同時に走るので、新しい端末の最初の会話で起きる。schemaの移行は、書き込みlockを取ってから版を読み直し、1つのtransactionで終える。WALへの切り替えは、断られたら読み直して5秒まで待つ。既に現行schemaのDBを開く時の動作は変わらない。
|
|
37
|
+
- 切り離した置き場で再現した。新しいDBを6 processが同時に開くと、Linuxで6回中3回、どれか1本が落ちた。Windowsでも、最初の1回で2本が落ちた。修理後は48回(6 process)で失敗0。
|
|
38
|
+
|
|
39
|
+
### 変更
|
|
40
|
+
|
|
41
|
+
- `~/.throughline/logs/hook-failures.log`に、失敗した位置(`stack`、先頭6行まで)を足す。`database is locked`のような文面だけでは、どの処理で落ちたかが分からないため。端末内にだけ残し、外へは送らない。
|
|
42
|
+
- hookのstdinがJSONとして読めない時の文面を`hook stdin is not valid JSON (N chars): …`にする。hostがstdinを渡さずに閉じた時(0字)と、途中で切れた時を見分けられる。失敗として数えることと、終了codeは変えない。
|
|
43
|
+
- 2026-10-04にWindows(0.12.4)で起きたSessionStartとUserPromptSubmitの失敗各1回は、原因が分かっていない。どちらもCursorの会話で2本ほぼ同時に走ったhookの片方で、もう片方は成功している。現行schemaのDBで起きたので、上の修正の形ではない。この2つの変更は、次に起きた時に理由を端末へ残すためのもの。
|
|
44
|
+
|
|
13
45
|
## [0.12.8] — 2026-10-04
|
|
14
46
|
|
|
15
47
|
### 変更
|
|
@@ -1621,7 +1653,9 @@ two attempts, instrument first instead of patching again.
|
|
|
1621
1653
|
|
|
1622
1654
|
---
|
|
1623
1655
|
|
|
1624
|
-
[Unreleased]: https://github.com/kitepon/Throughline/compare/v0.
|
|
1656
|
+
[Unreleased]: https://github.com/kitepon/Throughline/compare/v0.13.0...HEAD
|
|
1657
|
+
[0.13.0]: https://github.com/kitepon/Throughline/compare/v0.12.9...v0.13.0
|
|
1658
|
+
[0.12.9]: https://github.com/kitepon/Throughline/compare/v0.12.8...v0.12.9
|
|
1625
1659
|
[0.12.8]: https://github.com/kitepon/Throughline/compare/v0.12.7...v0.12.8
|
|
1626
1660
|
[0.12.7]: https://github.com/kitepon/Throughline/compare/v0.12.6...v0.12.7
|
|
1627
1661
|
[0.12.6]: https://github.com/kitepon/Throughline/compare/v0.12.5...v0.12.6
|
package/README.ja.md
CHANGED
|
@@ -44,6 +44,26 @@ throughline auto-handoff resume --operation <handoff-id> --json
|
|
|
44
44
|
|
|
45
45
|
[実測と検証範囲](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-03-codex-auto-continuation.md)を参照してください。
|
|
46
46
|
|
|
47
|
+
## Claude Codeの自動継続
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
throughline auto-handoff enable --host claude --project /absolute/project/path
|
|
51
|
+
throughline auto-handoff status --host claude --json
|
|
52
|
+
throughline auto-handoff disable --host claude
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
既定では無効です。`--project`を省略すると全projectを対象にします。Codexの設定とは別で、互いに影響しません。
|
|
56
|
+
|
|
57
|
+
Claude Codeは、自動圧縮の前にhookから作業を止めることも、新しい会話へ指示を送ることもできません。自動圧縮の後は、同じ会話で作業をそのまま続けます。Throughlineは会話を切り替えず、圧縮の直後に、圧縮前の記録を同じ会話へ注入します([ADR 0032](docs/adr/0032-claude-compact-continuation.md))。
|
|
58
|
+
|
|
59
|
+
- `PreCompact`フックが、自動圧縮の時だけ印を残します。圧縮は止めません。
|
|
60
|
+
- 圧縮直後の`SessionStart`(`source: "compact"`)フックが、作業途中のユーザー依頼と圧縮直前の発言、直近の会話の原文を注入します。上限は9,500字で、入り切らない古いターンは`throughline recall`の案内になります。
|
|
61
|
+
- モデルは追加の入力なしで作業を続けます。圧縮の要約と記録が食い違う時は、記録を正とするよう指示します。
|
|
62
|
+
|
|
63
|
+
手動`/compact`は対象に含めません。圧縮をまたいだターンは、元の依頼と最終回答の1ターンとして保存します(0.12.9以前は、圧縮の要約をユーザー発言として保存していました)。
|
|
64
|
+
|
|
65
|
+
LinuxのClaude Code 2.1.289(`claude -p`と対話画面)で実機確認済みです。macOS・Windows、VS Code拡張・Desktop、subagentの中の圧縮は未検証です。[実測と検証範囲](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-04-claude-compact-continuation.md)を参照してください。
|
|
66
|
+
|
|
47
67
|
## 所有境界
|
|
48
68
|
|
|
49
69
|
本repositoryは導入、設定、状態、schemaとmigration、診断、復旧、更新、release判断を
|
package/README.md
CHANGED
|
@@ -43,6 +43,26 @@ throughline auto-handoff resume --operation <handoff-id> --json
|
|
|
43
43
|
|
|
44
44
|
[実測と検証範囲](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-03-codex-auto-continuation.md)を参照してください。
|
|
45
45
|
|
|
46
|
+
## Claude Codeの自動継続
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
throughline auto-handoff enable --host claude --project /absolute/project/path
|
|
50
|
+
throughline auto-handoff status --host claude --json
|
|
51
|
+
throughline auto-handoff disable --host claude
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
既定では無効です。`--project`を省略すると全projectを対象にします。Codexの設定とは別で、互いに影響しません。
|
|
55
|
+
|
|
56
|
+
Claude Codeは、自動圧縮の前にhookから作業を止めることも、新しい会話へ指示を送ることもできません。自動圧縮の後は、同じ会話で作業をそのまま続けます。Throughlineは会話を切り替えず、圧縮の直後に、圧縮前の記録を同じ会話へ注入します([ADR 0032](docs/adr/0032-claude-compact-continuation.md))。
|
|
57
|
+
|
|
58
|
+
- `PreCompact`フックが、自動圧縮の時だけ印を残します。圧縮は止めません。
|
|
59
|
+
- 圧縮直後の`SessionStart`(`source: "compact"`)フックが、作業途中のユーザー依頼と圧縮直前の発言、直近の会話の原文を注入します。上限は9,500字で、入り切らない古いターンは`throughline recall`の案内になります。
|
|
60
|
+
- モデルは追加の入力なしで作業を続けます。圧縮の要約と記録が食い違う時は、記録を正とするよう指示します。
|
|
61
|
+
|
|
62
|
+
手動`/compact`は対象に含めません。圧縮をまたいだターンは、元の依頼と最終回答の1ターンとして保存します(0.12.9以前は、圧縮の要約をユーザー発言として保存していました)。
|
|
63
|
+
|
|
64
|
+
LinuxのClaude Code 2.1.289(`claude -p`と対話画面)で実機確認済みです。macOS・Windows、VS Code拡張・Desktop、subagentの中の圧縮は未検証です。[実測と検証範囲](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-04-claude-compact-continuation.md)を参照してください。
|
|
65
|
+
|
|
46
66
|
## Ownership boundary
|
|
47
67
|
|
|
48
68
|
This repository owns installation, configuration, state, schema and migrations,
|
package/bin/throughline.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* throughline uninstall # hook を削除
|
|
9
9
|
* throughline process-turn # Stop hook (Claude Code から呼ばれる)
|
|
10
10
|
* throughline session-start # SessionStart hook (Claude Code から呼ばれる)
|
|
11
|
+
* throughline pre-compact # PreCompact hook (Claude Code から呼ばれる。自動継続の印)
|
|
11
12
|
* throughline detail <時刻> # L2+L3 詳細取得 (Claude が Bash 経由で呼ぶ想定)
|
|
12
13
|
* throughline recall --l2|--l1 # 注入案内から辿る pull 用 read-only 記憶取得
|
|
13
14
|
* throughline handoff-preview # Codex-facing throughline_handoff JSON preview
|
|
@@ -52,7 +53,7 @@ const [, , cmd, ...rest] = process.argv;
|
|
|
52
53
|
|
|
53
54
|
// hook入口では、送信を明示して有効にした端末だけ、未受領のruntime errorを裏で送るprocessを起こす。
|
|
54
55
|
// 既定(送信設定なし)では小さなファイル1つを見るだけで、通信もprocess起動もしない。
|
|
55
|
-
if (['process-turn', 'session-start', 'prompt-submit', 'codex-hook'].includes(cmd)) {
|
|
56
|
+
if (['process-turn', 'session-start', 'prompt-submit', 'pre-compact', 'codex-hook'].includes(cmd)) {
|
|
56
57
|
try {
|
|
57
58
|
(await import('../src/runtime-error-report-trigger.mjs')).triggerRuntimeErrorReportBestEffort();
|
|
58
59
|
} catch {
|
|
@@ -80,6 +81,9 @@ switch (cmd) {
|
|
|
80
81
|
case 'prompt-submit':
|
|
81
82
|
await (await import('../src/prompt-submit.mjs')).run();
|
|
82
83
|
break;
|
|
84
|
+
case 'pre-compact':
|
|
85
|
+
await (await import('../src/claude-pre-compact.mjs')).run();
|
|
86
|
+
break;
|
|
83
87
|
case 'monitor':
|
|
84
88
|
(await import('../src/token-monitor.mjs')).main();
|
|
85
89
|
break;
|
|
@@ -233,6 +237,7 @@ switch (cmd) {
|
|
|
233
237
|
'session-start': 'HOOK_SESSION_START_FAILED',
|
|
234
238
|
'prompt-submit': 'HOOK_PROMPT_SUBMIT_FAILED',
|
|
235
239
|
'process-turn': 'HOOK_PROCESS_TURN_FAILED',
|
|
240
|
+
'pre-compact': 'HOOK_PRE_COMPACT_FAILED',
|
|
236
241
|
}[cmd];
|
|
237
242
|
if (code) {
|
|
238
243
|
const { recordRuntimeErrorBestEffort } = await import('../src/runtime-error-store.mjs');
|
|
@@ -401,6 +406,9 @@ Usage:
|
|
|
401
406
|
同じ引き継ぎを再開(結果不明は再送しない)
|
|
402
407
|
throughline auto-handoff detail --operation <id> --origin <session> --turn <n>
|
|
403
408
|
凍結したL2/L3を取得
|
|
409
|
+
throughline auto-handoff enable|disable|status --host claude [--project <path>] [--json]
|
|
410
|
+
Claude Codeの自動継続(自動圧縮の直後に、同じ会話へ
|
|
411
|
+
圧縮前の記録を注入)を有効化・無効化・確認
|
|
404
412
|
throughline doctor Check environment
|
|
405
413
|
throughline doctor --trim Show trim host boundary diagnostics
|
|
406
414
|
throughline doctor --codex Show Codex primary diagnostics
|
|
@@ -411,6 +419,7 @@ Hook subcommands (called by Claude Code / Codex):
|
|
|
411
419
|
throughline session-start SessionStart hook
|
|
412
420
|
throughline process-turn Stop hook
|
|
413
421
|
throughline prompt-submit UserPromptSubmit hook (/tl & /clear baton writer)
|
|
422
|
+
throughline pre-compact PreCompact hook (Claude自動圧縮の印。圧縮後に記憶を注入)
|
|
414
423
|
throughline codex-hook user-prompt-submit Codex current-session refresh prompt hook
|
|
415
424
|
throughline codex-hook post-tool-use Codex current-session refresh tool-loop hook
|
|
416
425
|
throughline codex-hook stop Codex Stop hook
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
| `/tl` | `UserPromptSubmit` が現在の session id を baton として保存する。次の実セッションがその前任を確定的に引き継ぐ |
|
|
12
12
|
| VS Code の `/clear` | 組み込みコマンドは `UserPromptSubmit` に届かない。`SessionStart source='clear'` を使う auto path が、同じ project の直近前任を凍結して引き継ぐ |
|
|
13
13
|
| Claude Desktop の `/clear` | `UserPromptSubmit` に届かず、`SessionStart source='clear'` も来ないため自動継承しない。続ける場合は `/clear` の前に `/tl` を実行する |
|
|
14
|
+
| Claude Code の自動圧縮 | 会話は切り替わらない。`auto-handoff enable --host claude` で有効にした時だけ、`PreCompact`(`trigger: auto`)が印を残し、圧縮直後の `SessionStart source='compact'` が作業途中のターンと直近の会話を同じ会話へ注入する。既定は無効。手動 `/compact` は対象外。正本は [ADR 0032](adr/0032-claude-compact-continuation.md) |
|
|
14
15
|
|
|
15
16
|
`/clear` が `UserPromptSubmit` に届かないことは、VS Code と Desktop の対照実測で
|
|
16
17
|
確定している。`prompt-submit` に残る `/clear` 分岐は host が将来その文字列を渡した場合の
|
|
@@ -43,7 +44,7 @@ baton path と auto path は同じ `buildBudgetedResumeContext` を使う。注
|
|
|
43
44
|
|
|
44
45
|
## host 別境界
|
|
45
46
|
|
|
46
|
-
- Claude Code: VS Code `/clear` は auto path、Desktop `/clear` は `/tl`
|
|
47
|
+
- Claude Code: VS Code `/clear` は auto path、Desktop `/clear` は `/tl` 併用。自動圧縮は、有効にした時だけ同じ会話へ注入する(ADR 0032)。
|
|
47
48
|
- Grok: `/tl` の baton 成功後に `throughline grok-continue --session <id>` で後継席を
|
|
48
49
|
起動する。`/clear` では起動しない。正本は [ADR 0021](adr/0021-grok-host-capture.md)。
|
|
49
50
|
- Cursor: `/tl` は baton を残すが後継会話を自動起動しない。次の会話が baton を消費する。
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
最初の成立検証はCodex Desktopで行う。OSごとの仕組みと受入条件は共通にし、
|
|
29
29
|
差分は既存の `src/os/` と配送ライブラリの環境適合へ閉じ込める。
|
|
30
30
|
VS Code・CLIとClaude/Grok/Cursorの対応はhostごとに同じ条件で検証する。
|
|
31
|
+
Claude Codeは圧縮前の停止と新しい会話への配送がhookからできないため、自動圧縮の直後に同じ会話へ記憶を注入する
|
|
32
|
+
([ADR 0032](adr/0032-claude-compact-continuation.md))。この節の処理順と進行状態はCodexだけに適用する。
|
|
31
33
|
一つのhostの成功で他のhostも対応済みとはしない。
|
|
32
34
|
|
|
33
35
|
### 根拠と検証範囲
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# ADR 0031: schema の移行は書き込み lock の中で版を読み直して行い、WAL への切り替えは読み直して待つ
|
|
2
|
+
|
|
3
|
+
日付: 2026-10-04
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
`getDb()` は DB を開くたびに `PRAGMA user_version` を読み、現行版より古ければ移行を走らせていた。
|
|
8
|
+
版を読むのも、移行の各 statement も、transaction の外だった。
|
|
9
|
+
|
|
10
|
+
Windows の Cursor の会話では、同じ hook が2本ほぼ同時に走る(2026-10-04 の記録では、1会話の SessionStart が
|
|
11
|
+
数ms〜320ms の間隔で2行残る。`~/.cursor/hooks.json` と `~/.claude/settings.json` の両方に Throughline の登録がある。
|
|
12
|
+
2本目がどちらの登録から来るかは確かめていない)。新しい端末の最初の会話では、2本とも版 0 を読む。片方が先に移行を終えると、もう片方は版 0 のつもりで
|
|
13
|
+
v2 の移行(`DELETE FROM judgments ...`)を走らせ、v4 で消えた表へ触って `no such table: judgments` で落ちる。
|
|
14
|
+
|
|
15
|
+
もう1つ、新しい DB の journal mode を WAL へ切り替える `PRAGMA journal_mode = WAL` は排他 lock を要る。
|
|
16
|
+
他の process が同じ DB を開いている間は、`busy_timeout` を待たずに `database is locked` で断られる。
|
|
17
|
+
|
|
18
|
+
2026-10-04 に、切り離した置き場で再現した。新しい DB を 6 process が同時に開くと、Linux(Node 26)で
|
|
19
|
+
6回中3回、どれか1本が上のどちらかで落ちた。Windows(Node 24)でも、SessionStart と UserPromptSubmit を
|
|
20
|
+
2本ずつ同時に動かした最初の1回で2本が `no such table: judgments` で落ちた。
|
|
21
|
+
既に現行 schema の DB では、どちらも起きない(版が現行なら移行は何も書かず、WAL なら切り替えない)。
|
|
22
|
+
|
|
23
|
+
## Decision
|
|
24
|
+
|
|
25
|
+
1. 移行は `BEGIN IMMEDIATE` で書き込み lock を取り、その中で `PRAGMA user_version` を読み直してから走らせ、
|
|
26
|
+
`user_version` の更新まで1つの transaction で commit する。失敗した時は rollback し、途中の schema を残さない。
|
|
27
|
+
2. lock の外で読んだ版が現行版以上なら、lock を取らない。現行 schema の DB を開く hook は、今までどおり何も書かない。
|
|
28
|
+
3. 他の process が移行している間に開いた process は、`busy_timeout`(5秒)まで lock を待ち、
|
|
29
|
+
取れた後に読み直した版が現行なら何もしない。
|
|
30
|
+
4. WAL への切り替えは、`database is locked` で断られたら 25ms 待って journal mode を読み直し、
|
|
31
|
+
`busy_timeout` と同じ時間まで繰り返す。他の process が先に切り替えれば、読み直しで WAL が見えて終わる。
|
|
32
|
+
5. `migrate --json`(ADR 0018 の正規入口)も同じ関数を通る。結果の形は変えない。
|
|
33
|
+
|
|
34
|
+
## Consequences
|
|
35
|
+
|
|
36
|
+
- 新しい端末で複数の hook が同時に最初の DB を開いても、全員が現行 schema で開ける。
|
|
37
|
+
- 古い schema からの移行中、他の hook は最長5秒待つ。移行が5秒を超えると、待った hook は
|
|
38
|
+
`database is locked` で失敗する。今までは、移行の途中の schema で走るか、同じ移行をもう一度走らせていた。
|
|
39
|
+
- 移行が途中で失敗した DB は、移行前の schema のまま残る。次に開いた時に最初からやり直す。
|
|
40
|
+
- 2026-10-04 に Windows(0.12.4)で起きた SessionStart と UserPromptSubmit の失敗各1回は、
|
|
41
|
+
現行 schema の DB で起きている。この ADR の2つの形ではなく、原因は分かっていない。
|
|
42
|
+
理由の文面は 0.12.4 では端末に残らない。0.12.9 は `hook-failures.log` に失敗した位置(stack の先頭)を足し、
|
|
43
|
+
hook の stdin が JSON として読めない時の文面に字数を入れる。
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# ADR 0032: Claude Code の自動継続は、自動圧縮の直後に同じ会話へ記憶を注入する
|
|
2
|
+
|
|
3
|
+
日付: 2026-10-04
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
Codex Desktop の自動継続(0.11.0)は、自動圧縮の前に旧タスクを止め、記憶を注入した新しいタスクで
|
|
8
|
+
作業を続ける。オーナーの指示(2026-10-04)で、同じことを Claude Code でも行う。
|
|
9
|
+
|
|
10
|
+
Claude Code 2.1.289 で、Codex と同じ方式が取れるかを公式の hook 仕様と実機で確かめた。
|
|
11
|
+
|
|
12
|
+
| 必要な操作 | Codex Desktop | Claude Code 2.1.289 |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| 圧縮の前に作業を止める | `PreCompact` が `continue: false` を返す | `PreCompact` の `continue` は捨てられる。できるのは圧縮を止めること(exit code 2、`decision: "block"`)だけで、止めると会話は圧縮されないまま続き、文脈上限に達した後は元の error で失敗する |
|
|
15
|
+
| 新しい会話を作って記憶を入れる | app-server の `thread/start` と `thread/inject_items` | 公開された入口が無い |
|
|
16
|
+
| 新しい会話へ指示を送って始めさせる | 公式 queue への配送 | `claude-cli://` は入力欄へ下書きを入れるだけで送信しない。`claude --bg` は別の background session を作り、編集の前に git worktree へ移る。元の画面の会話は入れ替わらない |
|
|
17
|
+
|
|
18
|
+
一方、Claude Code は自動圧縮の後、同じ会話で作業をそのまま続ける。圧縮の直後に
|
|
19
|
+
`SessionStart`(`source: "compact"`)の hook を走らせ、その出力を圧縮後の文脈へ足すと公式文書に書かれている。
|
|
20
|
+
|
|
21
|
+
実機の結果(Linux、`claude -p`、Haiku 4.5、`CLAUDE_CODE_AUTO_COMPACT_WINDOW=100000`):
|
|
22
|
+
|
|
23
|
+
- `PreCompact` は `trigger: "auto"` で届く。payload は `session_id`・`transcript_path`・`cwd`・`trigger`・`custom_instructions`。
|
|
24
|
+
- 圧縮の後、同じ `session_id` の `SessionStart(source=compact)` が届く。stdout は `<system-reminder>` として
|
|
25
|
+
モデルへ届き、モデルは追加の入力なしで作業を続けた。9,099 字の出力は file 化されずにそのまま届いた。
|
|
26
|
+
- transcript は圧縮より前の行を残したまま、`system`(`subtype: "compact_boundary"`、`compactMetadata.trigger`)と、
|
|
27
|
+
要約を本文に持つ `user` 行(`isCompactSummary: true`)を足す。圧縮境界の行は `SessionStart` hook が
|
|
28
|
+
走った時刻より後の時刻を持つ。
|
|
29
|
+
- 1回目の圧縮の前に `PreCompact` が5秒の間隔で2回届いたことがある。
|
|
30
|
+
- 依頼を送った直後(最初の応答の前)に自動圧縮が起きることがある。その回では、`SessionStart(source=compact)` の
|
|
31
|
+
時点で、送った依頼の行は transcript に書かれていた。行の `promptId` は hook payload の `prompt_id` と同じ値。
|
|
32
|
+
|
|
33
|
+
あわせて、既存の不具合が見つかった。`readTranscript` は要約の `user` 行を人の発言として数えていた。
|
|
34
|
+
圧縮をまたいだ1ターンは「元の依頼 + 途中の発言」と「要約 + 圧縮後の発言」に割れ、要約が user 発言として
|
|
35
|
+
bodies に入り、圧縮より前の tool 入出力は L3 に入らなかった。3回圧縮した実機のターンは4ターンとして保存された。
|
|
36
|
+
|
|
37
|
+
## Decision
|
|
38
|
+
|
|
39
|
+
Claude では会話を切り替えず、host が続ける同じ会話へ記憶を渡す。
|
|
40
|
+
|
|
41
|
+
1. **印**: `PreCompact` hook(`throughline pre-compact`、matcher 無し)が、`trigger: "auto"` で自動継続が有効な
|
|
42
|
+
project の時だけ、`~/.throughline/claude-auto-handoff/<session_id>.json` に印を残す。手動の `/compact` と
|
|
43
|
+
無効の時は、同じ会話の古い印を消す。subagent の中の圧縮(payload に `agent_id`)は印に触らない。
|
|
44
|
+
hook は圧縮を止めない。stdout へ何も書かず、失敗しても exit code 2 を返さない。
|
|
45
|
+
2. **注入**: `SessionStart(source=compact)` が印を取り出して消し、次を stdout へ書く(上限 9,500 字、ADR 0014)。
|
|
46
|
+
- ヘッダ: 自動圧縮が起きたこと、下記が圧縮前の原文であること、作業の途中なので入力を待たずに続けること、
|
|
47
|
+
完了済みの操作を繰り返さないこと。宣言文は求めない。
|
|
48
|
+
- 現在地: 作業途中のターンの user 依頼(4,000 字まで)と、圧縮直前の assistant 発言(1,500 字まで)。
|
|
49
|
+
transcript から読む。上限を超える時は先頭 3/4 と末尾 1/4 を残し、間を抜いた字数を書く。
|
|
50
|
+
- 案内と L2: `/clear` の引き継ぎ(ADR 0016)と同じ。完了済みのターンを新しい順に、丸ごと入る分だけ。
|
|
51
|
+
3. **作業途中のターンは保存しない**: 注入の前に完了済みのターンを bodies へ回収するが、最後の user 発言の群は除く。
|
|
52
|
+
途中の発言を代表として保存すると、群レベルの重複除去で最終回答が入らなくなる。保存は今までどおり Stop で行う。
|
|
53
|
+
transcript は hook より遅れて書かれることがある。hook payload の `prompt_id` と、最後の user 行の `promptId` が
|
|
54
|
+
両方あって食い違う時は、今の依頼がまだ書かれていない。その時は最後の群を現在地に載せず、回収もせず、
|
|
55
|
+
現在地には「記録からまだ読めない」とだけ書く。
|
|
56
|
+
4. **trigger は `PreCompact` の payload だけから決める**: `SessionStart` の時点の transcript から推測しない
|
|
57
|
+
(圧縮境界の行は hook より後に書かれる)。手動の `/compact` は対象にしない(Codex と同じ)。
|
|
58
|
+
5. **要約行**: `isCompactSummary: true` の行は、ターンの始まりにも本文にもしない。`turn_number`(transcript 内の
|
|
59
|
+
本文行の通し番号)は詰めず、要約行の分も進める。詰めると、要約行を数えていた頃に保存した行と番号が食い違う。
|
|
60
|
+
L3 の範囲も要約行で切らず、元の依頼から取る。
|
|
61
|
+
6. **有効化**: 既定は無効。`throughline auto-handoff enable --host claude [--project <path>]` が
|
|
62
|
+
`~/.throughline/claude-auto-handoff.json` を書く。`--host` を省いた時は今までどおり Codex。
|
|
63
|
+
Codex の設定とは別のファイルで、互いに影響しない。有効判定は会話を起動した project(`CLAUDE_PROJECT_DIR`)で行う。
|
|
64
|
+
`resume`・`detail`・`worker` と `--operation` は Claude では使えない(引き継ぎ操作の記録を持たない)。
|
|
65
|
+
7. **hook の登録**: `throughline install` が Claude の `PreCompact` hook を登録する。`enable` は登録が無ければ足す。
|
|
66
|
+
無効の時、hook は判定を記録して抜ける。
|
|
67
|
+
8. **失敗**: `PreCompact` の失敗は `HOOK_PRE_COMPACT_FAILED`(severity `warn`。圧縮と作業は続く)、
|
|
68
|
+
注入の失敗は既存の `HOOK_SESSION_START_FAILED`。どちらも理由を `hook-failures.log` に残す。
|
|
69
|
+
判定は `inheritance-decision.log` に `phase: "pre-compact"` と `phase: "compact-continuation"` で残す。
|
|
70
|
+
|
|
71
|
+
## Consequences
|
|
72
|
+
|
|
73
|
+
- Claude Code の圧縮は走る。Codex のように圧縮の本文生成を省けない。圧縮後の文脈は、host の要約と
|
|
74
|
+
Throughline の記録の両方を持つ。食い違う時は記録を正とするよう、ヘッダで指示する。
|
|
75
|
+
- schema は変えない(v12 のまま)。新しい会話も、配送も、常駐 process も作らない。`aiterm-steer-delivery` は使わない。
|
|
76
|
+
- Claude の `/clear` と `/tl` の引き継ぎ(ADR 0014・0016)は変えない。`SessionStart(source=compact)` の
|
|
77
|
+
pending intent の登録も今までどおり行う。
|
|
78
|
+
- 作業途中のターンの tool 入出力は、Stop まで `throughline detail` で取れない。
|
|
79
|
+
- 圧縮をまたいだターンは、元の依頼と最終回答の1ターンとして保存される。Observer feed にも1ターンで出て、
|
|
80
|
+
`turn_start` は元の依頼のものになる。今までは要約行のターンとして `unknown` で出ていた。
|
|
81
|
+
- 修正より前に保存した行はそのまま残る。同じ断片を持つ群は入れ直さない。
|
|
82
|
+
- `throughline install`(`self-update` を含む)は `~/.claude/settings.json` に `PreCompact` を1件足す。
|
|
83
|
+
|
|
84
|
+
## 確認した範囲
|
|
85
|
+
|
|
86
|
+
- Linux の Claude Code 2.1.289(`claude -p` と対話画面)、Haiku 4.5。自動圧縮2回をまたいで作業が完了し、
|
|
87
|
+
圧縮のたびに注入が届き、圧縮されたターンが1ターンとして保存された。
|
|
88
|
+
- macOS・Windows、VS Code 拡張・Desktop、subagent の中の圧縮は確かめていない。
|
package/package.json
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Claude Code: 圧縮まわりの hook(一次ソース抜粋)
|
|
2
|
+
|
|
3
|
+
取得: 2026-10-04、`https://code.claude.com/docs/en/hooks.md`・`context-window.md`・`deep-links.md`・`agent-view.md`・`env-vars.md`。
|
|
4
|
+
実機は Claude Code 2.1.289。設計は [ADR 0032](../../../docs/adr/0032-claude-compact-continuation.md)。
|
|
5
|
+
|
|
6
|
+
## PreCompact
|
|
7
|
+
|
|
8
|
+
> Exit with code 2 to block compaction. For a manual `/compact`, the stderr message is shown to the user. You can also block by returning JSON with `"decision": "block"`.
|
|
9
|
+
>
|
|
10
|
+
> Blocking automatic compaction has different effects depending on when it fires. If compaction was triggered proactively before the context limit, Claude Code skips it and the conversation continues uncompacted. If compaction was triggered to recover from a context-limit error already returned by the API, the underlying error surfaces and the current request fails.
|
|
11
|
+
>
|
|
12
|
+
> Claude Code discards a PreCompact hook's `systemMessage` and `continue` fields.
|
|
13
|
+
|
|
14
|
+
- matcher は `manual` / `auto`。入力は共通項目に加えて `trigger` と `custom_instructions`(auto では `null`)。
|
|
15
|
+
- → 圧縮の前に作業を止める口は無い。止められるのは圧縮だけ。
|
|
16
|
+
|
|
17
|
+
## SessionStart(source=compact)
|
|
18
|
+
|
|
19
|
+
> | `compact` | Auto or manual compaction |
|
|
20
|
+
|
|
21
|
+
> | [SessionStart hooks](/docs/en/hooks-guide#re-inject-context-after-compaction) that match the `compact` source | Claude Code runs them and adds their output to the compacted context |
|
|
22
|
+
|
|
23
|
+
- 入力の `source` は `compact`。auto と manual の区別は入力に無い。
|
|
24
|
+
- → 区別は `PreCompact` の `trigger` で取る。
|
|
25
|
+
|
|
26
|
+
## PostCompact
|
|
27
|
+
|
|
28
|
+
> PostCompact hooks have no decision control. They can't affect the compaction result but can perform follow-up tasks.
|
|
29
|
+
|
|
30
|
+
- 入力に `compact_summary`(生成された要約)。Throughline は使わない。
|
|
31
|
+
|
|
32
|
+
## subagent
|
|
33
|
+
|
|
34
|
+
> | `agent_id` | Unique identifier for the subagent. Present only when the hook fires inside a subagent call. Use this to distinguish subagent hook calls from main-thread calls. |
|
|
35
|
+
|
|
36
|
+
## 新しい会話を始める口
|
|
37
|
+
|
|
38
|
+
> A deep link never executes anything on its own. The link only chooses a directory and fills the prompt box.
|
|
39
|
+
|
|
40
|
+
> When you dispatch a background session from agent view or start one with `claude --bg`, the session starts in your working directory. Before editing files, Claude moves the session into an isolated git worktree under `.claude/worktrees/`
|
|
41
|
+
|
|
42
|
+
- `claude-cli://open?q=…` は入力欄へ下書きを入れるだけ。`claude --bg "<prompt>"` は別の background session。
|
|
43
|
+
- → 対話中の会話を、記憶を入れた新しい会話へ入れ替えて続けさせる公開の口は無い。
|
|
44
|
+
|
|
45
|
+
## 自動圧縮の窓
|
|
46
|
+
|
|
47
|
+
> | `CLAUDE_CODE_AUTO_COMPACT_WINDOW` | Set the auto-compact window in tokens, from `100000` to `1000000`. … |
|
|
48
|
+
|
|
49
|
+
- 実機試験だけで使う。製品は依存しない。
|
|
50
|
+
|
|
51
|
+
## 実機で見た transcript(2.1.289)
|
|
52
|
+
|
|
53
|
+
- 圧縮の後に `{"type":"system","subtype":"compact_boundary","compactMetadata":{"trigger":"auto","preTokens":…,"postTokens":…}}`。
|
|
54
|
+
- 続けて `{"type":"user","isCompactSummary":true,"isVisibleInTranscriptOnly":true,"message":{"role":"user","content":"This session is being continued from a previous conversation that ran out of context. …"}}`。
|
|
55
|
+
- `SessionStart:compact` の hook 出力は `attachment`(`type: "hook_success"`)として残り、モデルには `<system-reminder>` で届く。
|
|
56
|
+
- 圧縮より前の行は消えない。
|
package/rag/INDEX.md
CHANGED
|
@@ -27,6 +27,10 @@ rag/
|
|
|
27
27
|
- [fork rolloutのmetadata実測](01-hooks/codex-fork-rollout-metadata.md) — 子の先頭metadataの後に親のmetadataと履歴がコピーされる並びを実機で確認。自動継続の誤ったthread不一致判定を最小fixtureと実rolloutで再現・修正した。
|
|
28
28
|
- [PreCompact・記憶注入・配送の一次ソース抜粋](01-hooks/raw/codex-auto-handoff-extract.md) — 公式仕様と配送ライブラリの固定source。[Desktop実測](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-03-codex-auto-continuation.md)でauto停止・新規idle taskの自動開始・配送process終了後の継続が成立した。cold readの途中状態と表示後の権限更新を観測し、[設計](../docs/05_codex_first_roadmap.md#自動新規タスク継続の設計案)へ反映した。
|
|
29
29
|
|
|
30
|
+
## Claude自動継続の根拠
|
|
31
|
+
|
|
32
|
+
- [圧縮まわりのhookの一次ソース抜粋](01-hooks/raw/claude-compact-hooks-extract.md) — `PreCompact`は圧縮を止められるが作業は止められない。`SessionStart(source=compact)`の出力は圧縮後の文脈へ足される。新しい会話へ指示を送る公開の口は無い。[実測](https://github.com/kitepon/Throughline/blob/main/evidence/2026-10-04-claude-compact-continuation.md)で、自動圧縮の直後に注入が届き、入力なしで作業が続いた。Finding 6・第三仮説(PreCompactでの再投影)に対する現行の答えは[ADR 0032](../docs/adr/0032-claude-compact-continuation.md)。
|
|
33
|
+
|
|
30
34
|
## Question this RAG was built to answer
|
|
31
35
|
|
|
32
36
|
> Throughline は「コンテキスト削減しつつ過去の記憶を一切失わない」と定義されている。記憶を引き継いでいてもモデルがそれを自分の作業履歴として体感していないなら、その記憶は無意味なコンテキストである。
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { readFileSync, mkdirSync, writeFileSync, renameSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join, dirname, isAbsolute } from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { normalizeProjectPathForCompare, sameProjectPath } from './project-path.mjs';
|
|
6
|
+
|
|
7
|
+
// Claude の自動継続の設定。Codex の設定(codex-auto-handoff.json)とは別のファイルで、互いに影響しない。
|
|
8
|
+
export const CLAUDE_AUTO_HANDOFF_CONFIG_SCHEMA = 'throughline.claude-auto-handoff.v1';
|
|
9
|
+
export const claudeAutoHandoffConfigPath = () => join(homedir(), '.throughline', 'claude-auto-handoff.json');
|
|
10
|
+
|
|
11
|
+
export function readClaudeAutoHandoffConfig(path = claudeAutoHandoffConfigPath()) {
|
|
12
|
+
let text;
|
|
13
|
+
try { text = readFileSync(path, 'utf8'); }
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error.code === 'ENOENT') return { schema: CLAUDE_AUTO_HANDOFF_CONFIG_SCHEMA, enabled: false, projects: [] };
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
const config = JSON.parse(text);
|
|
19
|
+
if (config.schema !== CLAUDE_AUTO_HANDOFF_CONFIG_SCHEMA || typeof config.enabled !== 'boolean' ||
|
|
20
|
+
!Array.isArray(config.projects) ||
|
|
21
|
+
config.projects.some(p => typeof p !== 'string' || !isAbsolute(p))) throw new Error('auto_handoff_config_invalid');
|
|
22
|
+
return config;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function writeClaudeAutoHandoffConfig({ enabled, projects = [] }, path = claudeAutoHandoffConfigPath()) {
|
|
26
|
+
if (typeof enabled !== 'boolean') throw new TypeError('auto_handoff_config_invalid');
|
|
27
|
+
const config = { schema: CLAUDE_AUTO_HANDOFF_CONFIG_SCHEMA, enabled,
|
|
28
|
+
projects: projects.map(normalizeProjectPathForCompare) };
|
|
29
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
30
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
31
|
+
writeFileSync(temporary, `${JSON.stringify(config)}\n`, { mode: 0o600, flag: 'wx' });
|
|
32
|
+
renameSync(temporary, path);
|
|
33
|
+
return config;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function claudeAutoHandoffEnabledFor(config, projectPath) {
|
|
37
|
+
return config.enabled && (config.projects.length === 0 || config.projects.some(p => sameProjectPath(p, projectPath)));
|
|
38
|
+
}
|