Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
43d6e7a
refactor: compress to rule them all
spoons-and-mirrors Feb 9, 2026
7e3fbed
tweaks
spoons-and-mirrors Feb 9, 2026
00ae4b4
3.0 release image
spoons-and-mirrors Feb 10, 2026
55f4f5f
prompt rewrite
spoons-and-mirrors Feb 10, 2026
8c28716
feat: annotate tool outputs with token counts
spoons-and-mirrors Feb 11, 2026
553ca89
pull prompts from dev for ref
spoons-and-mirrors Feb 11, 2026
6bdbd95
remove uid refs
spoons-and-mirrors Feb 11, 2026
8d21d58
refine tool format
spoons-and-mirrors Feb 11, 2026
15e4f17
prompts
spoons-and-mirrors Feb 11, 2026
a65d5db
rm dev prompts
spoons-and-mirrors Feb 11, 2026
9cffd3c
nudge
spoons-and-mirrors Feb 11, 2026
90318ce
changin token count wording
spoons-and-mirrors Feb 11, 2026
4c6b024
prompts: preserving user intent
spoons-and-mirrors Feb 11, 2026
1132af4
nudgeGap
spoons-and-mirrors Feb 12, 2026
da56861
feat: cLog
spoons-and-mirrors Feb 11, 2026
e391a8f
summary searching sucks
Tarquinen Feb 12, 2026
7741734
nudge
spoons-and-mirrors Feb 12, 2026
7a48c26
prompts
spoons-and-mirrors Feb 12, 2026
9b40772
cleanup context-limit hint injection architecture
Tarquinen Feb 12, 2026
ffc340d
token counts targets
spoons-and-mirrors Feb 12, 2026
701088b
range chronology badgering
spoons-and-mirrors Feb 12, 2026
13d622a
tool inputs badger
spoons-and-mirrors Feb 12, 2026
48d4729
refactor context-limit anchor lifecycle
Tarquinen Feb 12, 2026
eea6067
rename
Tarquinen Feb 12, 2026
3b19bb8
cleanup
Tarquinen Feb 12, 2026
e2b2797
cleanup
Tarquinen Feb 12, 2026
4025efe
cleanup
Tarquinen Feb 12, 2026
7608931
bargraph: v1
spoons-and-mirrors Feb 12, 2026
63bd217
bargraph v2
spoons-and-mirrors Feb 12, 2026
5c8e9b7
different shading for "new stuff since last compress use"
spoons-and-mirrors Feb 12, 2026
07eb6b5
cleanup
Tarquinen Feb 12, 2026
aa60c0d
remath proportional bargraph display
spoons-and-mirrors Feb 12, 2026
12c763a
cleanup
Tarquinen Feb 12, 2026
fca89fb
Merge branch 'refactor/one-tool-to-rule-them-all' into cleanup/contex…
Tarquinen Feb 12, 2026
83907c2
cleanup
Tarquinen Feb 12, 2026
38dc7f4
Merge pull request #386 from Opencode-DCP/cleanup/context-limit-hints
Tarquinen Feb 12, 2026
ed7e66f
use real nudge prompt
Tarquinen Feb 12, 2026
96e5b2e
add system prompt to bargraph session representation and use full bra…
spoons-and-mirrors Feb 12, 2026
96062b0
differentiate system prompt char
spoons-and-mirrors Feb 12, 2026
d4d637f
refactor: derive compress graph from token buckets
Tarquinen Feb 13, 2026
94f97bd
remove: unused contextPressure and compressContext config options
Tarquinen Feb 13, 2026
6c9c795
remove: unused nudgeEnabled and nudgeFrequency config options
Tarquinen Feb 13, 2026
bffa213
refactor: rename limitNudgeInterval to nudgeGap
Tarquinen Feb 13, 2026
e18f764
cleanup: remove dead prune/distill refs from prompts
Tarquinen Feb 13, 2026
acd72b9
cleanup: remove prune/distill remnants
spoons-and-mirrors Feb 13, 2026
7de0666
cleanup: simplify prompts to compress-only
Tarquinen Feb 13, 2026
8896aaa
cleanup: remove ToolFlags, simplify renderSystemPrompt
Tarquinen Feb 13, 2026
2399d1f
fix: fallback token accounting when api tokens missing
spoons-and-mirrors Feb 13, 2026
eaf25eb
cleanup: remove prompt loading indirection
Tarquinen Feb 13, 2026
43497b1
cleanup: rename applyAnchoredHints to applyAnchoredNudge
Tarquinen Feb 13, 2026
3684485
cleanup: remove unused nudgeCounter
Tarquinen Feb 13, 2026
e2cb885
cleanup: remove unused lastToolPrune
Tarquinen Feb 13, 2026
6ab7f24
feat(ui): render positional compress graph
Tarquinen Feb 13, 2026
65b016c
fix(compress): reject ambiguous summary matches
Tarquinen Feb 13, 2026
8af64d1
feat(ui): restore strict bargraph v2 compress map
Tarquinen Feb 13, 2026
0bdce2e
cleanup(ui): remove unused compression graph helpers
Tarquinen Feb 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .repomixignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ dist/
repomix-output.xml
bun.lock
package-lock.jsonc
LICENCE
LICENSE
scripts/
tests/
README.md
38 changes: 11 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ Restart OpenCode. The plugin will automatically start optimizing your sessions.

## How Pruning Works

DCP uses multiple tools and strategies to reduce context size:
DCP uses one user-facing tool and strategies to reduce context size:

### Tools
For model-facing behavior (prompts and tool calls), this capability is always addressed as `compress`.

**Distill** — Exposes a `distill` tool that the AI can call to distill valuable context into concise summaries before removing the tool content.
### Tool

**Compress** — Exposes a `compress` tool that the AI can call to collapse a large section of conversation (messages and tools) into a single summary.
**Compress** — Exposes a single `compress` tool with one method: match a conversation range using `startString` and `endString`, then replace it with a technical summary.

**Prune** — Exposes a `prune` tool that the AI can call to remove completed or noisy tool content from context.
The model can use that same method at different scales: tiny ranges for noise cleanup, focused ranges for preserving key findings, and full chapters for completed work.

### Strategies

Expand Down Expand Up @@ -105,13 +105,10 @@ DCP uses its own config file:
> // Protect file operations from pruning via glob patterns
> // Patterns match tool parameters.filePath (e.g. read/write/edit)
> "protectedFilePatterns": [],
> // LLM-driven context pruning tools
> // LLM-driven context management tool
> "tools": {
> // Shared settings for all prune tools
> // Shared settings for context management
> "settings": {
> // Nudge the LLM to use prune tools (every <nudgeFrequency> tool results)
> "nudgeEnabled": true,
> "nudgeFrequency": 10,
> // Token limit at which the model compresses session context
> // to keep the model in the "smart zone" (not a hard limit)
> // Accepts: number or "X%" (percentage of model's context window)
Expand All @@ -126,25 +123,13 @@ DCP uses its own config file:
> // Additional tools to protect from pruning
> "protectedTools": [],
> },
> // Distills key findings into preserved knowledge before removing raw content
> "distill": {
> // Unified context compression tool
> "compress": {
> // Permission mode: "allow" (no prompt), "ask" (prompt), "deny" (tool not registered)
> "permission": "allow",
> // Show distillation content as an ignored message notification
> "showDistillation": false,
> },
> // Collapses a range of conversation content into a single summary
> "compress": {
> // Permission mode: "deny" (tool not registered), "ask" (prompt), "allow" (no prompt)
> "permission": "deny",
> // Show summary content as an ignored message notification
> "showCompression": false,
> },
> // Removes tool content from context without preservation (for completed tasks or noise)
> "prune": {
> // Permission mode: "allow" (no prompt), "ask" (prompt), "deny" (tool not registered)
> "permission": "allow",
> },
> },
> // Automatic pruning strategies
> "strategies": {
Expand Down Expand Up @@ -181,14 +166,13 @@ DCP provides a `/dcp` slash command:
- `/dcp stats` — Shows cumulative pruning statistics across all sessions.
- `/dcp sweep` — Prunes all tools since the last user message. Accepts an optional count: `/dcp sweep 10` prunes the last 10 tools. Respects `commands.protectedTools`.
- `/dcp manual [on|off]` — Toggle manual mode or set explicit state. When on, the AI will not autonomously use context management tools.
- `/dcp prune [focus]` — Trigger a single prune tool execution. Optional focus text directs the AI's pruning decisions.
- `/dcp distill [focus]` — Trigger a single distill tool execution. Optional focus text directs what to distill.
- `/dcp compress [focus]` — Trigger a single compress tool execution. Optional focus text directs what range to compress.
### Protected Tools
By default, these tools are always protected from pruning:
`task`, `todowrite`, `todoread`, `distill`, `compress`, `prune`, `batch`, `plan_enter`, `plan_exit`
`task`, `todowrite`, `todoread`, `compress`, `batch`, `plan_enter`, `plan_exit`
The `protectedTools` arrays in each section add to this default list.
Expand Down
Binary file added assets/images/3.0 release.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 10 additions & 45 deletions dcp.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@
"type": "string",
"enum": ["off", "minimal", "detailed"],
"default": "detailed",
"description": "Level of notification shown when pruning occurs"
"description": "Level of notification shown when context management occurs"
},
"pruneNotificationType": {
"type": "string",
"enum": ["chat", "toast"],
"default": "chat",
"description": "Where to display prune notifications (chat message or toast notification)"
"description": "Where to display notifications (chat message or toast notification)"
},
"commands": {
"type": "object",
Expand Down Expand Up @@ -104,23 +104,19 @@
},
"tools": {
"type": "object",
"description": "Configuration for pruning tools",
"description": "Configuration for context-management tools",
"additionalProperties": false,
"properties": {
"settings": {
"type": "object",
"description": "General tool settings",
"additionalProperties": false,
"properties": {
"nudgeEnabled": {
"type": "boolean",
"default": true,
"description": "Enable nudge reminders to prune context"
},
"nudgeFrequency": {
"nudgeGap": {
"type": "number",
"default": 10,
"description": "Frequency of nudge reminders (in turns)"
"default": 1,
"minimum": 1,
"description": "How often the context-limit nudge fires when above contextLimit (1 = every fetch, 5 = every 5th fetch)"
},
"protectedTools": {
"type": "array",
Expand Down Expand Up @@ -160,52 +156,21 @@
}
}
},
"distill": {
"type": "object",
"description": "Configuration for the distill tool",
"additionalProperties": false,
"properties": {
"permission": {
"type": "string",
"enum": ["ask", "allow", "deny"],
"default": "allow",
"description": "Permission mode (deny disables the tool)"
},
"showDistillation": {
"type": "boolean",
"default": false,
"description": "Show distillation output in the UI"
}
}
},
"compress": {
"type": "object",
"description": "Configuration for the compress tool",
"description": "Configuration for the unified compress tool",
"additionalProperties": false,
"properties": {
"permission": {
"type": "string",
"enum": ["ask", "allow", "deny"],
"default": "ask",
"default": "allow",
"description": "Permission mode (deny disables the tool)"
},
"showCompression": {
"type": "boolean",
"default": false,
"description": "Show summary output in the UI"
}
}
},
"prune": {
"type": "object",
"description": "Configuration for the prune tool",
"additionalProperties": false,
"properties": {
"permission": {
"type": "string",
"enum": ["ask", "allow", "deny"],
"default": "allow",
"description": "Permission mode (deny disables the tool)"
"description": "Show compression summaries in notifications"
}
}
}
Expand Down
24 changes: 1 addition & 23 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Plugin } from "@opencode-ai/plugin"
import { getConfig } from "./lib/config"
import { Logger } from "./lib/logger"
import { createSessionState } from "./lib/state"
import { createPruneTool, createDistillTool, createCompressTool } from "./lib/strategies"
import { createCompressTool } from "./lib/tools"
import {
createChatMessageTransformHandler,
createCommandExecuteHandler,
Expand Down Expand Up @@ -61,15 +61,6 @@ const plugin: Plugin = (async (ctx) => {
ctx.directory,
),
tool: {
...(config.tools.distill.permission !== "deny" && {
distill: createDistillTool({
client: ctx.client,
state,
logger,
config,
workingDirectory: ctx.directory,
}),
}),
...(config.tools.compress.permission !== "deny" && {
compress: createCompressTool({
client: ctx.client,
Expand All @@ -79,15 +70,6 @@ const plugin: Plugin = (async (ctx) => {
workingDirectory: ctx.directory,
}),
}),
...(config.tools.prune.permission !== "deny" && {
prune: createPruneTool({
client: ctx.client,
state,
logger,
config,
workingDirectory: ctx.directory,
}),
}),
},
config: async (opencodeConfig) => {
if (config.commands.enabled) {
Expand All @@ -99,9 +81,7 @@ const plugin: Plugin = (async (ctx) => {
}

const toolsToAdd: string[] = []
if (config.tools.distill.permission !== "deny") toolsToAdd.push("distill")
if (config.tools.compress.permission !== "deny") toolsToAdd.push("compress")
if (config.tools.prune.permission !== "deny") toolsToAdd.push("prune")

if (toolsToAdd.length > 0) {
const existingPrimaryTools = opencodeConfig.experimental?.primary_tools ?? []
Expand All @@ -118,9 +98,7 @@ const plugin: Plugin = (async (ctx) => {
const permission = opencodeConfig.permission ?? {}
opencodeConfig.permission = {
...permission,
distill: config.tools.distill.permission,
compress: config.tools.compress.permission,
prune: config.tools.prune.permission,
} as typeof permission
},
}
Expand Down
10 changes: 4 additions & 6 deletions lib/commands/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,16 @@ const BASE_COMMANDS: [string, string][] = [
]

const TOOL_COMMANDS: Record<string, [string, string]> = {
prune: ["/dcp prune [focus]", "Trigger manual prune tool execution"],
distill: ["/dcp distill [focus]", "Trigger manual distill tool execution"],
compress: ["/dcp compress [focus]", "Trigger manual compress tool execution"],
}

function getVisibleCommands(config: PluginConfig): [string, string][] {
const commands = [...BASE_COMMANDS]
for (const tool of ["prune", "distill", "compress"] as const) {
if (config.tools[tool].permission !== "deny") {
commands.push(TOOL_COMMANDS[tool])
}

if (config.tools.compress.permission !== "deny") {
commands.push(TOOL_COMMANDS.compress)
}

return commands
}

Expand Down
61 changes: 5 additions & 56 deletions lib/commands/manual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
*
* Usage:
* /dcp manual [on|off] - Toggle manual mode or set explicit state
* /dcp prune [focus] - Trigger manual prune execution
* /dcp distill [focus] - Trigger manual distill execution
* /dcp compress [focus] - Trigger manual compress execution
*/

Expand All @@ -14,33 +12,11 @@ import type { SessionState, WithParts } from "../state"
import type { PluginConfig } from "../config"
import { sendIgnoredMessage } from "../ui/notification"
import { getCurrentParams } from "../strategies/utils"
import { syncToolCache } from "../state/tool-cache"
import { buildToolIdList } from "../messages/utils"
import { buildPrunableToolsList } from "../messages/inject"

const MANUAL_MODE_ON =
"Manual mode is now ON. Use /dcp prune, /dcp distill, or /dcp compress to trigger context tools manually."
const MANUAL_MODE_ON = "Manual mode is now ON. Use /dcp compress to trigger context tools manually."

const MANUAL_MODE_OFF = "Manual mode is now OFF."

const NO_PRUNABLE_TOOLS = "No prunable tool outputs are currently available for manual triggering."

const PRUNE_TRIGGER_PROMPT = [
"<prune triggered manually>",
"Manual mode trigger received. You must now use the prune tool exactly once.",
"Find the most significant set of prunable tool outputs to remove safely.",
"Follow prune policy and avoid pruning outputs that may be needed later.",
"Return after prune with a brief explanation of what you pruned and why.",
].join("\n\n")

const DISTILL_TRIGGER_PROMPT = [
"<distill triggered manually>",
"Manual mode trigger received. You must now use the distill tool.",
"Select the most information-dense prunable outputs and distill them into complete technical substitutes.",
"Be exhaustive and preserve all critical technical details.",
"Return after distill with a brief explanation of what was distilled and why.",
].join("\n\n")

const COMPRESS_TRIGGER_PROMPT = [
"<compress triggered manually>",
"Manual mode trigger received. You must now use the compress tool.",
Expand All @@ -49,25 +25,13 @@ const COMPRESS_TRIGGER_PROMPT = [
"Return after compress with a brief explanation of what range was compressed.",
].join("\n\n")

function getTriggerPrompt(
tool: "prune" | "distill" | "compress",
context?: string,
userFocus?: string,
): string {
const base =
tool === "prune"
? PRUNE_TRIGGER_PROMPT
: tool === "distill"
? DISTILL_TRIGGER_PROMPT
: COMPRESS_TRIGGER_PROMPT
function getTriggerPrompt(tool: "compress", userFocus?: string): string {
const base = COMPRESS_TRIGGER_PROMPT

const sections = [base]
if (userFocus && userFocus.trim().length > 0) {
sections.push(`Additional user focus:\n${userFocus.trim()}`)
}
if (context) {
sections.push(context)
}

return sections.join("\n\n")
}
Expand Down Expand Up @@ -109,23 +73,8 @@ export async function handleManualToggleCommand(

export async function handleManualTriggerCommand(
ctx: ManualCommandContext,
tool: "prune" | "distill" | "compress",
tool: "compress",
userFocus?: string,
): Promise<string | null> {
const { client, state, config, logger, sessionId, messages } = ctx

if (tool === "prune" || tool === "distill") {
syncToolCache(state, config, logger, messages)
buildToolIdList(state, messages, logger)
const prunableToolsList = buildPrunableToolsList(state, config, logger)
if (!prunableToolsList) {
const params = getCurrentParams(state, messages, logger)
await sendIgnoredMessage(client, sessionId, NO_PRUNABLE_TOOLS, params, logger)
return null
}

return getTriggerPrompt(tool, prunableToolsList, userFocus)
}

return getTriggerPrompt("compress", undefined, userFocus)
return getTriggerPrompt(tool, userFocus)
}
2 changes: 1 addition & 1 deletion lib/commands/sweep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export async function handleSweepCommand(ctx: SweepCommandContext): Promise<void
const protectedTools = config.commands.protectedTools

syncToolCache(state, config, logger, messages)
buildToolIdList(state, messages, logger)
buildToolIdList(state, messages)

// Parse optional numeric argument
const numArg = args[0] ? parseInt(args[0], 10) : null
Expand Down
Loading