@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.

Packages

Package details

extensionskill

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

A long agentic loop in Pi typically wastes tokens on:

  • Repeated lookups — the same grep or read re-issued five turns later
  • Failed payloads — a 4-KB bash command 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, or detailed (reserved).
  • experimental.customPrompts: when true, honors prompts/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: soft or strong wording.
  • 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, check compress.permission is not "deny".
  • Restart pi after any config change. Extensions load once at startup.

Nothing is being pruned:

  • /dcp context shows live stats. If always 0:
    • turnProtection.turns may cover your whole session (recent turns are protected).
    • strategies.*.enabled may be false.
    • You may be hitting protected tools — write and edit are 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 lower turnProtection.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.