18 KiB
Configuration reference
Full contextPrune settings, commands, footer widget states, spilled outputs,
and the summarizer-model-by-plan table. See the README for the
6 knobs most people touch and the conceptual model; see
PRUNING.md for the algorithm behind each setting.
Full settings JSON
Settings live under the contextPrune key in <agent-dir>/settings.json (i.e. pi's own settings file). <agent-dir> is $PI_CODING_AGENT_DIR if set, otherwise ~/.pi/agent. Each pi preset gets its own settings, so you can run different summarizer models per preset. Missing fields use the maintained my-pi defaults in memory; explicit values are preserved and extension load does not rewrite the file.
{
"contextPrune": {
"enabled": true,
"showPruneStatusLine": true,
"summarizerModel": "default",
"summarizerThinking": "default",
"summarizerIdleTimeoutMs": 90000,
"summarizerMaxTimeoutMs": 300000,
"summarizerMaxRetries": 2,
"summarizerRetryBaseDelayMs": 1000,
"summarizerRetryMaxDelayMs": 8000,
"pruneOn": "agent-message",
"batchingMode": "turn",
"quietOversizedSkips": false,
"minBatchChars": 1000,
"recoveryGraceTurns": 3,
"protectedTools": [],
"protectedPaths": ["**/skills/**/*.md"],
"dedupByContentHash": true,
"autoBudgetThreshold": 0.735,
"spillThreshold": 65536,
"spillPreviewBytes": 2048,
"budgetTurnDelta": 0.04,
"chainCompression": {
"enabled": true,
"rollingWindow": 3,
"stripFinalAssistantThinking": true,
"fuseRangeSummary": true
}
}
}
Every key
| Key | Values | Default | Notes |
|---|---|---|---|
enabled |
true / false |
true |
Master switch |
showPruneStatusLine |
true / false |
true |
Footer widget + queued-turn notifications |
summarizerModel |
"default" or "provider/model-id" |
"default" |
default = your active pi model. See Choosing a summarizer model |
summarizerThinking |
default/off/minimal/low/medium/high/xhigh |
default |
Provider-specific reasoning effort knob |
pruneOn |
agent-message / on-demand |
agent-message |
Trigger mode - see README Architecture section |
batchingMode |
turn / agent-message |
turn |
How coarse each summary is (independent of pruneOn) |
quietOversizedSkips |
true / false |
false |
Silences skipped-oversized / skipped-trivial info notifications |
minBatchChars |
non-negative integer, 0 disables |
1000 |
Pre-flush guard - batches smaller than this skip the LLM entirely |
recoveryGraceTurns |
non-negative integer (user-turn-groups), 0 disables |
3 |
After a context_tree_query recovery, render that tool's output verbatim for this many user-turn-groups before re-stubbing it. Enforced at render time (Phase 1 + chain-compression eligibility), not at capture time. See PRUNING.md § What Pruning Does |
summarizerIdleTimeoutMs |
non-negative integer (ms), 0 disables |
90000 |
Abort a summarizer attempt after this much silence. Resets on every stream event and also bounds time-to-first-token. A timeout is transient and can consume retry slots. |
summarizerMaxTimeoutMs |
non-negative integer (ms), 0 disables |
300000 |
Hard ceiling on one summarizer attempt. Each retry is independently bounded. |
summarizerMaxRetries |
integer 0-10 |
2 |
Transient retries after the initial attempt. 2 means three total attempts. With a distinct summarizer model, the final slot is reserved for session-model fallback. 0 reports the first failure immediately and disables fallback rescue. |
summarizerRetryBaseDelayMs |
non-negative integer (ms) | 1000 |
Delay before retry 1; subsequent waits double. 0 makes retries immediate. |
summarizerRetryMaxDelayMs |
non-negative integer (ms) | 8000 |
Per-wait cap for exponential retry backoff. 0 makes retries immediate. |
protectedTools |
string[] |
[] |
Never-pruned tool names (e.g. ["todowrite","todoread"]). When a protected tool's chain is range-compressed, its output is preserved verbatim inside the <compressed-chain> block as <protected-output> - protected outputs are never lost. |
protectedPaths |
string[] |
["**/skills/**/*.md"] |
Globs matched against a tool call's args.path; matching outputs are never pruned (same semantics as protectedTools, including <protected-output> relocation in compressed chains). Already-summarized matching reads are repaired on the next turn; chain-compressed ones are not. Set [] to disable. |
dedupByContentHash |
true / false |
true |
Re-reads of identical (toolName, content) skip the LLM and alias the original |
autoBudgetThreshold |
fraction 0-1, or null |
0.735 |
Token-budget auto-flush at 73.5% of the window or 300k tokens, whichever comes first, regardless of pruneOn. null disables it. See Token-budget auto-flush |
spillThreshold |
positive integer | 65536 |
Minimum chars (resultText.length) for a single tool result to be spilled eagerly to a sidecar file at capture time rather than waiting for normal summarization. Non-positive / invalid values fall back to the default; to effectively disable spilling, set it above any result you expect. See Spilled outputs |
spillPreviewBytes |
non-negative integer | 2048 |
Head preview (bytes) kept inline in the stub and index record for a spilled result. Full body is on disk. |
budgetTurnDelta |
fraction 0-1 |
0.04 |
Force a flush when one turn grows context by at least 4%, ORed with autoBudgetThreshold. The fraction is measured against min(context window, 300k). |
chainCompression.enabled |
true / false |
true |
Master toggle for chain-level range compression |
chainCompression.rollingWindow |
positive integer | 3 |
Keep this many most-recent closed chains raw; compress older ones |
chainCompression.stripFinalAssistantThinking |
true / false |
true |
Strip thinking blocks from the kept final text-only assistant when compressing |
chainCompression.fuseRangeSummary |
true / false |
true |
Fuse a compressed chain's per-batch summaries into one cohesive LLM summary (one extra summarizer call per multi-batch span); off keeps the per-batch concatenation |
purgeErrors.enabled |
true / false |
true |
Replace failed toolCall argument bodies with compact stubs after cooldown |
purgeErrors.cooldownTurns |
positive integer | 2 |
Turns to wait after a tool error before purging its argument body |
purgeErrors.minArgChars |
non-negative integer | 500 |
Only purge arg bodies at least this many characters long |
See PRUNING.md § Chain Compression and PRUNING.md § Error Purge for the full algorithms.
The three pre-flush features (minBatchChars, protectedTools, dedupByContentHash) are explained in PRUNING.md § Pre-flush Pipeline & Safeguards. They run BEFORE any summarizer LLM call and can each drop a batch outright while still advancing the prune frontier.
Token-budget auto-flush
When autoBudgetThreshold is set to a value in (0, 1], the extension checks context usage at the end of every tool-using turn. If tokens reaches min(300_000, threshold * contextWindow), ALL pending batches are flushed immediately - regardless of pruneOn mode. This is an additional trigger layered on top of pruneOn, not a replacement.
0.8means 80% of the context window - it is a fraction, not a percentage.0.8 != 80.- The trigger point is capped at 300,000 tokens (
MAX_BUDGET_WINDOWinsrc/budget.ts). Without it, an advertised 1M window makes every setting unreachable:0.9would mean 900k tokens, so a session ends before pruning ever fires. The ceiling never binds on a model advertising 300k or less - a 256k window at0.9still fires at 230.4k - so only huge-window models change behavior, and they flush earlier. budgetTurnDeltaapplies the same ceiling in the other shape: growth is measured againstmin(context window, 300k), because a 300k ceiling on a single turn's growth could never bind.- The trigger is a no-op when
tokensisnull(right after a provider-side compaction); it resumes once usage is known again. - Editable live via
/pruner settings(row "Auto-flush at context %", presets Off / 60 / 70 / 80 / 90%). - Default
null= off.
Inspired by DCP's maxContextLimit nudging; simplified to a single threshold that forces a flush rather than separate nudge/force levels.
Spilled outputs
Single tool results larger than spillThreshold chars are written to <session-dir>/<sessionId>-blobs/<key>.txt at capture time and replaced in context with a short stub (tool name, byte size, head preview, file path). <key> is the sanitized occurrence key id@resultTimestamp (@ becomes _, so bash_23@1150 -> bash_23_1150.txt), not the bare toolCallId - keying on the bare id alone would let two occurrences of one reused provider id share a path and the second overwrite the first's blob. Recovery reads the spillPath persisted on the record rather than re-deriving it from the id, so a bare-id-named sidecar (no occurrence timestamp) still resolves. See PRUNING.md § Occurrence Identity. The full body is recoverable via the native read tool at the embedded path (offset/limit supported) or via context_tree_query by id, which falls back to the inline preview if the sidecar is missing. Moving a session .jsonl without its -blobs/ directory loses only the giant-blob recovery path; bodies under spillThreshold stay inline in the index entry as usual.
Choosing a summarizer model
The default setting reuses whatever model you have active in pi - convenient but wasteful, since summary writing doesn't need a top-tier coding model. Picking the smallest/fastest model on your plan saves both latency and cost.
If the configured summarizer model suffers a transient outage while your active pi model is healthy, pi-condense automatically falls back to the session model for the duration (with a one-time notice) and probes the configured model back every few minutes - no configuration needed.
| Plan | Suggested summarizer |
|---|---|
| OpenAI / Codex / Copilot | openai/gpt-4.1-mini, google/gemini-2.5-flash, xai/grok-3-fast |
| OpenRouter | openrouter/qwen/qwen3-30b-a3b (cheap MoE) |
| Anthropic direct | anthropic/claude-haiku-3-5 |
| Google AI direct | google/gemini-2.5-flash |
Set it from the slash command (saves immediately):
/pruner model openai/gpt-4.1-mini
/pruner thinking low
# or both in one go:
/pruner model openai/gpt-4.1-mini:low
Summarizer timeouts and retries
Every summarizer attempt is bounded by two independent timers:
- Idle timeout (
summarizerIdleTimeoutMs, default 90s) resets on every stream event, including reasoning/thinkingevents. It also bounds time-to-first-token. - Total-duration ceiling (
summarizerMaxTimeoutMs, default 300s) is a hard upper bound for one attempt.
Timeouts and provider failures are classified as transient. The initial call is
followed by at most summarizerMaxRetries retries (default 2), with exponential
backoff starting at summarizerRetryBaseDelayMs and capped by
summarizerRetryMaxDelayMs (1s → 2s under maintained defaults). The UI
reports an error only after the retry budget is exhausted. Each attempt has its
own idle and ceiling timers, so the worst-case duration can span multiple attempt
ceilings; user abort remains immediate, including during backoff.
Authentication failures and unusable empty/length-truncated summaries do not
consume retries. With a distinct summarizerModel, the final retry slot switches
to the active session model and feeds the sticky fallback controller. Set
summarizerMaxRetries to 0 to disable retries and fallback rescue. Both timeout
fields can independently be set to 0.
Commands
| Command | Effect |
|---|---|
/pruner |
Interactive picker over all subcommands |
/pruner settings |
Settings overlay (toggle / cycle every option) |
/pruner on / off |
Enable / disable pruning |
/pruner status |
Show mode, model, trigger, cumulative stats, and a --- context --- block (see below) |
/pruner stats |
Detailed cumulative summarizer token/cost stats |
/pruner model [id[:thinking]] |
Get / set summarizer model (and optionally thinking level) |
/pruner thinking [level] |
Get / set summarizer reasoning effort |
/pruner prune-on [mode] |
Get / set trigger mode |
/pruner batching [mode] |
Get / set batching granularity (turn / agent-message) |
/pruner protected-tools [names] |
Show or edit the never-pruned tool allowlist (comma- or space-separated; none clears) |
/pruner protected-paths [globs] |
Show or edit the never-pruned path globs (none clears) |
/pruner min-batch-chars [n] |
Show or set the pre-flush trivial-batch threshold (0 disables) |
/pruner recovery-grace [n] |
Show or set the post-recovery verbatim grace window, in user-turn-groups (0 disables) |
/pruner dedup [on|off|status] |
Toggle pre-flush content-hash dedup |
/pruner tree |
Foldable browser of pruned tool calls; Ctrl-O opens the full summary in an overlay |
/pruner compact |
Retroactively compress every eligible closed chain (bypasses rollingWindow) |
/pruner now |
Flush pending batches immediately with a multi-row progress widget above the input |
/pruner help |
Full help text |
Tools surfaced to the LLM
context_tree_query - always available when the extension is loaded. Pruned summaries end with short refs like Summarized tool refs: \t1`, `t2`. Use `context_tree_query` with these refs to retrieve the original full outputs.The model passes those refs (or fulltoolCallIds) and gets back the original tool result text from the session index. Each per-tool bullet in the summary also carries its own inline `` tN` `` ref, so recovering a specific tool is a single hop; the footer still lists every ref as a fallback. Content-hash-deduped duplicates resolve to the original's record automatically.
Short refs (tN) always resolve 1:1 and are the primary, recommended path; a reused toolCallId returns every matching occurrence instead of silently picking one, including occurrences deduplicated to an earlier record. See PRUNING.md § Occurrence Identity for multi-occurrence recovery mechanics.
Footer status widget
A footer widget shows the current state, controlled by showPruneStatusLine:
Every rendered state is prefixed with a single leading | divider so the segment stays visually isolated in the shared footer regardless of where other extensions' status segments land (load-order independent); there is no trailing divider - the footer's own space-join between segments already provides one.
| prune: OFF- disabled| prune: ON- enabled, no flushes yet| prune: ON . 92.0k->14.0k (-85%)- enabled; live reclaim ratio (estimated tokens before->after, percent reduction). Updates on everypruneMessagescall.| prune: 3 pending- batches queued, waiting for the trigger| prune: summarizing...- flush in progress| prune: ON . 92.0k->14.0k (-85%) . diag u1/o2- same as above, plus a self-hiding diagnostic segment (u/m/ocounters, each hidden at zero). See PRUNING.md § Diagnostics.| prune: ON . think 3.2k . gap 5.1k . chain 41%- a context-pressure suffix, shown only whenfrontierGapTokens > 0(nothing to append when the frontier is fully caught up). All three are chars/4 token estimates, same convention as the reclaim ratio above:think-openCycleThinkingTokens: thinking-block tokens trapped in the still-open cycle (after the last text-only assistant reply), not yet eligible for pruning.gap-frontierGapTokens: summarization-eligible tool-result tokens sitting past the persisted prune frontier.chain-largestChainSharePct: the largest single chain's (or the open segment's) share of total branch chars. See PRUNING.md § Single-chain sessions for exact definitions.
| prune: recovered pending (reload)- shown atagent_endwhen a session reload found recoverable unflushed work but no new turn re-queued it. See PRUNING.md § Reload rearm for what "recovered" means and when it actually flushes.
Setting showPruneStatusLine: false hides the widget and silences the queued-turn notice; pruning still runs.
The status line does not show cost. Full token/cost detail is available via /pruner stats. The extension also emits cumulative session cost on the cost:external pi.events channel for external aggregators - see README § External cost channel.
/pruner status context block
When context metrics are available, /pruner status appends a --- context --- block below the settings summary:
--- context ---
thinking: 3.2k tokens (open segment)
chain share: 41%
frontier gap: 5.1k tokens
rearmed: yes
thinking/chain share/frontier gapare the sameopenCycleThinkingTokens/largestChainSharePct/frontierGapTokensmetrics shown in the footer suffix above, formatted as full labels here instead of a compact suffix.rearmed: yesappears only while the reload-rearm flag is armed (cleared the moment any flush attempt runs). See PRUNING.md § Reload rearm.