Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ DCP uses multiple strategies to reduce context size:

**Supersede Writes** — Prunes write tool inputs for files that have subsequently been read. When a file is written and later read, the original write content becomes redundant since the current file state is captured in the read result. Runs automatically on every request with zero LLM cost.

**Prune Tool** — Exposes a `prune` tool that the AI can call to manually trigger pruning when it determines context cleanup is needed.
**Discard Tool** — Exposes a `discard` tool that the AI can call to remove completed or noisy tool outputs from context. Use this for task completion cleanup and removing irrelevant outputs.

**Extract Tool** — Exposes an `extract` tool that the AI can call to distill valuable context into concise summaries before removing the raw outputs. Use this when you need to preserve key findings while reducing context size.

**On Idle Analysis** — Uses a language model to semantically analyze conversation context during idle periods and identify tool outputs that are no longer relevant.

Expand Down Expand Up @@ -72,8 +74,8 @@ DCP uses its own config file:
"supersedeWrites": {
"enabled": true
},
// Exposes a prune tool to your LLM to call when it determines pruning is necessary
"pruneTool": {
// Removes tool content from context without preservation (for completed tasks or noise)
"discardTool": {
"enabled": true,
// Additional tools to protect from pruning
"protectedTools": [],
Expand All @@ -82,12 +84,30 @@ DCP uses its own config file:
"enabled": false,
"turns": 4
},
// Nudge the LLM to use the prune tool (every <frequency> tool results)
// Nudge the LLM to use the discard tool (every <frequency> tool results)
"nudge": {
"enabled": true,
"frequency": 10
}
},
// Distills key findings into preserved knowledge before removing raw content
"extractTool": {
"enabled": true,
// Additional tools to protect from pruning
"protectedTools": [],
// Protect from pruning for <turn protection> message turns
"turnProtection": {
"enabled": false,
"turns": 4
},
// Nudge the LLM to use the extract tool (every <frequency> tool results)
"nudge": {
"enabled": true,
"frequency": 10
},
// Show distillation content as an ignored message notification
"showDistillation": false
},
// (Legacy) Run an LLM to analyze what tool calls are no longer relevant on idle
"onIdle": {
"enabled": false,
Expand All @@ -109,7 +129,7 @@ DCP uses its own config file:
### Protected Tools

By default, these tools are always protected from pruning across all strategies:
`task`, `todowrite`, `todoread`, `prune`, `batch`
`task`, `todowrite`, `todoread`, `discard`, `extract`, `batch`

The `protectedTools` arrays in each strategy add to this default list.

Expand Down
59 changes: 43 additions & 16 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getConfig } from "./lib/config"
import { Logger } from "./lib/logger"
import { loadPrompt } from "./lib/prompt"
import { createSessionState } from "./lib/state"
import { createPruneTool } from "./lib/strategies"
import { createDiscardTool, createExtractTool } from "./lib/strategies"
import { createChatMessageTransformHandler, createEventHandler } from "./lib/hooks"

const plugin: Plugin = (async (ctx) => {
Expand All @@ -18,18 +18,30 @@ const plugin: Plugin = (async (ctx) => {
(globalThis as any).AI_SDK_LOG_WARNINGS = false
}

// Initialize core components
const logger = new Logger(config.debug)
const state = createSessionState()

// Log initialization
logger.info("DCP initialized", {
strategies: config.strategies,
})

return {
"experimental.chat.system.transform": async (_input: unknown, output: { system: string[] }) => {
const syntheticPrompt = loadPrompt("prune-system-prompt")
const discardEnabled = config.strategies.discardTool.enabled
const extractEnabled = config.strategies.extractTool.enabled

let promptName: string
if (discardEnabled && extractEnabled) {
promptName = "system/system-prompt-both"
} else if (discardEnabled) {
promptName = "system/system-prompt-discard"
} else if (extractEnabled) {
promptName = "system/system-prompt-extract"
} else {
return
}

const syntheticPrompt = loadPrompt(promptName)
output.system.push(syntheticPrompt)
},
"experimental.chat.messages.transform": createChatMessageTransformHandler(
Expand All @@ -38,25 +50,40 @@ const plugin: Plugin = (async (ctx) => {
logger,
config
),
tool: config.strategies.pruneTool.enabled ? {
prune: createPruneTool({
client: ctx.client,
state,
logger,
config,
workingDirectory: ctx.directory
tool: {
...(config.strategies.discardTool.enabled && {
discard: createDiscardTool({
client: ctx.client,
state,
logger,
config,
workingDirectory: ctx.directory
}),
}),
} : undefined,
...(config.strategies.extractTool.enabled && {
extract: createExtractTool({
client: ctx.client,
state,
logger,
config,
workingDirectory: ctx.directory
}),
}),
},
config: async (opencodeConfig) => {
// Add prune to primary_tools by mutating the opencode config
// Add enabled tools to primary_tools by mutating the opencode config
// This works because config is cached and passed by reference
if (config.strategies.pruneTool.enabled) {
const toolsToAdd: string[] = []
if (config.strategies.discardTool.enabled) toolsToAdd.push("discard")
if (config.strategies.extractTool.enabled) toolsToAdd.push("extract")

if (toolsToAdd.length > 0) {
const existingPrimaryTools = opencodeConfig.experimental?.primary_tools ?? []
opencodeConfig.experimental = {
...opencodeConfig.experimental,
primary_tools: [...existingPrimaryTools, "prune"],
primary_tools: [...existingPrimaryTools, ...toolsToAdd],
}
logger.info("Added 'prune' to experimental.primary_tools via config mutation")
logger.info(`Added ${toolsToAdd.map(t => `'${t}'`).join(" and ")} to experimental.primary_tools via config mutation`)
}
},
event: createEventHandler(ctx.client, config, state, logger, ctx.directory),
Expand Down
Loading