throughline 0.4.11 → 0.5.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 CHANGED
@@ -10,6 +10,84 @@ shipped to npm but were not individually tagged on GitHub.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.5.0] — 2026-05-24
14
+
15
+ This release closes out the v0.5 transcript-injection investigation and
16
+ locks in **path C** (`resume-context.mjs` v2.1 header + 現在地 anchor) as
17
+ the plugin-scope completion form for Throughline.
18
+
19
+ ### Changed
20
+
21
+ - Strengthened the Claude `/clear` resume context header with two new
22
+ short-message handling rules so the cleared-me side stops misreading
23
+ follow-up shorts as fresh requests:
24
+ - **短文/相槌の判定**: any user message that is ≤50 chars or built solely
25
+ out of acknowledgment / agreement / prompt words (はい / うん / 了解 /
26
+ OK / やって / 進めて / 続き / 次) must be treated as a GO sign on the
27
+ previous assistant's proposed next move, not a new request, and the
28
+ cleared-me must not ask back, re-list options, or pivot to other work.
29
+ - **古い番号リストの再実行禁止**: when the latest user references an
30
+ older numbered list (e.g. `2 をやれ`) but the most recent assistant turn
31
+ already executed that item, the cleared-me must respond with a result
32
+ confirmation / next move, not by re-executing the already-done item.
33
+ The latest assistant utterance outranks any older numbered list
34
+ referenced from it.
35
+
36
+ ### Research (no shipped behavior change)
37
+
38
+ Two alternative injection routes were spiked end-to-end against real
39
+ Claude Code (v2.1.145) and both confirmed dead, locking path C as the
40
+ plugin-scope ceiling.
41
+
42
+ - **D route — transcript JSONL append** (Phase 0-2 / Phase 0-5): four
43
+ real-machine runs across `SessionStart` (chain `null` orphan) and
44
+ `UserPromptSubmit` (chain `b` reachable-from-attachment) timings, with
45
+ both synthetic and real Claude model names. All four runs produced
46
+ 「ない」when the cleared-me was asked to quote the spike tracer. Root
47
+ cause: Claude Code decides each new turn's `parentUuid` from its
48
+ in-process memory state and never re-reads the JSONL, so any text a
49
+ hook writes to `transcript_path` lives on a parallel chain that the
50
+ next prompt's parent-walk never reaches.
51
+ - **`hookSpecificOutput.initialUserMessage` route** (Phase 0-6): real
52
+ Claude Code interactive run on 2026-05-24 13:33 (tracer `9220a79c`,
53
+ session `0979ad20-…`) returned 「ない」, empirically confirming the
54
+ openclaude source comment that `initialUserMessage` is consumed only
55
+ for headless orchestrator sessions, not for the interactive `/clear`
56
+ scenario this project needs.
57
+
58
+ Both routes are kept in-tree behind marker files
59
+ (`~/.throughline/spike-inject.flag`,
60
+ `~/.throughline/spike-prompt.flag`,
61
+ `~/.throughline/initial-user-message-test.flag`) as research
62
+ infrastructure for future re-evaluation; they are no-op when the flags
63
+ are absent and have no effect on the shipped path.
64
+
65
+ ### Added
66
+
67
+ - `docs/THROUGHLINE_TRANSCRIPT_INJECTION_PLAN.md`: full Phase 0 plan and
68
+ result log for the D / `initialUserMessage` investigation.
69
+ - `docs/RAG/`: third-party spec knowledge base (Claude Code hooks
70
+ reference, Anthropic Messages API, sessions docs, openclaude
71
+ `initialUserMessage` source extract) used as the grounding for the
72
+ no-go calls above.
73
+
74
+ ## [0.4.12] — 2026-05-17
75
+
76
+ ### Changed
77
+
78
+ - Added a 「現在地 (直前のやりとり)」 anchor at the top of the Claude
79
+ `/clear` resume context injection. The anchor re-surfaces the latest user
80
+ directive and the latest assistant turn body (each truncated to 600
81
+ characters) directly under the header, before the L1 / L2 sections.
82
+ Observed failure mode: with a long L2 window the model's attention could
83
+ fixate on the *first* L2 entry (oldest in the window) and mistake an older
84
+ plan discussion for the current state of the conversation. The anchor pins
85
+ the latest exchange at the position the model reads first, with the existing
86
+ L2-tail anchor preserved as reinforcement. The header reading instructions
87
+ now point to the new anchor as the first bullet.
88
+
89
+ ## [0.4.11] — 2026-05-10
90
+
13
91
  ### Changed
14
92
 
15
93
  - Disabled Codex automatic current-thread refresh from `UserPromptSubmit`,
@@ -23,6 +101,17 @@ shipped to npm but were not individually tagged on GitHub.
23
101
  and opens the selected host. Explicit `throughline trim --execute --host codex`
24
102
  remains available as a diagnostic current-thread rollback / inject command.
25
103
 
104
+ ### Fixed
105
+
106
+ - Codex hooks registered by `throughline install` now resolve the Node
107
+ executable through `PATH` (matching `process.execPath` by `realpath`) instead
108
+ of always hard-coding `process.execPath`. On Homebrew-installed Node on macOS,
109
+ `process.execPath` points at a Cellar-versioned binary that disappears on the
110
+ next `brew upgrade`, leaving stale absolute paths in `~/.codex/hooks.json`.
111
+ The new resolver prefers a stable `PATH` entry (e.g. `/opt/homebrew/bin/node`)
112
+ and falls back to `process.execPath` only when no PATH entry resolves to the
113
+ same binary.
114
+
26
115
  ## [0.4.10] — 2026-05-09
27
116
 
28
117
  ### Fixed
package/README.md CHANGED
@@ -216,14 +216,21 @@ this fallback. **The env var only affects the fallback**; typed `/clear` and
216
216
 
217
217
  Both paths inject the **same** curated memory:
218
218
 
219
+ - A **"現在地 (latest exchange)"** anchor (added in v0.4.12) re-surfaces the
220
+ most recent user directive and the most recent assistant turn directly under
221
+ the header, each truncated to 600 characters
219
222
  - L1 summaries (older turns, one-line)
220
223
  - L2 verbatim (most recent 20 turns, full text)
221
- - L3 references (`throughline detail <time>` retrieval commands; bodies stay in SQLite)
224
+ - L3 references (`throughline detail <time>` retrieval commands, attached
225
+ inline to each L1/L2 row; bodies stay in SQLite)
222
226
 
223
227
  The injection is reframed as **"resuming an interrupted task"** rather than
224
228
  "reading past logs". The L2 verbatim already contains the last assistant
225
229
  turn — what Claude was about to do next — so no separate memo or extended
226
- thinking section is injected.
230
+ thinking section is injected. The current-state anchor exists because, on
231
+ long L2 windows, attention can fixate on the *first* L2 entry (the oldest
232
+ turn in the window) and misread an old plan discussion as the current task;
233
+ pinning the latest exchange at the top of the injection prevents that drift.
227
234
 
228
235
  Each merged row keeps its `origin_session_id`, so repeated handoffs
229
236
  accumulate memory through chains:
@@ -0,0 +1,160 @@
1
+ # Throughline RAG Index — Context Spec Knowledge Base
2
+
3
+ Built: 2026-05-24
4
+
5
+ This directory accumulates third-party specifications relevant to Throughline's mission ("コンテキスト削減しつつ過去の記憶を一切失わない") so design decisions are grounded in actual Claude Code / Anthropic API constraints rather than guesses.
6
+
7
+ ## Folder layout
8
+
9
+ ```text
10
+ docs/RAG/
11
+ ├── INDEX.md (this file — synthesized findings, paths forward)
12
+ └── _raw/ (verbatim spec extracts, kept close to source wording)
13
+ ├── 01-hooks/
14
+ │ └── hooks-reference-extract.md ← Claude Code hooks reference
15
+ ├── 02-messages-api/
16
+ │ └── messages-api-extract.md ← Anthropic Messages API spec
17
+ ├── 03-settings/
18
+ │ └── sessions-extract.md ← /clear, /compact, /resume behavior
19
+ └── 04-skills/
20
+ └── initialUserMessage-investigation.md ← deep-dive on the killer field
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Question this RAG was built to answer
26
+
27
+ > Throughline は「コンテキスト削減しつつ過去の記憶を一切失わない」と定義されている。記憶を引き継いでいてもモデルがそれを自分の作業履歴として体感していないなら、その記憶は無意味なコンテキストである。
28
+ >
29
+ > Claude Code の hook 系内で **モデルが「これは自分の過去発話である」と認識する形** で記憶を注入する経路は、本当に存在しないのか?
30
+
31
+ ## Hard findings from spec (verified, not guessed)
32
+
33
+ ### Finding 1: `additionalContext` is a system reminder, not a user message
34
+
35
+ > "Claude Code wraps the string in a system reminder and inserts it into the conversation at the point where the hook fired. Claude reads the reminder on the next model request, but it **does not appear as a chat message** in the interface."
36
+ > — [Hooks reference](_raw/01-hooks/hooks-reference-extract.md#what-additionalcontext-actually-does-critical)
37
+
38
+ → システムリマインダ = ブリーフィング扱い。モデルが「他人事」と感じる構造的原因。
39
+
40
+ ### Finding 2: stdout from `SessionStart` / `UserPromptSubmit` / `UserPromptExpansion` is also a system reminder
41
+
42
+ > "any non-JSON text written to stdout is added as context"
43
+ > "Claude Code wraps the string in a system reminder"
44
+ > — [Hooks reference](_raw/01-hooks/hooks-reference-extract.md#stdout)
45
+
46
+ → 現行 Throughline v0.4.12 の stdout 注入はこの経路。`additionalContext` と同じカテゴリ = 同じ「他人事」問題。
47
+
48
+ ### Finding 3: `initialUserMessage` exists in the schema, but is **HEADLESS-ONLY**
49
+
50
+ Verified via [openclaude source](_raw/04-skills/initialUserMessage-investigation.md#the-critical-constraint-from-openclaude-source-comment):
51
+
52
+ ```text
53
+ // SessionStart hooks can emit initialUserMessage — the first user turn for
54
+ // headless orchestrator sessions where stdin is empty.
55
+ ```
56
+
57
+ → Interactive mode (`/clear` シナリオ) では発火しない。我々の問題には使えない。
58
+
59
+ **2026-05-24 実機確認**: real Claude Code (v2.1.145) で `~/.throughline/initial-user-message-test.flag` を立てて SessionStart hook を JSON 出力モードに切り替え、`hookSpecificOutput.initialUserMessage` に 8 hex tracer 入りメッセージを乗せて `/clear` 後の cleared-me に「過去発話の tracer を message history だけ見て返して」と尋ねた。ラン (2) 13:33 tracer `9220a79c` (session `0979ad20-…`) → モデル応答 **「ない」**。openclaude のソースコメントが real CC でも妥当であることを実機で確認。詳細: [docs/THROUGHLINE_TRANSCRIPT_INJECTION_PLAN.md §6 Phase 0-6](../THROUGHLINE_TRANSCRIPT_INJECTION_PLAN.md#phase-0-6--hookspecificoutputinitialusermessage-経路-spike)
60
+
61
+ ### Finding 4: Messages API treats all messages[] entries equally
62
+
63
+ > "When creating a new Message, you specify the prior conversational turns with the messages parameter, and the model then generates the next Message in the conversation."
64
+ > — [Messages API](_raw/02-messages-api/messages-api-extract.md#no-differentiation-between-real--synthetic-messages-key)
65
+
66
+ → もし messages[] に synthetic な過去 turn を入れられれば、モデルは「本物」と区別できない。問題は CC が messages[] を hook から制御させていないこと。
67
+
68
+ ### Finding 5: `/clear` preserves the JSONL but resets in-memory state
69
+
70
+ > "/clear: start fresh with an empty context. The previous conversation is saved and resumable"
71
+ > — [Sessions](_raw/03-settings/sessions-extract.md#clear-behavior-key)
72
+
73
+ → CC は in-memory state を一次ソースに messages[] を構築。JSONL を外から書き換えても in-memory には反映されない (= Phase 0 / Phase 0-5 で実測確認済み)。
74
+
75
+ ### Finding 6: `/compact` is the documented memory-preservation path
76
+
77
+ > "/compact [instructions]: replace history with a summary"
78
+
79
+ → /compact は同一 session を継続したまま履歴を要約版に置換。**PreCompact / PostCompact hook が発火**。これが Anthropic 公式の「記憶圧縮しつつ継続」経路。
80
+
81
+ ### Finding 7: First-party hook-dev SKILL omits `initialUserMessage`
82
+
83
+ [anthropics/claude-code/plugins/plugin-dev/skills/hook-development/SKILL.md](https://github.com/anthropics/claude-code/blob/main/plugins/plugin-dev/skills/hook-development/SKILL.md) は SessionStart hook の `hookSpecificOutput` の `additionalContext` も `initialUserMessage` も触れていない。Plugin 開発者向けの公式チュートリアルですら触れない = どちらも primary 経路として推奨されていない可能性。
84
+
85
+ ---
86
+
87
+ ## Throughline 仮説の見直し
88
+
89
+ ### 当初仮説 (D 経路)
90
+
91
+ 「JSONL に user/assistant 行を append すれば、CC が messages[] 構築時にそれを読む」
92
+
93
+ **実測 (4 ラン): すべて「ない」 → 反証済み**
94
+
95
+ JSONL は read 対象ではなく、CC の in-memory state が messages[] のソース。書き込んでも再読込されない。
96
+
97
+ ### 第二仮説 (initialUserMessage)
98
+
99
+ 「`initialUserMessage` で interactive モードでも first user message を注入できる」
100
+
101
+ **spec 調査 (openclaude source): HEADLESS-ONLY → 反証済み**
102
+
103
+ ### 第三仮説 (PreCompact での再投影)
104
+
105
+ 未検証。`/compact` のタイミングで PreCompact hook が「これを summary に必ず含めろ」と影響を与えられるなら、compaction 後の messages[] は本物の assistant turn として要約を含む可能性。
106
+
107
+ ---
108
+
109
+ ## ここから取れる現実的な道 (3 つ)
110
+
111
+ ### 道 A: スコープを `/compact` に切り替え
112
+
113
+ - Throughline は `/clear` を諦め、`/compact` への置換を提案
114
+ - PreCompact hook で「直近 N turn を保持し、それ以前を summary に置換」のロジックを差し込む
115
+ - 同一 session 内なので messages[] は real assistant turn として要約を持つ → 本人体感を維持
116
+ - 制約: ユーザーの習慣 ("/clear で切り替える") から外れる; /compact は context full のときに自動発火する別経路でもあるので、頻発させると体感が変わる
117
+ - **`/clear` シナリオには適用できない** (構造的に別物)
118
+
119
+ ### 道 B: Agent SDK / 自前ランタイム (= E)
120
+
121
+ - Claude Code を捨てて Agent SDK (Python or TypeScript) でラッパーを書く
122
+ - messages[] を完全制御 — synthetic user/assistant turn を自由に prepend
123
+ - /clear に相当する UX は自前で実装、内部的には messages[] を選択的に圧縮
124
+ - 制約: 大改修。Throughline は plugin から product になる
125
+ - これが「本人体感」を達成する唯一の正攻法
126
+
127
+ ### 道 C: 現状受容 + A2.0 文言調整
128
+
129
+ - 現行 stdout 注入のままで継続
130
+ - 案内文の文言を改善 (現行 A 実装) で「他人事感」を多少緩和
131
+ - 「本人体感」までは届かないが、「『何のこと?』が出ない」レベルは達成済み
132
+ - 制約: Throughline のミッション定義 (「体感」まで含む) を満たさない
133
+
134
+ ---
135
+
136
+ ## 推奨される判断順
137
+
138
+ 1. **Throughline のミッション定義の再確認**:
139
+ - 「体感まで保証」が必須 → 道 B (E pivot) しかない。CC plugin である限り構造的に不可能。
140
+ - 「配送までで OK、体感は best-effort」 → 道 C で十分。
141
+
142
+ 2. **`/clear` という UI シグナルへのこだわり**:
143
+ - 必須 → 道 B または C。/compact は別物。
144
+ - 妥協可能 → 道 A も視野 (ユーザーに /compact を勧める)
145
+
146
+ 3. **着手コスト**:
147
+ - 道 C: 小 (文言調整のみ。実装済み)
148
+ - 道 A: 中 (PreCompact hook + summary 制御)
149
+ - 道 B: 大 (別プロジェクト相当)
150
+
151
+ ---
152
+
153
+ ## 蓄積予定 (今後 RAG に足すべき調査)
154
+
155
+ - [ ] PreCompact / PostCompact hook の詳細仕様 (specific output schema)
156
+ - [ ] Agent SDK Python / TypeScript の messages[] 制御 API
157
+ - [ ] `claude --continue` / `--resume` の実 messages[] 構築フロー (JSONL → API call の変換)
158
+ - [ ] CC のバージョンによる hook 挙動差分 (2.1.128 で source='clear' が変わった等)
159
+ - [ ] 他の OSS CC 互換実装 (openclaude 以外) の messages[] 構築コード
160
+ - [ ] Codex の同等問題と解決手法 (Throughline は Claude / Codex 両対応)
@@ -0,0 +1,250 @@
1
+ # Claude Code Hooks Reference — Extract
2
+
3
+ Source: <https://code.claude.com/docs/en/hooks> (fetched 2026-05-24)
4
+
5
+ ## Hook events (31 total)
6
+
7
+ ### Per-Session
8
+
9
+ - `SessionStart` — session begins or resumes
10
+ - `Setup` — when launched with `--init-only` / `--init` / `--maintenance`
11
+ - `SessionEnd` — session terminates
12
+
13
+ ### Per-Turn
14
+
15
+ - `UserPromptSubmit` — user submits a prompt, before Claude processes it
16
+ - `UserPromptExpansion` — when a user-typed command expands into a prompt
17
+ - `Stop` — Claude finishes responding
18
+ - `StopFailure` — turn ends due to API error
19
+
20
+ ### Per-Tool-Call
21
+
22
+ - `PreToolUse` — before tool call
23
+ - `PostToolUse` — after tool call succeeds
24
+ - `PostToolUseFailure` — after tool call fails
25
+ - `PostToolBatch` — after parallel tool batch resolves
26
+ - `PermissionRequest` — when permission dialog appears
27
+ - `PermissionDenied` — denied by auto mode classifier
28
+
29
+ ### Agent/Team
30
+
31
+ - `SubagentStart` / `SubagentStop`
32
+ - `TaskCreated` / `TaskCompleted`
33
+ - `TeammateIdle`
34
+
35
+ ### File/Config
36
+
37
+ - `InstructionsLoaded` — when CLAUDE.md / `.claude/rules/*.md` loaded
38
+ - `ConfigChange`
39
+ - `FileChanged`
40
+ - `CwdChanged`
41
+ - `WorktreeCreate` / `WorktreeRemove`
42
+
43
+ ### Compaction
44
+
45
+ - `PreCompact` — before context compaction
46
+ - `PostCompact` — after context compaction
47
+
48
+ ### MCP
49
+
50
+ - `Elicitation` / `ElicitationResult`
51
+
52
+ ### Notification
53
+
54
+ - `Notification`
55
+
56
+ ---
57
+
58
+ ## Common input payload (all events)
59
+
60
+ ```json
61
+ {
62
+ "session_id": "abc123",
63
+ "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
64
+ "cwd": "/home/user/my-project",
65
+ "permission_mode": "default|plan|acceptEdits|auto|dontAsk|bypassPermissions",
66
+ "hook_event_name": "PreToolUse",
67
+ "effort": { "level": "low|medium|high|xhigh|max" },
68
+ "agent_id": "optional-subagent-id",
69
+ "agent_type": "optional-agent-name"
70
+ }
71
+ ```
72
+
73
+ ### SessionStart input
74
+
75
+ ```json
76
+ {
77
+ "session_id": "abc123",
78
+ "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
79
+ "cwd": "/Users/...",
80
+ "hook_event_name": "SessionStart",
81
+ "source": "startup|resume|clear|compact",
82
+ "model": "claude-sonnet-4-6"
83
+ }
84
+ ```
85
+
86
+ ### UserPromptSubmit input
87
+
88
+ ```json
89
+ {
90
+ "session_id": "abc123",
91
+ "transcript_path": "...",
92
+ "cwd": "/Users/...",
93
+ "permission_mode": "default",
94
+ "hook_event_name": "UserPromptSubmit",
95
+ "prompt": "Write a function to calculate the factorial of a number"
96
+ }
97
+ ```
98
+
99
+ ---
100
+
101
+ ## hookSpecificOutput
102
+
103
+ Common shape:
104
+
105
+ ```json
106
+ {
107
+ "hookSpecificOutput": {
108
+ "hookEventName": "PostToolUse",
109
+ "additionalContext": "text string to inject into Claude's context"
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### What `additionalContext` actually does (CRITICAL)
115
+
116
+ > The `additionalContext` field passes a string from your hook into Claude's context window. **Claude Code wraps the string in a system reminder** and inserts it into the conversation at the point where the hook fired. Claude reads the reminder on the next model request, but it does not appear as a chat message in the interface.
117
+
118
+ **Placement by event:**
119
+
120
+ | Event Category | Placement |
121
+ |---|---|
122
+ | `SessionStart`, `Setup`, `SubagentStart` | At the start of the conversation, before the first prompt |
123
+ | `UserPromptSubmit`, `UserPromptExpansion` | Alongside the submitted prompt |
124
+ | `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PostToolBatch` | Next to the tool result |
125
+
126
+ **Character limit:** 10,000 characters per context string. Excess → written to file in session dir, file path + preview passed to Claude.
127
+
128
+ **Content type:** delivered AS A SYSTEM REMINDER (not user message). Same category as stdout.
129
+
130
+ ### SessionStart hookSpecificOutput (THE KEY DISCOVERY)
131
+
132
+ ```json
133
+ {
134
+ "hookSpecificOutput": {
135
+ "hookEventName": "SessionStart",
136
+ "additionalContext": "context string",
137
+ "initialUserMessage": "first message (SessionStart only)",
138
+ "watchPaths": ["/path/to/watch1", "/path/to/watch2"]
139
+ }
140
+ }
141
+ ```
142
+
143
+ 🎯 **`initialUserMessage` is a SessionStart-only field that becomes the first user message of the session.** This goes into `messages[]` as a real user-role turn, NOT a system reminder.
144
+
145
+ This is the unexplored angle for Throughline: instead of injecting context (system reminder = "briefing"), inject the resume context AS the first user message ("the user just said this, including past history") so the model treats it as actual conversation input.
146
+
147
+ ### UserPromptSubmit hookSpecificOutput
148
+
149
+ ```json
150
+ {
151
+ "hookSpecificOutput": {
152
+ "hookEventName": "UserPromptSubmit",
153
+ "additionalContext": "context string",
154
+ "sessionTitle": "auto-set session title"
155
+ }
156
+ }
157
+ ```
158
+
159
+ (No `initialUserMessage` — it only exists on SessionStart.)
160
+
161
+ ---
162
+
163
+ ## Hook output handling
164
+
165
+ | Exit Code | Meaning | JSON Processed? | Blocking? |
166
+ |---|---|---|---|
167
+ | **0** | Success | YES (if JSON present) | No |
168
+ | **2** | Blocking error | NO (JSON ignored) | Yes (event-dependent) |
169
+ | Other | Non-blocking error | NO | No |
170
+
171
+ ### Stdout
172
+
173
+ | Event | stdout becomes Claude-visible? |
174
+ |---|---|
175
+ | `SessionStart` / `UserPromptSubmit` / `UserPromptExpansion` | YES (as system reminder) |
176
+ | Other events | NO (debug log only) |
177
+
178
+ ### Resume / replay behavior
179
+
180
+ > Once injected, the text is saved in the session transcript. For mid-session events like `PostToolUse` or `UserPromptSubmit`, resuming with `--continue` or `--resume` replays the saved text rather than re-running the hook for past turns, so values like timestamps or commit SHAs become stale on resume. **`SessionStart` hooks run again on resume with `source` set to `"resume"`, so they can refresh their context.**
181
+
182
+ ### JSON output fields (exit 0 only)
183
+
184
+ ```json
185
+ {
186
+ "continue": true,
187
+ "stopReason": "message if continue is false",
188
+ "suppressOutput": false,
189
+ "systemMessage": "warning shown to user",
190
+ "terminalSequence": "OSC escape sequence",
191
+ "hookSpecificOutput": {
192
+ "hookEventName": "EventName",
193
+ "additionalContext": "..."
194
+ }
195
+ }
196
+ ```
197
+
198
+ | Field | Default | Effect |
199
+ |---|---|---|
200
+ | `continue` | `true` | If `false`, Claude stops processing entirely |
201
+ | `stopReason` | — | Message shown when `continue: false` |
202
+ | `suppressOutput` | `false` | Hide hook's stdout from transcript (still in debug log) |
203
+ | `systemMessage` | — | Warning shown to user |
204
+ | `terminalSequence` | — | OSC 0/1/2/9/99/777, BEL only |
205
+
206
+ ### PreToolUse decision control (extra)
207
+
208
+ ```json
209
+ {
210
+ "hookSpecificOutput": {
211
+ "hookEventName": "PreToolUse",
212
+ "permissionDecision": "allow|deny|ask|defer",
213
+ "permissionDecisionReason": "explanation text",
214
+ "additionalContext": "context for Claude",
215
+ "updatedInput": { "command": "modified command" }
216
+ }
217
+ }
218
+ ```
219
+
220
+ `updatedInput` modifies tool parameters before execution. Note: only PreToolUse has `updatedInput`.
221
+
222
+ ### When multiple hooks return additionalContext
223
+
224
+ > When several hooks return `additionalContext` for the same event, Claude receives all of the values.
225
+
226
+ ---
227
+
228
+ ## Transcript / messages[] construction (incompletely documented)
229
+
230
+ The docs do NOT detail how messages[] is constructed from JSONL. What we can infer:
231
+
232
+ 1. Hook context = system reminders (not user/assistant messages)
233
+ 2. Stdout = system reminders for SessionStart / UserPromptSubmit / UserPromptExpansion, log-only otherwise
234
+ 3. Hook outputs are persisted in transcript and replayed on `--continue` / `--resume`
235
+ 4. SessionStart re-runs on resume with `source: "resume"` (so it can refresh)
236
+ 5. **`initialUserMessage`** appears to be the only documented way for a hook to inject a real user-role message
237
+
238
+ ---
239
+
240
+ ## Output method summary
241
+
242
+ | Method | Exit | Content Type | Visible to Claude? | Use Case |
243
+ |---|---|---|---|---|
244
+ | Plain stdout (SessionStart / UserPromptSubmit / UserPromptExpansion) | 0 | Text | YES (system reminder) | Quick context injection |
245
+ | Plain stdout (other) | 0 | Text | NO (debug log) | Logging only |
246
+ | Exit code 2 | 2 | stderr | YES (as error) | Policy enforcement |
247
+ | JSON `additionalContext` | 0 | JSON | YES (system reminder) | Structured context |
248
+ | **JSON `initialUserMessage` (SessionStart only)** | 0 | JSON | **YES (as user message!)** | **First user-role injection** |
249
+ | JSON `decision: "block"` | 0 | JSON | YES (decision reason) | Event-specific block |
250
+ | JSON `continue: false` | 0 | JSON | Halts session | Hard stop |
@@ -0,0 +1,126 @@
1
+ # Anthropic Messages API — Extract
2
+
3
+ Source: <https://platform.claude.com/docs/en/api/messages> (fetched 2026-05-24)
4
+
5
+ ## Messages array structure
6
+
7
+ ### Role values
8
+
9
+ Only `"user"` and `"assistant"`. No `"system"` role in messages[].
10
+
11
+ > "Our models are trained to operate on alternating `user` and `assistant` conversational turns."
12
+
13
+ > "Consecutive `user` or `assistant` turns in your request will be combined into a single turn."
14
+
15
+ ### Content block types
16
+
17
+ Each message's `content` is either a string (shorthand for `[{"type": "text", "text": "..."}]`) or an array of `ContentBlockParam`.
18
+
19
+ | Type | Schema | Purpose |
20
+ |---|---|---|
21
+ | `text` | `{type: "text", text, cache_control?, citations?}` | Plain text |
22
+ | `image` | `{type: "image", source, cache_control?}` | Images |
23
+ | `document` | `{type: "document", source, title?, context?, citations?, cache_control?}` | PDF / plain text |
24
+ | `tool_use` | `{type: "tool_use", id, name, input, caller?, cache_control?}` | Model's tool invocation |
25
+ | `tool_result` | `{type: "tool_result", tool_use_id, content?, is_error?, cache_control?}` | Tool execution result |
26
+ | `thinking` | `{type: "thinking", thinking, signature}` | Extended thinking (input only) |
27
+ | `redacted_thinking` | `{type: "redacted_thinking", data}` | Redacted thinking |
28
+ | `search_result` | `{type: "search_result", title, source, content, cache_control?, citations?}` | Web search results |
29
+ | `server_tool_use` | `{type: "server_tool_use", id, name, input, caller?, cache_control?}` | Server-side tool execution |
30
+
31
+ ---
32
+
33
+ ## System field (top-level)
34
+
35
+ ```json
36
+ {
37
+ "system": "You are a helpful assistant.",
38
+ "messages": [{"role": "user", "content": "Hello"}]
39
+ }
40
+ ```
41
+
42
+ > "Note that if you want to include a system prompt, you can use the top-level `system` parameter — there is no `system` role for input messages in the Messages API."
43
+
44
+ `system` accepts string OR `TextBlockParam[]`:
45
+
46
+ ```json
47
+ {
48
+ "system": [
49
+ {"type": "text", "text": "Today's date is 2024-06-01.",
50
+ "cache_control": {"type": "ephemeral", "ttl": "5m"}}
51
+ ]
52
+ }
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Conversation continuation & synthetic messages (CRITICAL FOR THROUGHLINE)
58
+
59
+ ### Synthetic assistant prefill
60
+
61
+ > "If the final message uses the `assistant` role, the response content will continue immediately from the content in that message. This can be used to constrain part of the model's response."
62
+
63
+ Example:
64
+
65
+ ```json
66
+ {
67
+ "messages": [
68
+ {"role": "user", "content": "What's the Greek name for Sun? (A) Sol (B) Helios (C) Sun"},
69
+ {"role": "assistant", "content": "The best answer is ("}
70
+ ]
71
+ }
72
+ ```
73
+
74
+ Response: model continues from `"The best answer is ("` and outputs `"B)"`.
75
+
76
+ ### No differentiation between real & synthetic messages (KEY)
77
+
78
+ > "When creating a new `Message`, you specify the prior conversational turns with the `messages` parameter, and the model then generates the next `Message` in the conversation."
79
+
80
+ **The documentation does not distinguish between:**
81
+
82
+ - Historically real messages from past API calls
83
+ - Newly constructed/injected synthetic messages in the current request
84
+
85
+ → Models treat all messages equally regardless of source. **No metadata field indicates whether a message was real or synthetic.**
86
+
87
+ This validates the D-route theory: IF we could inject prior turns into messages[], the model would treat them as continuation. The question is purely about **how to control messages[] from within Claude Code**.
88
+
89
+ ### Turn combining
90
+
91
+ > "Consecutive `user` or `assistant` turns in your request will be combined into a single turn."
92
+
93
+ This applies uniformly regardless of source.
94
+
95
+ ---
96
+
97
+ ## Fields outside messages[]
98
+
99
+ Top-level request parameters (relevant for context):
100
+
101
+ | Parameter | Type | Purpose |
102
+ |---|---|---|
103
+ | `system` | `string \| TextBlockParam[]` | System prompt |
104
+ | `model` | `string` | Required |
105
+ | `max_tokens` | `number` | Required |
106
+ | `temperature`, `top_p`, `top_k`, `stop_sequences` | — | sampling |
107
+ | `tools`, `tool_choice` | — | Tool definitions |
108
+ | `thinking` | `ThinkingConfig` | Extended thinking budget |
109
+ | `stream` | `boolean` | Streaming |
110
+ | `metadata` | `{user_id?}` | Request tracking |
111
+ | `cache_control` | `CacheControlEphemeral` | Top-level cache marker |
112
+ | `service_tier` | `"auto" \| "standard_only"` | Capacity tier |
113
+ | `output_config` | `{format?, effort?}` | Output formatting |
114
+
115
+ No "transcript" or "session_history" field — all conversation context goes through `messages[]` or `system`.
116
+
117
+ ---
118
+
119
+ ## Implications for Throughline
120
+
121
+ 1. **Only `messages[]` carries "real" conversation history**. `system` is briefing material.
122
+ 2. **The model treats all `messages[]` entries equally** — synthetic past turns are indistinguishable from real ones.
123
+ 3. **For "本人体感" (the assistant feeling like it was the one talking), the past turns MUST be in `messages[]`, not in `system`.**
124
+ 4. **Synthetic assistant prefill is officially supported** — we could end messages[] with an assistant turn to constrain continuation.
125
+
126
+ → Claude Code's challenge: how does CC build the API request's `messages[]`? If we can influence that (via `initialUserMessage` or another mechanism), we can solve the "本人体感" problem.