Skip to content

Commit 55926d9

Browse files
authored
Merge pull request #178 from Opencode-DCP/feat/split-prune-into-discard-and-extract
Split prune tool into discard and extract
2 parents 60dffe3 + de76de1 commit 55926d9

19 files changed

+812
-355
lines changed

README.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ DCP uses multiple strategies to reduce context size:
2929

3030
**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.
3131

32-
**Prune Tool** — Exposes a `prune` tool that the AI can call to manually trigger pruning when it determines context cleanup is needed.
32+
**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.
33+
34+
**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.
3335

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

@@ -72,8 +74,8 @@ DCP uses its own config file:
7274
"supersedeWrites": {
7375
"enabled": true
7476
},
75-
// Exposes a prune tool to your LLM to call when it determines pruning is necessary
76-
"pruneTool": {
77+
// Removes tool content from context without preservation (for completed tasks or noise)
78+
"discardTool": {
7779
"enabled": true,
7880
// Additional tools to protect from pruning
7981
"protectedTools": [],
@@ -82,12 +84,30 @@ DCP uses its own config file:
8284
"enabled": false,
8385
"turns": 4
8486
},
85-
// Nudge the LLM to use the prune tool (every <frequency> tool results)
87+
// Nudge the LLM to use the discard tool (every <frequency> tool results)
8688
"nudge": {
8789
"enabled": true,
8890
"frequency": 10
8991
}
9092
},
93+
// Distills key findings into preserved knowledge before removing raw content
94+
"extractTool": {
95+
"enabled": true,
96+
// Additional tools to protect from pruning
97+
"protectedTools": [],
98+
// Protect from pruning for <turn protection> message turns
99+
"turnProtection": {
100+
"enabled": false,
101+
"turns": 4
102+
},
103+
// Nudge the LLM to use the extract tool (every <frequency> tool results)
104+
"nudge": {
105+
"enabled": true,
106+
"frequency": 10
107+
},
108+
// Show distillation content as an ignored message notification
109+
"showDistillation": false
110+
},
91111
// (Legacy) Run an LLM to analyze what tool calls are no longer relevant on idle
92112
"onIdle": {
93113
"enabled": false,
@@ -109,7 +129,7 @@ DCP uses its own config file:
109129
### Protected Tools
110130

111131
By default, these tools are always protected from pruning across all strategies:
112-
`task`, `todowrite`, `todoread`, `prune`, `batch`
132+
`task`, `todowrite`, `todoread`, `discard`, `extract`, `batch`
113133

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

index.ts

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { getConfig } from "./lib/config"
33
import { Logger } from "./lib/logger"
44
import { loadPrompt } from "./lib/prompt"
55
import { createSessionState } from "./lib/state"
6-
import { createPruneTool } from "./lib/strategies"
6+
import { createDiscardTool, createExtractTool } from "./lib/strategies"
77
import { createChatMessageTransformHandler, createEventHandler } from "./lib/hooks"
88

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

21-
// Initialize core components
2221
const logger = new Logger(config.debug)
2322
const state = createSessionState()
2423

25-
// Log initialization
2624
logger.info("DCP initialized", {
2725
strategies: config.strategies,
2826
})
2927

3028
return {
3129
"experimental.chat.system.transform": async (_input: unknown, output: { system: string[] }) => {
32-
const syntheticPrompt = loadPrompt("prune-system-prompt")
30+
const discardEnabled = config.strategies.discardTool.enabled
31+
const extractEnabled = config.strategies.extractTool.enabled
32+
33+
let promptName: string
34+
if (discardEnabled && extractEnabled) {
35+
promptName = "system/system-prompt-both"
36+
} else if (discardEnabled) {
37+
promptName = "system/system-prompt-discard"
38+
} else if (extractEnabled) {
39+
promptName = "system/system-prompt-extract"
40+
} else {
41+
return
42+
}
43+
44+
const syntheticPrompt = loadPrompt(promptName)
3345
output.system.push(syntheticPrompt)
3446
},
3547
"experimental.chat.messages.transform": createChatMessageTransformHandler(
@@ -38,25 +50,40 @@ const plugin: Plugin = (async (ctx) => {
3850
logger,
3951
config
4052
),
41-
tool: config.strategies.pruneTool.enabled ? {
42-
prune: createPruneTool({
43-
client: ctx.client,
44-
state,
45-
logger,
46-
config,
47-
workingDirectory: ctx.directory
53+
tool: {
54+
...(config.strategies.discardTool.enabled && {
55+
discard: createDiscardTool({
56+
client: ctx.client,
57+
state,
58+
logger,
59+
config,
60+
workingDirectory: ctx.directory
61+
}),
4862
}),
49-
} : undefined,
63+
...(config.strategies.extractTool.enabled && {
64+
extract: createExtractTool({
65+
client: ctx.client,
66+
state,
67+
logger,
68+
config,
69+
workingDirectory: ctx.directory
70+
}),
71+
}),
72+
},
5073
config: async (opencodeConfig) => {
51-
// Add prune to primary_tools by mutating the opencode config
74+
// Add enabled tools to primary_tools by mutating the opencode config
5275
// This works because config is cached and passed by reference
53-
if (config.strategies.pruneTool.enabled) {
76+
const toolsToAdd: string[] = []
77+
if (config.strategies.discardTool.enabled) toolsToAdd.push("discard")
78+
if (config.strategies.extractTool.enabled) toolsToAdd.push("extract")
79+
80+
if (toolsToAdd.length > 0) {
5481
const existingPrimaryTools = opencodeConfig.experimental?.primary_tools ?? []
5582
opencodeConfig.experimental = {
5683
...opencodeConfig.experimental,
57-
primary_tools: [...existingPrimaryTools, "prune"],
84+
primary_tools: [...existingPrimaryTools, ...toolsToAdd],
5885
}
59-
logger.info("Added 'prune' to experimental.primary_tools via config mutation")
86+
logger.info(`Added ${toolsToAdd.map(t => `'${t}'`).join(" and ")} to experimental.primary_tools via config mutation`)
6087
}
6188
},
6289
event: createEventHandler(ctx.client, config, state, logger, ctx.directory),

0 commit comments

Comments
 (0)