throughline 0.4.12 → 0.6.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/.codex-sidecar.yml +5 -0
- package/CHANGELOG.md +106 -0
- package/README.ja.md +37 -21
- package/README.md +47 -26
- package/docs/00_overview.md +34 -0
- package/docs/{L1_L2_L3_REDESIGN.md → 01_l1_l2_l3_redesign.md} +3 -3
- package/docs/{THROUGHLINE_CLEAR_AUTO_HANDOFF_PLAN.md → 02_clear_auto_handoff_plan.md} +6 -6
- package/docs/{INHERITANCE_ON_CLEAR_ONLY.md → 03_inheritance_on_clear_only.md} +3 -3
- package/docs/{PUBLIC_RELEASE_PLAN.md → 04_public_release_plan.md} +3 -3
- package/docs/{THROUGHLINE_CODEX_FIRST_ROADMAP.md → 05_codex_first_roadmap.md} +9 -9
- package/docs/{THROUGHLINE_CODEX_TRIM_ROLLBACK_FIX_PLAN.md → 06_codex_trim_rollback_fix_plan.md} +6 -6
- package/docs/{THROUGHLINE_CODEX_TRIM_IMPLEMENTATION_PLAN.md → 07_codex_trim_implementation_plan.md} +10 -10
- package/docs/{THROUGHLINE_CODEX_DUAL_SUPPORT.md → 08_codex_dual_support.md} +8 -8
- package/docs/{throughline-rollback-context-trim-insight.md → 09_rollback_context_trim_insight.md} +5 -5
- package/docs/10_transcript_injection_plan.md +446 -0
- package/docs/{THROUGHLINE_CODEX_MONITOR_IMPLEMENTATION_PLAN.md → 11_codex_monitor_implementation_plan.md} +1 -1
- package/docs/12_desktop_clear_handoff_plan.md +215 -0
- package/docs/adr/0001-claude-primary-codex-adapter.md +22 -0
- package/docs/archive/README.md +3 -3
- package/docs/archive/THROUGHLINE_NEXT_STEPS.md +3 -3
- package/package.json +2 -1
- package/rag/01-hooks/raw/hooks-reference-extract.md +250 -0
- package/rag/01-hooks/raw/session-end-reasons.md +21 -0
- package/rag/02-messages-api/raw/messages-api-extract.md +126 -0
- package/rag/03-settings/raw/sessions-extract.md +64 -0
- package/rag/04-skills/raw/initialUserMessage-investigation.md +101 -0
- package/rag/INDEX.md +164 -0
- package/src/baton.mjs +2 -2
- package/src/db.mjs +2 -2
- package/src/hook-entrypoints.test.mjs +390 -0
- package/src/package-files.test.mjs +1 -0
- package/src/prompt-submit.mjs +132 -5
- package/src/resume-context.mjs +23 -6
- package/src/resume-context.test.mjs +21 -5
- package/src/session-merger.mjs +1 -1
- package/src/session-start.mjs +155 -14
- package/src/spike-transcript-writer.mjs +196 -0
- package/src/spike-transcript-writer.test.mjs +298 -0
- package/src/state-file.mjs +1 -1
- package/src/token-monitor.mjs +1 -1
- package/src/transcript-reader.mjs +71 -0
- package/src/turn-backfill.mjs +131 -0
- package/src/turn-backfill.test.mjs +213 -0
- package/src/turn-processor.mjs +28 -40
- /package/docs/{throughline-codex-trim-rollback-incident-report.md → audit-2026-05/codex-trim-rollback-incident-report.md} +0 -0
|
@@ -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.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Claude Code Sessions — Extract
|
|
2
|
+
|
|
3
|
+
Source: <https://code.claude.com/docs/en/sessions> (fetched 2026-05-24)
|
|
4
|
+
|
|
5
|
+
## Session storage
|
|
6
|
+
|
|
7
|
+
- JSONL at `~/.claude/projects/<project>/<session-id>.jsonl`
|
|
8
|
+
- `<project>` derived from working directory path
|
|
9
|
+
- Each line is a JSON object (message / tool use / metadata)
|
|
10
|
+
- Override storage location: `CLAUDE_CONFIG_DIR` env var
|
|
11
|
+
- Default retention: 30 days, configurable via `cleanupPeriodDays`
|
|
12
|
+
- Disable transcript writes: `CLAUDE_CODE_SKIP_PROMPT_HISTORY` (env), or `--no-session-persistence` (CLI flag)
|
|
13
|
+
|
|
14
|
+
## Resume / continue
|
|
15
|
+
|
|
16
|
+
| Command | Behavior |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `claude --continue` | Resumes most recent session in current directory |
|
|
19
|
+
| `claude --resume` | Opens session picker |
|
|
20
|
+
| `claude --resume <name>` | Resumes named session directly |
|
|
21
|
+
| `claude --from-pr <number>` | Resumes session linked to PR |
|
|
22
|
+
| `/resume` (inside session) | Switches to a different conversation |
|
|
23
|
+
|
|
24
|
+
## /clear behavior (KEY)
|
|
25
|
+
|
|
26
|
+
> `/clear`: start fresh with an empty context. **The previous conversation is saved and resumable**.
|
|
27
|
+
|
|
28
|
+
- /clear empties CC's in-memory context buffer
|
|
29
|
+
- Previous conversation remains in JSONL, retrievable via `--continue` / `--resume`
|
|
30
|
+
- This implies CC has BOTH an in-memory state AND the persisted JSONL — and after /clear, the in-memory state is reset but the file is preserved
|
|
31
|
+
|
|
32
|
+
## /compact behavior
|
|
33
|
+
|
|
34
|
+
> `/compact [instructions]`: replace history with a summary, optionally focused on what you specify
|
|
35
|
+
|
|
36
|
+
- Summarizes existing context in-place
|
|
37
|
+
- Optionally focused via instructions
|
|
38
|
+
- Triggers `PreCompact` / `PostCompact` hooks (mid-session events)
|
|
39
|
+
|
|
40
|
+
## /context
|
|
41
|
+
|
|
42
|
+
> `/context`: show what is currently consuming context
|
|
43
|
+
|
|
44
|
+
## Branch / fork
|
|
45
|
+
|
|
46
|
+
> `/branch [name]` or `--fork-session` with `--continue` / `--resume`:
|
|
47
|
+
> Creates a copy of the conversation so far and switches into it, leaving the original intact.
|
|
48
|
+
|
|
49
|
+
- Permission grants don't carry over to new branch
|
|
50
|
+
- Resuming the same session in two terminals interleaves messages into one transcript
|
|
51
|
+
|
|
52
|
+
## Naming
|
|
53
|
+
|
|
54
|
+
- `claude -n <name>` (at startup)
|
|
55
|
+
- `/rename <name>` (mid-session)
|
|
56
|
+
- `Ctrl+R` in session picker
|
|
57
|
+
- Auto-naming on plan accept
|
|
58
|
+
|
|
59
|
+
## Implications for Throughline
|
|
60
|
+
|
|
61
|
+
1. **/clear preserves JSONL**. CC's in-memory state is the canonical source for the new session's messages[], NOT the JSONL.
|
|
62
|
+
2. **/compact preserves session continuity** — same session_id, just summarized. This is the documented "memory preservation" path.
|
|
63
|
+
3. **`--continue` / `--resume` are the official "restore from JSONL" mechanisms** but they pull the WHOLE prior conversation (no Throughline-style L1/L2/L3 selective compression).
|
|
64
|
+
4. **There's no documented hook to influence in-memory state after /clear**, only context attachments (system reminders).
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# `initialUserMessage` Field — Investigation Results
|
|
2
|
+
|
|
3
|
+
## Sources
|
|
4
|
+
|
|
5
|
+
- Official hooks reference: <https://code.claude.com/docs/en/hooks> (mentions field in SessionStart hookSpecificOutput shape)
|
|
6
|
+
- First-party hook-dev SKILL: <https://github.com/anthropics/claude-code/blob/main/plugins/plugin-dev/skills/hook-development/SKILL.md> (does NOT mention)
|
|
7
|
+
- Open-source CC implementation `Gitlawb/openclaude`:
|
|
8
|
+
- `src/utils/hooks.ts` — schema definition
|
|
9
|
+
- `src/utils/sessionStart.ts` — storage + retrieval mechanism
|
|
10
|
+
- `src/cli/print.ts` — consumer (the critical one)
|
|
11
|
+
- `src/main.tsx` — initialMessages array construction
|
|
12
|
+
|
|
13
|
+
## The schema (confirmed)
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// From openclaude src/utils/hooks.ts and src/types/hooks.ts
|
|
17
|
+
interface HookResult {
|
|
18
|
+
additionalContext?: string
|
|
19
|
+
initialUserMessage?: string
|
|
20
|
+
watchPaths?: string[]
|
|
21
|
+
updatedInput?: Record<string, unknown>
|
|
22
|
+
// ...
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// SessionStart hook output processing
|
|
26
|
+
case 'SessionStart':
|
|
27
|
+
result.additionalContext = json.hookSpecificOutput.additionalContext
|
|
28
|
+
result.initialUserMessage = json.hookSpecificOutput.initialUserMessage
|
|
29
|
+
if ('watchPaths' in json.hookSpecificOutput && json.hookSpecificOutput.watchPaths) {
|
|
30
|
+
result.watchPaths = json.hookSpecificOutput.watchPaths
|
|
31
|
+
}
|
|
32
|
+
break
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## The CRITICAL constraint (from openclaude source comment)
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// src/cli/print.ts:
|
|
39
|
+
// SessionStart hooks can emit initialUserMessage — the first user turn for
|
|
40
|
+
// headless orchestrator sessions where stdin is empty.
|
|
41
|
+
takeInitialUserMessage,
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**`initialUserMessage` is for HEADLESS MODE ONLY.** It's invoked when `claude -p` is launched and stdin doesn't provide a prompt — the hook can supply the "first user turn" as a substitute.
|
|
45
|
+
|
|
46
|
+
## Storage / consumption mechanism
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// src/utils/sessionStart.ts
|
|
50
|
+
// Set by processSessionStartHooks when a hook emits initialUserMessage;
|
|
51
|
+
// consumed once by takeInitialUserMessage. This side channel avoids changing
|
|
52
|
+
// the return type of processSessionStartHooks.
|
|
53
|
+
let pendingInitialUserMessage: string | undefined = undefined
|
|
54
|
+
|
|
55
|
+
if (hookResult.initialUserMessage) {
|
|
56
|
+
pendingInitialUserMessage = hookResult.initialUserMessage
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function takeInitialUserMessage(): string | undefined {
|
|
60
|
+
const v = pendingInitialUserMessage
|
|
61
|
+
pendingInitialUserMessage = undefined
|
|
62
|
+
return v
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// src/cli/print.ts
|
|
68
|
+
const hookInitialUserMessage = takeInitialUserMessage()
|
|
69
|
+
if (hookInitialUserMessage) {
|
|
70
|
+
structuredIO.prependUserMessage(hookInitialUserMessage)
|
|
71
|
+
}
|
|
72
|
+
// then runHeadlessStreaming() processes the queue
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## What this means for Throughline
|
|
76
|
+
|
|
77
|
+
`initialUserMessage` does NOT solve the `/clear` continuation problem because:
|
|
78
|
+
|
|
79
|
+
1. `/clear` is an INTERACTIVE mode operation
|
|
80
|
+
2. Interactive mode always has a user typing the next prompt — there's no "missing first user message" slot to fill
|
|
81
|
+
3. `initialUserMessage` is consumed by `print.ts` (print mode = `claude -p`), not by the interactive REPL
|
|
82
|
+
|
|
83
|
+
**Conclusion**: `initialUserMessage` is the wrong tool for our problem. It can't be used to make `/clear` continuation feel native.
|
|
84
|
+
|
|
85
|
+
## What IS the right tool? (As of this research)
|
|
86
|
+
|
|
87
|
+
For interactive `/clear`-then-prompt continuity, the available hook surfaces are:
|
|
88
|
+
|
|
89
|
+
- `additionalContext` → system reminder ("briefing" framing, what we have now)
|
|
90
|
+
- `stdout` (SessionStart/UserPromptSubmit/UserPromptExpansion) → system reminder (same as above)
|
|
91
|
+
- `PreCompact`/`PostCompact` hooks → fire on /compact, not /clear (different code path)
|
|
92
|
+
- `decision: block` for UserPromptSubmit → can block prompts, doesn't help inject memory
|
|
93
|
+
- Modifying transcript JSONL externally → ignored by CC's in-memory parent chain (proven dead via Phase 0 experiments)
|
|
94
|
+
|
|
95
|
+
**The current Claude Code hook system does NOT provide a documented mechanism to inject true conversation history (messages[]) into an interactive session after /clear.**
|
|
96
|
+
|
|
97
|
+
The only paths that would solve this are:
|
|
98
|
+
|
|
99
|
+
1. **Use /compact instead of /clear** — preserves session continuity in-place; PreCompact hook can influence the summary
|
|
100
|
+
2. **Agent SDK rewrite (E)** — bypass Claude Code's REPL, build a custom runtime that controls messages[] directly
|
|
101
|
+
3. **API-level proxy** — intercept the Claude Code → Anthropic API call and rewrite messages[]
|
package/rag/INDEX.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
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
|
+
rag/
|
|
11
|
+
├── INDEX.md (this file — synthesized findings, paths forward)
|
|
12
|
+
├── 01-hooks/
|
|
13
|
+
│ ├── raw/hooks-reference-extract.md ← Claude Code hooks reference
|
|
14
|
+
│ └── raw/session-end-reasons.md ← SessionEnd reason enum + timeout 1.5s (2026-07-11 fetch)
|
|
15
|
+
├── 02-messages-api/
|
|
16
|
+
│ └── raw/messages-api-extract.md ← Anthropic Messages API spec
|
|
17
|
+
├── 03-settings/
|
|
18
|
+
│ └── raw/sessions-extract.md ← /clear, /compact, /resume behavior
|
|
19
|
+
└── 04-skills/
|
|
20
|
+
└── raw/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](01-hooks/raw/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](01-hooks/raw/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](04-skills/raw/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/10_transcript_injection_plan.md §6 Phase 0-6](../docs/10_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](02-messages-api/raw/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](03-settings/raw/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
|
+
### Finding 8: SessionEnd has a `clear` reason; built-in `/clear` never reaches UserPromptSubmit (2026-07-11)
|
|
86
|
+
|
|
87
|
+
SessionEnd reason enum: `clear|resume|logout|prompt_input_exit|bypass_permissions_disabled|other`、default timeout 1.5s(/clear にも適用)— [session-end-reasons.md](01-hooks/raw/session-end-reasons.md)。実測: ビルトイン /clear はどのクライアントでも UserPromptSubmit に届かない(同一セッション /tl 対照実験 ×2 + VSCode 2.1.207)。VSCode は `source:"clear"` を送るが Desktop 2.1.205 は `source:"startup"`(クライアント実装差・バージョン交絡棄却済み)。→ Desktop の /clear 検知は SessionEnd(reason='clear') が唯一の hook 経路候補(実機検証は [docs/12](../docs/12_desktop_clear_handoff_plan.md) A Phase 1)。
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Throughline 仮説の見直し
|
|
92
|
+
|
|
93
|
+
### 当初仮説 (D 経路)
|
|
94
|
+
|
|
95
|
+
「JSONL に user/assistant 行を append すれば、CC が messages[] 構築時にそれを読む」
|
|
96
|
+
|
|
97
|
+
**実測 (4 ラン): すべて「ない」 → 反証済み**
|
|
98
|
+
|
|
99
|
+
JSONL は read 対象ではなく、CC の in-memory state が messages[] のソース。書き込んでも再読込されない。
|
|
100
|
+
|
|
101
|
+
### 第二仮説 (initialUserMessage)
|
|
102
|
+
|
|
103
|
+
「`initialUserMessage` で interactive モードでも first user message を注入できる」
|
|
104
|
+
|
|
105
|
+
**spec 調査 (openclaude source): HEADLESS-ONLY → 反証済み**
|
|
106
|
+
|
|
107
|
+
### 第三仮説 (PreCompact での再投影)
|
|
108
|
+
|
|
109
|
+
未検証。`/compact` のタイミングで PreCompact hook が「これを summary に必ず含めろ」と影響を与えられるなら、compaction 後の messages[] は本物の assistant turn として要約を含む可能性。
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## ここから取れる現実的な道 (3 つ)
|
|
114
|
+
|
|
115
|
+
### 道 A: スコープを `/compact` に切り替え
|
|
116
|
+
|
|
117
|
+
- Throughline は `/clear` を諦め、`/compact` への置換を提案
|
|
118
|
+
- PreCompact hook で「直近 N turn を保持し、それ以前を summary に置換」のロジックを差し込む
|
|
119
|
+
- 同一 session 内なので messages[] は real assistant turn として要約を持つ → 本人体感を維持
|
|
120
|
+
- 制約: ユーザーの習慣 ("/clear で切り替える") から外れる; /compact は context full のときに自動発火する別経路でもあるので、頻発させると体感が変わる
|
|
121
|
+
- **`/clear` シナリオには適用できない** (構造的に別物)
|
|
122
|
+
|
|
123
|
+
### 道 B: Agent SDK / 自前ランタイム (= E)
|
|
124
|
+
|
|
125
|
+
- Claude Code を捨てて Agent SDK (Python or TypeScript) でラッパーを書く
|
|
126
|
+
- messages[] を完全制御 — synthetic user/assistant turn を自由に prepend
|
|
127
|
+
- /clear に相当する UX は自前で実装、内部的には messages[] を選択的に圧縮
|
|
128
|
+
- 制約: 大改修。Throughline は plugin から product になる
|
|
129
|
+
- これが「本人体感」を達成する唯一の正攻法
|
|
130
|
+
|
|
131
|
+
### 道 C: 現状受容 + A2.0 文言調整
|
|
132
|
+
|
|
133
|
+
- 現行 stdout 注入のままで継続
|
|
134
|
+
- 案内文の文言を改善 (現行 A 実装) で「他人事感」を多少緩和
|
|
135
|
+
- 「本人体感」までは届かないが、「『何のこと?』が出ない」レベルは達成済み
|
|
136
|
+
- 制約: Throughline のミッション定義 (「体感」まで含む) を満たさない
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 推奨される判断順
|
|
141
|
+
|
|
142
|
+
1. **Throughline のミッション定義の再確認**:
|
|
143
|
+
- 「体感まで保証」が必須 → 道 B (E pivot) しかない。CC plugin である限り構造的に不可能。
|
|
144
|
+
- 「配送までで OK、体感は best-effort」 → 道 C で十分。
|
|
145
|
+
|
|
146
|
+
2. **`/clear` という UI シグナルへのこだわり**:
|
|
147
|
+
- 必須 → 道 B または C。/compact は別物。
|
|
148
|
+
- 妥協可能 → 道 A も視野 (ユーザーに /compact を勧める)
|
|
149
|
+
|
|
150
|
+
3. **着手コスト**:
|
|
151
|
+
- 道 C: 小 (文言調整のみ。実装済み)
|
|
152
|
+
- 道 A: 中 (PreCompact hook + summary 制御)
|
|
153
|
+
- 道 B: 大 (別プロジェクト相当)
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 蓄積予定 (今後 RAG に足すべき調査)
|
|
158
|
+
|
|
159
|
+
- [ ] PreCompact / PostCompact hook の詳細仕様 (specific output schema)
|
|
160
|
+
- [ ] Agent SDK Python / TypeScript の messages[] 制御 API
|
|
161
|
+
- [ ] `claude --continue` / `--resume` の実 messages[] 構築フロー (JSONL → API call の変換)
|
|
162
|
+
- [ ] CC のバージョンによる hook 挙動差分 (2.1.128 で source='clear' が変わった等)
|
|
163
|
+
- [ ] 他の OSS CC 互換実装 (openclaude 以外) の messages[] 構築コード
|
|
164
|
+
- [ ] Codex の同等問題と解決手法 (Throughline は Claude / Codex 両対応)
|
package/src/baton.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* baton.mjs — 引き継ぎバトン管理
|
|
3
3
|
*
|
|
4
|
-
* バトン方式の設計 (docs/
|
|
4
|
+
* バトン方式の設計 (docs/02_clear_auto_handoff_plan.md):
|
|
5
5
|
* - 新仕様では `/clear` 自動引継ぎがデフォルト ON。バトンは「/clear 自動引継ぎを
|
|
6
6
|
* 使わずに明示的に引き継ぎたい」ユーザーのための逃げ道。
|
|
7
7
|
* - ユーザーが旧セッションで `/tl` スラッシュコマンドを打つ → UserPromptSubmit hook が
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* "startup" に潰される問題 (#49937) に対する明示意思マーカーとして導入。
|
|
15
15
|
* 2026-05-08 時点で Claude Code 2.1.128 で source='clear' は reliable に
|
|
16
16
|
* なったため auto path 中心の設計に変わったが、明示意思の signal として
|
|
17
|
-
* baton 仕組み自体は残す。詳細は docs/
|
|
17
|
+
* baton 仕組み自体は残す。詳細は docs/02_clear_auto_handoff_plan.md。
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
/**
|
package/src/db.mjs
CHANGED
|
@@ -180,7 +180,7 @@ function initSchema(db) {
|
|
|
180
180
|
// v5 → v6: handoff_batons テーブル追加(/tl スラッシュコマンドによる明示的引き継ぎ指名用)
|
|
181
181
|
// - project_path ごとに最新 1 件のみ (PRIMARY KEY)
|
|
182
182
|
// - SessionStart で読み出し、TTL 以内なら merge して DELETE
|
|
183
|
-
// - docs/
|
|
183
|
+
// - docs/03_inheritance_on_clear_only.md 参照: 案 D (時間差) 撤去、バトン方式へ移行
|
|
184
184
|
if (version < 6) {
|
|
185
185
|
db.exec(`
|
|
186
186
|
CREATE TABLE IF NOT EXISTS handoff_batons (
|
|
@@ -203,7 +203,7 @@ function initSchema(db) {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
// v7 → v8: handoff_batons から memo_text 列を drop。
|
|
206
|
-
// 新仕様 (docs/
|
|
206
|
+
// 新仕様 (docs/02_clear_auto_handoff_plan.md) で memo 廃止:
|
|
207
207
|
// - /clear 自動引継ぎ (SessionStart source='clear') + /tl baton (memo なし) の 2 経路に
|
|
208
208
|
// - 注入は L1 + L2 + L3 refs のみ
|
|
209
209
|
// - save-inflight CLI / updateBatonMemo 関数も併せて削除
|