Files
oh-my-pi/docs/compaction.md
T
can1357 2e45297c43 docs(docs): moved documentation to root docs directory and updated all references
- Moved documentation files from packages/coding-agent/docs/ to root docs/ directory to flatten the documentation structure.
- Updated all internal documentation links to account for the new file locations, adjusting relative paths to maintain correct references across the monorepo.
- Updated README.md and issue template configuration to reference documentation at the new root docs/ location instead of packages/coding-agent/docs/.
2026-02-16 18:33:03 +01:00

356 lines
13 KiB
Markdown

# Compaction and Branch Summaries
Compaction and branch summaries are the two mechanisms that keep long sessions usable without losing prior work context.
- **Compaction** rewrites old history into a summary on the current branch.
- **Branch summary** captures abandoned branch context during `/tree` navigation.
Both are persisted as session entries and converted back into user-context messages when rebuilding LLM input.
## Key implementation files
- `src/session/compaction/compaction.ts`
- `src/session/compaction/branch-summarization.ts`
- `src/session/compaction/pruning.ts`
- `src/session/compaction/utils.ts`
- `src/session/session-manager.ts`
- `src/session/agent-session.ts`
- `src/session/messages.ts`
- `src/extensibility/hooks/types.ts`
- `src/config/settings-schema.ts`
## Session entry model
Compaction and branch summaries are first-class session entries, not plain assistant/user messages.
- `CompactionEntry`
- `type: "compaction"`
- `summary`, optional `shortSummary`
- `firstKeptEntryId` (compaction boundary)
- `tokensBefore`
- optional `details`, `preserveData`, `fromExtension`
- `BranchSummaryEntry`
- `type: "branch_summary"`
- `fromId`, `summary`
- optional `details`, `fromExtension`
When context is rebuilt (`buildSessionContext`):
1. Latest compaction on the active path is converted to one `compactionSummary` message.
2. Kept entries from `firstKeptEntryId` to the compaction point are re-included.
3. Later entries on the path are appended.
4. `branch_summary` entries are converted to `branchSummary` messages.
5. `custom_message` entries are converted to `custom` messages.
Those custom roles are then transformed into LLM-facing user messages in `convertToLlm()` using the static templates:
- `prompts/compaction/compaction-summary-context.md`
- `prompts/compaction/branch-summary-context.md`
## Compaction pipeline
### Triggers
Compaction can run in three ways:
1. **Manual**: `/compact [instructions]` calls `AgentSession.compact(...)`.
2. **Automatic overflow recovery**: after an assistant error that matches context overflow.
3. **Automatic threshold compaction**: after a successful turn when context exceeds threshold.
### Compaction shape (visual)
```text
Before compaction:
entry: 0 1 2 3 4 5 6 7 8 9
┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘
└────────┬───────┘ └──────────────┬──────────────┘
messagesToSummarize kept messages
↑
firstKeptEntryId (entry 4)
After compaction (new entry appended):
entry: 0 1 2 3 4 5 6 7 8 9 10
┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘
└──────────┬──────┘ └──────────────────────┬───────────────────┘
not sent to LLM sent to LLM
↑
starts from firstKeptEntryId
What the LLM sees:
┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐
│ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │
└────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘
↑ ↑ └─────────────────┬────────────────┘
prompt from cmp messages from firstKeptEntryId
```
### Overflow-retry vs threshold compaction
The two automatic paths are intentionally different:
- **Overflow-retry compaction**
- Trigger: current-model assistant error is detected as context overflow.
- The failing assistant error message is removed from active agent state before retry.
- Auto compaction runs with `reason: "overflow"` and `willRetry: true`.
- On success, agent auto-continues (`agent.continue()`) after compaction.
- **Threshold compaction**
- Trigger: `contextTokens > contextWindow - compaction.reserveTokens`.
- Runs with `reason: "threshold"` and `willRetry: false`.
- On success, if `compaction.autoContinue !== false`, injects a synthetic prompt:
- `"Continue if you have next steps."`
### Pre-compaction pruning
Before compaction checks, tool-result pruning may run (`pruneToolOutputs`).
Default prune policy:
- Protect newest `40_000` tool-output tokens.
- Require at least `20_000` total estimated savings.
- Never prune tool results from `skill` or `read`.
Pruned tool results are replaced with:
- `[Output truncated - N tokens]`
If pruning changes entries, session storage is rewritten and agent message state is refreshed before compaction decisions.
### Boundary and cut-point logic
`prepareCompaction()` only considers entries since the last compaction entry (if any).
1. Find previous compaction index.
2. Compute `boundaryStart = prevCompactionIndex + 1`.
3. Adapt `keepRecentTokens` using measured usage ratio when available.
4. Run `findCutPoint()` over the boundary window.
Valid cut points include:
- message entries with roles: `user`, `assistant`, `bashExecution`, `hookMessage`, `branchSummary`, `compactionSummary`
- `custom_message` entries
- `branch_summary` entries
Hard rule: never cut at `toolResult`.
If there are non-message metadata entries immediately before the cut point (`model_change`, `thinking_level_change`, labels, etc.), they are pulled into the kept region by moving cut index backward until a message or compaction boundary is hit.
### Split-turn handling
If cut point is not at a user-turn start, compaction treats it as a split turn.
Turn start detection treats these as user-turn boundaries:
- `message.role === "user"`
- `message.role === "bashExecution"`
- `custom_message` entry
- `branch_summary` entry
Split-turn compaction generates two summaries:
1. History summary (`messagesToSummarize`)
2. Turn-prefix summary (`turnPrefixMessages`)
Final stored summary is merged as:
```markdown
<history summary>
---
**Turn Context (split turn):**
<turn prefix summary>
```
### Summary generation
`compact(...)` builds summaries from serialized conversation text:
1. Convert messages via `convertToLlm()`.
2. Serialize with `serializeConversation()`.
3. Wrap in `<conversation>...</conversation>`.
4. Optionally include `<previous-summary>...</previous-summary>`.
5. Optionally inject hook context as `<additional-context>` list.
6. Execute summarization prompt with `SUMMARIZATION_SYSTEM_PROMPT`.
Prompt selection:
- first compaction: `compaction-summary.md`
- iterative compaction with prior summary: `compaction-update-summary.md`
- split-turn second pass: `compaction-turn-prefix.md`
- short UI summary: `compaction-short-summary.md`
Remote summarization mode:
- If `compaction.remoteEndpoint` is set, compaction POSTs:
- `{ systemPrompt, prompt }`
- Expects JSON containing at least `{ summary }`.
### File-operation context in summaries
Compaction tracks cumulative file activity using assistant tool calls:
- `read(path)` → read set
- `write(path)` → modified set
- `edit(path)` → modified set
Cumulative behavior:
- Includes prior compaction details only when prior entry is pi-generated (`fromExtension !== true`).
- In split turns, includes turn-prefix file ops too.
- `readFiles` excludes files also modified.
Summary text gets file tags appended via prompt template:
```xml
<read-files>
...
</read-files>
<modified-files>
...
</modified-files>
```
### Persist and reload
After summary generation (or hook-provided summary), agent session:
1. Appends `CompactionEntry` with `appendCompaction(...)`.
2. Rebuilds context via `buildSessionContext()`.
3. Replaces live agent messages with rebuilt context.
4. Emits `session_compact` hook event.
## Branch summarization pipeline
Branch summarization is tied to tree navigation, not token overflow.
### Trigger
During `navigateTree(...)`:
1. Compute abandoned entries from old leaf to common ancestor using `collectEntriesForBranchSummary(...)`.
2. If caller requested summary (`options.summarize`), generate summary before switching leaf.
3. If summary exists, attach it at the navigation target using `branchWithSummary(...)`.
Operationally this is commonly driven by `/tree` flow when `branchSummary.enabled` is enabled.
### Branch switch shape (visual)
```text
Tree before navigation:
┌─ B ─ C ─ D (old leaf, being abandoned)
A ───┤
└─ E ─ F (target)
Common ancestor: A
Entries to summarize: B, C, D
After navigation with summary:
┌─ B ─ C ─ D ─ [summary of B,C,D]
A ───┤
└─ E ─ F (new leaf)
```
### Preparation and token budget
`generateBranchSummary(...)` computes budget as:
- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`
`prepareBranchEntries(...)` then:
1. First pass: collect cumulative file ops from all summarized entries, including prior pi-generated `branch_summary` details.
2. Second pass: walk newest → oldest, adding messages until token budget is reached.
3. Prefer preserving recent context.
4. May still include large summary entries near budget edge for continuity.
Compaction entries are included as messages (`compactionSummary`) during branch summarization input.
### Summary generation and persistence
Branch summarization:
1. Converts and serializes selected messages.
2. Wraps in `<conversation>`.
3. Uses custom instructions if supplied, otherwise `branch-summary.md`.
4. Calls summarization model with `SUMMARIZATION_SYSTEM_PROMPT`.
5. Prepends `branch-summary-preamble.md`.
6. Appends file-operation tags.
Result is stored as `BranchSummaryEntry` with optional details (`readFiles`, `modifiedFiles`).
## Extension and hook touchpoints
### `session_before_compact`
Pre-compaction hook.
Can:
- cancel compaction (`{ cancel: true }`)
- provide full custom compaction payload (`{ compaction: CompactionResult }`)
### `session.compacting`
Prompt/context customization hook for default compaction.
Can return:
- `prompt` (override base summary prompt)
- `context` (extra context lines injected into `<additional-context>`)
- `preserveData` (stored on compaction entry)
### `session_compact`
Post-compaction notification with saved `compactionEntry` and `fromExtension` flag.
### `session_before_tree`
Runs on tree navigation before default branch summary generation.
Can:
- cancel navigation
- provide custom `{ summary: { summary, details } }` used when user requested summarization
### `session_tree`
Post-navigation event exposing new/old leaf and optional summary entry.
## Runtime behavior and failure semantics
- Manual compaction aborts current agent operation first.
- `abortCompaction()` cancels both manual and auto-compaction controllers.
- Auto compaction emits start/end session events for UI/state updates.
- Auto compaction can try multiple model candidates and retry transient failures.
- Overflow errors are excluded from generic retry path because they are handled by compaction.
- If auto-compaction fails:
- overflow path emits `Context overflow recovery failed: ...`
- threshold path emits `Auto-compaction failed: ...`
- Branch summarization can be cancelled via abort signal (e.g., Escape), returning canceled/aborted navigation result.
## Settings and defaults
From `settings-schema.ts`:
- `compaction.enabled` = `true`
- `compaction.reserveTokens` = `16384`
- `compaction.keepRecentTokens` = `20000`
- `compaction.autoContinue` = `true`
- `compaction.remoteEndpoint` = `undefined`
- `branchSummary.enabled` = `false`
- `branchSummary.reserveTokens` = `16384`
These values are consumed at runtime by `AgentSession` and compaction/branch summarization modules.