返回 CodeWhale
SURVIVAL_CONTRACT.md
根目录 / crates / tui / src / compaction / SURVIVAL_CONTRACT.md
1 # Compaction survival contract
2
3 Schema version 2.
4
5 The protected last round means the current user instructions and the two
6 most recent complete tool exchanges (including their assistant output).
7 Short rounds remain intact. Older completed steps within a long uninterrupted
8 task may be summarized; otherwise a single user request could grow forever.
9 Tool-call batches and their results are never split, and unresolved calls
10 prevent a split across them. Original history is saved before any rewrite.
11
12 Language-invariant schema for session-tree journal entry types. Compaction
13 may summarize older turns; it must not drop the fields marked **survive**
14 below. The B1 strategy (Rust `codewhale-tui` compaction path) enforces this
15 over the live journal / API transcript. Later TS/Go strategies validate
16 against the same field names.
17
18 Failed compact must not replace live history. A second compact must replace
19 the prior summary instead of stacking it, and must not grow the cacheable
20 prefix by duplicating checkpoints. Internal protocol placeholders
21 (`(no summary available)`, refusal-only text) are never durable transcript
22 content.
23
24 Live repository, tool, and GitHub state is distinguished from summarized
25 facts: background-work handles and the exact branch/worktree identity
26 survive here or are re-read from live state before consequential work.
27
28 ## Entry types
29
30 Journal kinds are the protocol `kind` strings (`header`, `user`,
31 `assistant`, `tool_result`, `compaction`, `branch_summary`). Session-tree
32 `message` / `system` entries carry the same payload on `SessionEntryKind`.
33
34 | kind | payload location | survive | summarized | pruned | not retained |
35 | --- | --- | --- | --- | --- | --- |
36 | `header` | `payload` identity, `id`, `created_at` | always | — | — | secrets |
37 | `user` | `payload` text / `SessionEntryKind::User.text` / `Message` text blocks | last-round verbatim (bounded token budget for older user text) | older user turns | obsolete narration | secrets, raw dumps |
38 | `assistant` | `payload` text / `SessionEntryKind::Assistant.text` / `Message` text + `tool_use` | protected last-round verbatim | older assistant text and reasoning | — | internal placeholders |
39 | `tool_result` | `payload.tool_use_id`, `content`, `is_error`, `content_blocks` | last-round bounded (8KiB) | older results | nested images, oversized blocks | secrets, raw large outputs |
40 | `compaction` | `payload.summary`, `tokens_before`, `tokens_after`, `model`, coverage receipt | latest checkpoint only | prior summaries replaced | stacked prior summaries | placeholder-only summaries |
41 | `branch_summary` | `payload.branch_id`, `summary`, `parent_branch_id` | always (not rewritten by compact) | — | — | — |
42 | `message` | flattened `Message.role` + `Message.content[]` | see content-block table | older turns | see prune rules | see never-durable |
43 | `system` | `SessionEntryKind::System.content` | constitution / repo-law identity when present | duplicated policy | — | unsupported completion claims |
44
45 ## Content blocks on `message` entries
46
47 | block `type` | fields that must survive | bound / notes |
48 | --- | --- | --- |
49 | `text` | `text` on the last user round | older user text may truncate to the retained-user token budget |
50 | `tool_use` | `id`, `name`, `input`, `thought_signature` | last-round only; `id` is the join key for `tool_result` |
51 | `tool_result` | `tool_use_id`, `content`, `is_error` | last-round; `content` truncated at 8KiB, `content_blocks` dropped if truncated |
52 | `thinking` | `thinking`, `signature` (byte-for-byte) | all retained reasoning is replay protocol; older exchanges may be summarized as complete units |
53 | `image_url` | last-round images | older images may be pruned with tool results |
54 | `server_tool_use` / `*_tool_result` | last-round ids | older blocks may be summarized |
55
56 ## Fields restated from live state (not the summary)
57
58 These must remain usable after compact. They live outside free-form summary
59 text; if the journal copy is missing they are re-read before consequential
60 work.
61
62 | field | where it lives | after compact |
63 | --- | --- | --- |
64 | `/anchor` pin | `.codewhale/anchors.md` (legacy `.deepseek/anchors.md`) | restated verbatim on the checkpoint user message |
65 | compaction path + coverage | `CompactionCoverage` on the result / `/context` inspector | named as `summary` or `prune-only` plus last-round counts |
66 | compaction receipt | checkpoint `user` message whose text contains the summary marker; TUI `HistoryCell::System` | latest receipt kept; failed compact does not write a replacement |
67 | billed parent prompt tokens | `TurnContext.latest_parent_input_tokens` / session-carried copy | survive the turn boundary; cleared on history rewrite |
68 | branch / worktree identity | live git / workspace | re-read from live state |
69 | background-work / WorkRef handles | session + worker registry | survive or re-read from live state |
70 | child usage / cumulative tool steps | turn billing totals | must not create false compaction pressure |
71
72 ## Enforcement
73
74 Rust now:
75
76 - `last_round::build_replacement_history` keeps the bounded protected last round
77 (assistant + tool results) and appends one checkpoint receipt. A trailing
78 toolless user/assistant tail still walks back to the last tool-bearing
79 round; chat-only sessions keep only the latest user turn.
80 - `validate_last_round_coverage` refuses the rewrite if any of that round's
81 user texts, protected tool-call ids, tool-result ids, or assistant texts would vanish.
82 Every user turn in the round is checked, not just the first: the round spans
83 a tool-bearing turn plus the toolless tail after it, so checking one would
84 let the latest turn -- the one this contract exists for -- be dropped. The
85 assistant check compares text, because "some assistant message survived" is
86 satisfied by the summary the rewrite itself just wrote.
87 - `validate_survival_contract` also refuses a missing checkpoint receipt,
88 duplicated prior summaries, dropped `/anchor` text, or a placeholder-only
89 checkpoint.
90 - `compact_messages_safe` never mutates the caller's live history; the host
91 commits only after `Ok`.
92
93 The language-invariant fixture matrix is
94 `crates/tui/src/compaction/fixtures/matrix.json`. Rust loads it in
95 `last_round` tests. `validate_survival_contract.mjs` is the same coverage
96 floor for a later TypeScript strategy (`node validate_survival_contract.mjs`).
97 There is no Go runtime in this repository yet; a Go validator remains a
98 follow-up when a Go strategy exists.
99
100 `/context` names the compaction path and `/anchor` survival.
101
101 lines MARKDOWN