Skip to content

Commit 424249c

Browse files
committed
replace context header with token-triggered compress nudge
1 parent 47b1703 commit 424249c

File tree

7 files changed

+47
-47
lines changed

7 files changed

+47
-47
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ DCP uses its own config file:
104104
> // Nudge the LLM to use prune tools (every <nudgeFrequency> tool results)
105105
> "nudgeEnabled": true,
106106
> "nudgeFrequency": 10,
107-
> // Encourages the model to stay within this context budget (not a hard limit); set to "model" to use full model capacity
107+
> // When session tokens exceed this limit, the model is encouraged to compress context. Set to "model" to use full model context limit.
108108
> "contextLimit": 100000,
109109
> // Additional tools to protect from pruning
110110
> "protectedTools": [],

dcp.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
"description": "Tool names that should be protected from automatic pruning"
111111
},
112112
"contextLimit": {
113-
"description": "Context limit used for the prunable-tools header (\"model\" uses the active model's context limit)",
113+
"description": "When session tokens exceed this limit, a compress nudge is injected (\"model\" uses the active model's context limit)",
114114
"default": 100000,
115115
"oneOf": [
116116
{

lib/messages/inject.ts

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,22 @@ import type { SessionState, WithParts } from "../state"
22
import type { Logger } from "../logger"
33
import type { PluginConfig } from "../config"
44
import type { UserMessage } from "@opencode-ai/sdk/v2"
5-
import { renderNudge } from "../prompts"
5+
import { renderNudge, renderCompressNudge } from "../prompts"
66
import {
77
extractParameterKey,
88
buildToolIdList,
99
createSyntheticTextPart,
1010
createSyntheticToolPart,
1111
isIgnoredUserMessage,
12-
formatContextHeader,
13-
type ContextInfo,
1412
} from "./utils"
1513
import { getFilePathsFromParameters, isProtected } from "../protected-file-patterns"
1614
import { getLastUserMessage, isMessageCompacted } from "../shared-utils"
1715
import { getCurrentTokenUsage } from "../strategies/utils"
1816

19-
export const wrapPrunableTools = (content: string, contextInfo?: ContextInfo): string => {
20-
const contextHeader = formatContextHeader(contextInfo)
17+
// XML wrappers
18+
export const wrapPrunableTools = (content: string): string => {
2119
return `<prunable-tools>
22-
${contextHeader}The following tools have been invoked and are available for pruning. This list does not mandate immediate action. Consider your current goals and the resources you need before pruning valuable tool inputs or outputs. Consolidate your prunes for efficiency; it is rarely worth pruning a single tiny tool output. Keep the context free of noise.
20+
The following tools have been invoked and are available for pruning. This list does not mandate immediate action. Consider your current goals and the resources you need before pruning valuable tool inputs or outputs. Consolidate your prunes for efficiency; it is rarely worth pruning a single tiny tool output. Keep the context free of noise.
2321
${content}
2422
</prunable-tools>`
2523
}
@@ -55,6 +53,32 @@ Context management was just performed. Do NOT use the ${toolName} again. A fresh
5553
</context-info>`
5654
}
5755

56+
const resolveContextLimit = (config: PluginConfig, state: SessionState): number | undefined => {
57+
const configLimit = config.tools.settings.contextLimit
58+
if (configLimit === "model") {
59+
return state.modelContextLimit
60+
}
61+
return configLimit
62+
}
63+
64+
const shouldInjectCompressNudge = (
65+
config: PluginConfig,
66+
state: SessionState,
67+
messages: WithParts[],
68+
): boolean => {
69+
if (config.tools.compress.permission === "deny") {
70+
return false
71+
}
72+
73+
const contextLimit = resolveContextLimit(config, state)
74+
if (contextLimit === undefined) {
75+
return false
76+
}
77+
78+
const currentTokens = getCurrentTokenUsage(messages)
79+
return currentTokens > contextLimit
80+
}
81+
5882
const getNudgeString = (config: PluginConfig): string => {
5983
const flags = {
6084
prune: config.tools.prune.permission !== "deny",
@@ -135,20 +159,7 @@ const buildPrunableToolsList = (
135159
return ""
136160
}
137161

138-
const configLimit =
139-
config.tools.settings.contextLimit === "model"
140-
? state.modelContextLimit
141-
: config.tools.settings.contextLimit
142-
143-
const contextInfo: ContextInfo = {
144-
used: getCurrentTokenUsage(messages),
145-
limit:
146-
state.modelContextLimit !== undefined && configLimit !== undefined
147-
? Math.min(state.modelContextLimit, configLimit)
148-
: (configLimit ?? state.modelContextLimit),
149-
}
150-
151-
return wrapPrunableTools(lines.join("\n"), contextInfo)
162+
return wrapPrunableTools(lines.join("\n"))
152163
}
153164

154165
export const insertPruneToolContext = (
@@ -194,6 +205,12 @@ export const insertPruneToolContext = (
194205
logger.info("Inserting prune nudge message")
195206
contentParts.push(getNudgeString(config))
196207
}
208+
209+
// Add compress nudge if token usage exceeds contextLimit
210+
if (shouldInjectCompressNudge(config, state, messages)) {
211+
logger.info("Inserting compress nudge - token usage exceeds contextLimit")
212+
contentParts.push(renderCompressNudge())
213+
}
197214
}
198215

199216
if (contentParts.length === 0) {

lib/messages/utils.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,9 @@ import { isMessageCompacted } from "../shared-utils"
33
import { Logger } from "../logger"
44
import type { SessionState, WithParts } from "../state"
55
import type { UserMessage } from "@opencode-ai/sdk/v2"
6-
import { formatTokenCount } from "../ui/utils"
76

87
export const COMPRESS_SUMMARY_PREFIX = "[Compressed conversation block]\n\n"
98

10-
export interface ContextInfo {
11-
used: number
12-
limit: number | undefined
13-
}
14-
15-
export function formatContextHeader(contextInfo?: ContextInfo): string {
16-
if (!contextInfo || contextInfo.used === 0) {
17-
return ""
18-
}
19-
20-
const usedStr = formatTokenCount(contextInfo.used)
21-
22-
if (contextInfo.limit) {
23-
const limitStr = formatTokenCount(contextInfo.limit)
24-
const percentage = Math.round((contextInfo.used / contextInfo.limit) * 100)
25-
return `Context: ~${usedStr} / ${limitStr} (${percentage}% used)\n`
26-
}
27-
28-
return `Context: ~${usedStr}\n`
29-
}
30-
319
const generateUniqueId = (prefix: string): string => `${prefix}_${ulid()}`
3210

3311
const isGeminiModel = (modelID: string): boolean => {

lib/prompts/compress-nudge.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<context-limit-warning>
2+
Your session context has exceeded the configured limit. Use the `compress` tool to consolidate completed work phases into summaries. Target conversation segments where exploration or implementation is finished - compress preserves understanding while freeing space for continued work.
3+
</context-limit-warning>

lib/prompts/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Generated prompts (from .md files via scripts/generate-prompts.ts)
22
import { SYSTEM as SYSTEM_PROMPT } from "./_codegen/system.generated"
33
import { NUDGE } from "./_codegen/nudge.generated"
4+
import { COMPRESS_NUDGE } from "./_codegen/compress-nudge.generated"
45
import { PRUNE as PRUNE_TOOL_SPEC } from "./_codegen/prune.generated"
56
import { DISTILL as DISTILL_TOOL_SPEC } from "./_codegen/distill.generated"
67
import { COMPRESS as COMPRESS_TOOL_SPEC } from "./_codegen/compress.generated"
@@ -33,6 +34,10 @@ export function renderNudge(flags: ToolFlags): string {
3334
return processConditionals(NUDGE, flags)
3435
}
3536

37+
export function renderCompressNudge(): string {
38+
return COMPRESS_NUDGE
39+
}
40+
3641
const PROMPTS: Record<string, string> = {
3742
"prune-tool-spec": PRUNE_TOOL_SPEC,
3843
"distill-tool-spec": DISTILL_TOOL_SPEC,

lib/prompts/system.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ AVAILABLE TOOLS FOR CONTEXT MANAGEMENT
1414
<compress>THE COMPRESS TOOL
1515
`compress` is a sledgehammer and should be used accordingly. It's purpose is to reduce whole part of the conversation to its essence and technical details in order to leave room for newer context. Your summary MUST be technical and specific enough to preserve FULL understanding of WHAT TRANSPIRED, such that NO AMBIGUITY remains about what was done, found, or decided. Your compress summary must be thorough and precise. `compress` will replace everything in the range you match, user and assistant messages, tool inputs and outputs. It is preferred to not compress preemptively, but rather wait for natural breakpoints in the conversation. Those breakpoints are to be infered from user messages. You WILL NOT compress based on thinking that you are done with the task, wait for conversation queues that the user has moved on from current phase.
1616

17-
When the context usage indicator is high (around 80% or above), prioritize using `compress` to reduce verbosity and keep room for future context.
18-
1917
This tool will typically be used at the end of a phase of work, when conversation starts to accumulate noise that would better served summarized, or when you've done significant exploration and can FULLY synthesize your findings and understanding into a technical summary.
2018

2119
Make sure to match enough of the context with start and end strings so you're not faced with an error calling the tool. Be VERY CAREFUL AND CONSERVATIVE when using `compress`.
@@ -39,7 +37,6 @@ Be respectful of the user's API usage, manage context methodically as you work t
3937

4038
<instruction name=injected_context_handling policy_level=critical>
4139
This chat environment injects context information on your behalf in the form of a <prunable-tools> list to help you manage context effectively. Carefully read the list and use it to inform your management decisions. The list is automatically updated after each turn to reflect the current state of manageable tools and context usage. If no list is present, do NOT attempt to prune anything.
42-
Aim to keep the context usage indicator below roughly 80% so there is room for future turns and tool outputs.
4340
There may be tools in session context that do not appear in the <prunable-tools> list, this is expected, remember that you can ONLY prune what you see in list.
4441
</instruction>
4542
</system-reminder>

0 commit comments

Comments
 (0)