@davecodes/pi-dcp
Dynamic Context Pruning for Pi — port of opencode-dcp. Deduplication, error-input purge, and an LLM-callable compress tool to keep long sessions cheap.
Package details
Install @davecodes/pi-dcp from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@davecodes/pi-dcp- Package
@davecodes/pi-dcp- Version
0.1.8- Published
- May 14, 2026
- Downloads
- not available
- Author
- davecodes
- License
- AGPL-3.0-or-later
- Types
- extension, skill
- Size
- 209.5 KB
- Dependencies
- 0 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
],
"skills": [
"./skills"
],
"image": "assets/preview.png"
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
Pi-Dynamic-Context-Pruning
Cut LLM token spend in long Pi sessions, automatically. Dedup redundant tool calls, strip errored payloads, and let the model summarize closed work-streams — all without ever modifying your session history.
A faithful port of @tarquinen/opencode-dcp tailored to pi's extension API. Zero npm dependencies at runtime.
License: AGPL-3.0-or-later. Tests: 55 passing on Node 22 and 24.
Contents
- Why
- How it works
- Install
- Quick start
- Slash commands
- Configuration reference
- Per-model context limits
- Recipes
- Troubleshooting
- Develop
- How it differs from opencode-dcp
- Project layout
- Credits and license
Why
A long agentic loop in Pi typically wastes tokens on:
- Repeated lookups — the same
greporreadre-issued five turns later - Failed payloads — a 4-KB
bashcommand that errored, sent back to the model on every subsequent turn - Closed work-streams — initial repo scans, abandoned approaches, resolved retry loops whose raw output is no longer useful
pi-dcp prunes all three before the request hits the model. The on-disk session is never touched — pruning is applied to the request payload only, so /tree, /compact, fork, and resume all keep the originals intact.
How it works
A context event fires on every outbound LLM request. pi-dcp hooks that event, rewrites the request payload in-place, and lets it continue to the provider. The on-disk session is never mutated.
Three independent mechanisms run on the outbound request:
| Mechanism | What it does | When |
|---|---|---|
| Deduplication | Same toolName + canonical(args) keeps the newest result and replaces older copies with a [pruned by pi-dcp: duplicate ... call] marker. |
Every LLM call (auto) |
| Errored input purge | Failed tool calls have their arguments stripped after N turns. Error message is preserved. | Every LLM call (auto) |
compress tool |
LLM-callable. Replaces a span of tool results with a lossless technical summary. Two modes: message (per-id list) or range (start and end span). |
When the model decides |
Plus three nudge surfaces that bias the model toward compressing:
- Soft or strong in-system-prompt nudge when usage crosses
minContextLimit - Hard nudge above
maxContextLimit - Iteration nudge after N non-user messages without a user reply
Install
Three ways. Pick one:
npm (recommended — versioned, easy to update):
pi install npm:@davecodes/pi-dcp
git (always tracks main):
pi install git:github.com/Davidcreador/pi-dcp
manual clone (for hacking on the code):
git clone git@github.com:Davidcreador/pi-dcp.git ~/.pi/agent/extensions/pi-dcp
All three paths produce the same runtime behavior. Pi auto-discovers the extension via its pi.extensions package.json entry.
User state always lives at ~/.pi-dcp/, not next to the code. That directory holds:
~/.pi-dcp/
config.json your settings (written on first run from defaults)
prompts/
defaults/ regenerated each launch (read-only reference)
overrides/ drop *.md files here to customize the LLM prompts
dcp.log debug log (when config.debug is true)
stats.json lifetime savings counters
To update:
pi update npm:@davecodes/pi-dcp
Quick start
After install, run pi normally. Verify the extension is live:
pi -p "do you have a tool called 'compress'? answer yes/no"
Expected output: yes.
Open a long session as usual. Check savings any time with:
/dcp context # this session
/dcp stats # lifetime, across sessions
To bias the model toward compressing more aggressively, edit ~/.pi-dcp/config.json:
{
"compress": {
"minContextLimit": "30%",
"maxContextLimit": "60%",
"nudgeForce": "strong"
}
}
minContextLimit is the soft-nudge floor. maxContextLimit is the hard-nudge ceiling. Restart pi after config changes.
Slash commands
| Command | What it does |
|---|---|
/dcp |
Show this command list |
/dcp context |
Current session: token usage, DCP savings, active compressions |
/dcp stats |
Lifetime savings across all pi sessions |
/dcp sweep [n] |
Stage a compression over the last n tool results (default: since last user message). Use to nuke unwanted output. |
/dcp manual on/off/toggle/status |
Runtime manual mode — stops the LLM from auto-compressing. Edit config.json to persist. |
/dcp decompress <id> |
Temporarily restore a stored compression's original tool outputs |
/dcp recompress <id> |
Re-apply a previously decompressed entry |
Slash commands work in interactive pi mode only — pi -p (print mode) does not dispatch them. The compress tool and auto strategies work in both modes.
Configuration reference
Defaults are written to ~/.pi-dcp/config.json on first run. Per-project overrides at <repo>/.pi/dcp.json shallow-merge on top. Restart pi after edits.
The shipped defaults are tuned for real-world long sessions — see config.example.json in this repo for the exact reference shape and inline comments.
{
"enabled": true,
"debug": false,
"pruneNotification": "minimal",
"experimental": {
"customPrompts": false
},
"manualMode": {
"enabled": false,
"automaticStrategies": true
},
"turnProtection": {
"enabled": true,
"turns": 3
},
"compress": {
"mode": "range",
"permission": "allow",
"minContextLimit": 30000,
"maxContextLimit": 70000,
"modelMinLimits": {
"anthropic/claude-opus-4-7": 35000
},
"modelMaxLimits": {
"anthropic/claude-opus-4-7": 85000
},
"nudgeEveryTurns": 5,
"nudgeFrequency": 3,
"iterationNudgeThreshold": 8,
"nudgeForce": "strong",
"protectedTools": []
},
"strategies": {
"deduplication": {
"enabled": true,
"protectedTools": []
},
"purgeErrors": {
"enabled": true,
"turns": 2,
"protectedTools": []
}
}
}
Field notes:
pruneNotification:off,minimal, ordetailed(reserved).experimental.customPrompts: whentrue, honorsprompts/overrides/*.md.manualMode.automaticStrategies: when manual mode is on, still run dedup and purge.turnProtection.turns: last N user-bounded turns are immune to pruning.compress.minContextLimit/maxContextLimit: number of tokens or a"X%"string of the model's context window.compress.nudgeEveryTurns: per-turn soft-nudge throttle.compress.nudgeFrequency: per-request soft-nudge throttle (stacks with the per-turn one).compress.iterationNudgeThreshold: 0 disables; fires after N messages since the last user message.compress.nudgeForce:softorstrongwording.strategies.purgeErrors.turns: turns after which errored args are purged.
Always protected (never pruned, regardless of config): compress, write, edit, todo, task, skill.
Per-model context limits
compress.modelMinLimits and modelMaxLimits accept keys shaped as provider/id, matching ctx.model.provider and ctx.model.id. Examples mirroring the shipped config.json:
| Model | Window | Soft floor | Hard ceiling | Strategy |
|---|---|---|---|---|
anthropic/claude-haiku-4-5 |
200k | 30k | 70k | tight — cheap fast tier |
anthropic/claude-sonnet-4-5 |
200k | 50k | 120k | workhorse band |
anthropic/claude-sonnet-4-6 |
200k | 50k | 120k | workhorse band |
anthropic/claude-opus-4-1 to 4-7 |
200k | 35k | 85k | aggressive — save expensive tokens |
openai/gpt-5.4-mini-fast |
— | 25k | 50k | tightest |
openai/gpt-5.4-mini |
— | 30k | 70k | tight |
openai/gpt-5.5 |
— | 45k | 100k | medium |
Values accept either a number (absolute token count) or a "X%" string (percentage of the model's context window).
Recipes
Save tokens aggressively on premium models
{
"compress": {
"modelMinLimits": { "anthropic/claude-opus-4-7": "10%" },
"modelMaxLimits": { "anthropic/claude-opus-4-7": "25%" },
"nudgeForce": "strong"
}
}
Do not auto-compress, let me drive
{
"manualMode": { "enabled": true, "automaticStrategies": true }
}
Auto-dedup and purge still run. You drive compression via /dcp sweep.
Project-specific overrides
Drop a .pi/dcp.json in the repo root:
{
"strategies": {
"purgeErrors": {
"turns": 1,
"protectedTools": ["lint"]
}
}
}
Customize the nudge wording
{
"experimental": { "customPrompts": true }
}
Then create ~/.pi-dcp/prompts/overrides/strong-nudge.md with your text. Restart pi.
Troubleshooting
The compress tool is not showing up:
- Confirm with
pi -p "list your tools" 2>&1 | grep compress. If missing, checkcompress.permissionis not"deny". - Restart pi after any config change. Extensions load once at startup.
Nothing is being pruned:
/dcp contextshows live stats. If always 0:turnProtection.turnsmay cover your whole session (recent turns are protected).strategies.*.enabledmay befalse.- You may be hitting protected tools —
writeandeditare never deduped.
See what is happening under the hood:
{ "debug": true }
Restart pi. Logs land at ~/.pi-dcp/dcp.log:
[2026-05-13T...] INFO pi-dcp initialized {"mode":"range",...}
[2026-05-13T...] INFO pipeline applied {"dedupPruned":2,"errorInputsPurged":1,"tokensSaved":3214}
Compress tool refuses with protected_window_overlap:
- The model picked tool-call IDs that live inside
turnProtection.turns. Either lowerturnProtection.turns, disable it, or tell the model to pick older calls.
Develop
cd ~/.pi/agent/extensions/pi-dcp
# Set up dev deps (peer + typescript)
npm install --no-save typescript @earendil-works/pi-coding-agent
# Typecheck + test
npm run check
npm run test
CI on GitHub Actions runs the same on Node 22 and 24 against every push and PR.
How it differs from opencode-dcp
| Feature | opencode-dcp | pi-dcp |
|---|---|---|
| Tokenizer | @anthropic-ai/tokenizer |
ctx.getContextUsage() (built-in) |
| Auto-update | npm latest check | git pull |
| Soft/hard nudges | per-request injection | before_agent_start system-prompt addendum (functionally equivalent) |
compress.mode (range, message) |
both | both |
turnProtection |
runtime skip | runtime skip plus upfront refusal of compress tool overlap |
modelMinLimits and modelMaxLimits |
yes | yes |
iterationNudgeThreshold |
yes | yes |
nudgeForce |
yes | yes |
compress.nudgeFrequency |
yes | yes, plus per-turn nudgeEveryTurns |
| Prompt overrides | yes | yes |
manualMode.automaticStrategies |
yes | yes |
| Skipped in pi-dcp | — | pruneNotificationType:"toast", compress.showCompression, compress.summaryBuffer, experimental.allowSubAgents, protectedFilePatterns |
Project layout
pi-dcp/
index.ts extension entry — wires hooks, tool, /dcp command
config.json runtime config (auto-generated; tracked)
lib/
config.ts loader, DEFAULT_CONFIG, percent + per-model resolution
logger.ts ~/.pi-dcp/dcp.log writer (gated by config.debug)
state.ts per-session in-memory state
stats.ts ~/.pi-dcp/stats.json lifetime counters (atomic write)
messages.ts AgentMessage helpers + canonical JSON + cloneForMutation
pipeline.ts orchestrates strategies + applies compressions
nudges.ts soft/strong/hard/iteration system-prompt addendums
strategies/
deduplication.ts drop redundant tool calls
purge-errors.ts strip errored tool inputs after N turns
tools/
compress-message.ts LLM tool — per-id mode
compress-range.ts LLM tool — span mode
shared.ts preflight, storeCompression, branchToolCallIds
prompts/
index.ts PromptStore + defaults + override loader
commands/ /dcp subcommand handlers
help.ts
context.ts
stats.ts
manual.ts
sweep.ts
decompress.ts decompress + recompress
test/ 55 unit tests, zero external deps
pipeline.test.ts dedup, purge, mutation safety, idempotency
misc.test.ts config percent parsing, nudge throttling, parseStrictId
features.test.ts range mode, prompt overrides, manual modes, nudgeFreq
parity.test.ts turnProtection, modelMin/Max, iterationNudge, nudgeForce
audit.test.ts edge cases (no-user, iter-refire, protected overlap)
skills/pi-dcp/SKILL.md documentation surface pi reads at session start
prompts/defaults/ regenerated on every init (read-only reference)
prompts/overrides/ you put files here when customPrompts is true
.github/workflows/ci.yml Node 22/24 matrix typecheck + test
README.md
Credits and license
Concept and prompt design ported from @tarquinen/opencode-dcp by tarquinen. Pi adaptation and tests by @Davidcreador.
License: AGPL-3.0-or-later — inherits from upstream. See LICENSE.