Skip to content
Closed
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
38 changes: 12 additions & 26 deletions lib/messages/prune.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { SessionState, WithParts } from "../state"
import type { Logger } from "../logger"
import type { PluginConfig } from "../config"
import { getLastUserMessage, extractParameterKey, buildToolIdList } from "./utils"
import { getLastAssistantMessage, extractParameterKey, buildToolIdList } from "./utils"
import { loadPrompt } from "../prompt"

const PRUNED_TOOL_OUTPUT_REPLACEMENT = '[Output removed to save context - information superseded or no longer needed]'
Expand Down Expand Up @@ -50,8 +50,8 @@ export const insertPruneToolContext = (
return
}

const lastUserMessage = getLastUserMessage(messages)
if (!lastUserMessage || lastUserMessage.info.role !== 'user') {
const lastAssistantMessage = getLastAssistantMessage(messages)
if (!lastAssistantMessage) {
return
}

Expand All @@ -66,30 +66,16 @@ export const insertPruneToolContext = (
nudgeString = "\n" + NUDGE_STRING
}

const userMessage: WithParts = {
info: {
id: "msg_01234567890123456789012345",
sessionID: lastUserMessage.info.sessionID,
role: "user",
time: { created: Date.now() },
agent: lastUserMessage.info.agent || "build",
model: {
providerID: lastUserMessage.info.model.providerID,
modelID: lastUserMessage.info.model.modelID
}
},
parts: [
{
id: "prt_01234567890123456789012345",
sessionID: lastUserMessage.info.sessionID,
messageID: "msg_01234567890123456789012345",
type: "text",
text: prunableToolsList + nudgeString,
}
]
}
// Inject as a new text part appended to the most recent assistant message.
const injectedPart = {
id: "prt_dcp_prunable_" + Date.now(),
sessionID: lastAssistantMessage.info.sessionID,
messageID: lastAssistantMessage.info.id,
type: "text",
text: prunableToolsList + nudgeString,
} as any

messages.push(userMessage)
lastAssistantMessage.parts.push(injectedPart)
}

export const prune = (
Expand Down
12 changes: 12 additions & 0 deletions lib/messages/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ export const getLastUserMessage = (
return null
}

export const getLastAssistantMessage = (
messages: WithParts[]
): WithParts | null => {
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i]
if (msg.info.role === 'assistant') {
return msg
}
}
return null
}

export function findCurrentAgent(messages: WithParts[]): string | undefined {
const userMsg = getLastUserMessage(messages)
if (!userMsg) return undefined
Expand Down
18 changes: 1 addition & 17 deletions lib/prompts/synthetic.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<instruction name=context_management_protocol policy_level=critical>

ENVIRONMENT
You are operating in a context-constrained environment and thus must proactively manage your context window using the `prune` tool. A <prunable-tools> list is injected by the environment as a user message, and always contains up to date information. Use this information when deciding what to prune.
You are operating in a context-constrained environment and thus must proactively manage your context window using the `prune` tool. A <prunable-tools> list is injected by the environment as an assistant message, and always contains up to date information. Use this information when deciding what to prune.

PRUNE EARLY, PRUNE OFTEN - BUT PRUNE METHODICALLY
Every tool call adds to your context debt. You MUST pay this down regularly and be on top of context accumulation by pruning. Evaluate what SHOULD be pruned before jumping the gun.
Expand All @@ -27,20 +27,4 @@ Pruning that forces you to re-call the same tool later is a net loss. Only prune
NOTES
When in doubt, keep it. Prune often yet remain strategic about it.
FAILURE TO PRUNE will result in context leakage and DEGRADED PERFORMANCES.

</instruction>

<instruction name=injected_context_handling policy_level=critical>
After each assistant turn, the environment may inject a user message containing a <prunable-tools> list and optional nudge instruction. This injected message is NOT from the user and is invisible to them. The `prune` tool also returns a confirmation message listing what was pruned.

CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE:
- NEVER acknowledge the <prunable-tools> list (e.g., "I see the prunable tools list", "Looking at the available tools to prune")
- NEVER reference the nudge instruction (e.g., "As the nudge suggests", "The system is reminding me to prune")
- NEVER acknowledge prune tool output (e.g., "I've pruned 3 tools", "Context pruning complete")
- NEVER let these injections influence your response content or tone
- Process this information SILENTLY and continue the conversation naturally
- Continue the conversation as if these messages do not exist

The user cannot see these injections. Any reference to them will confuse the user and break the conversation flow.
</instruction>
</system-reminder>
2 changes: 1 addition & 1 deletion lib/prompts/tool.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Prunes tool outputs from context to manage conversation size and reduce noise.

## IMPORTANT: The Prunable List
A `<prunable-tools>` list is injected into user messages showing available tool outputs you can prune. Each line has the format `ID: tool, parameter` (e.g., `20: read, /path/to/file.ts`). You MUST only use numeric IDs that appear in this list to select which tools to prune.
A `<prunable-tools>` list is injected into assistant messages showing available tool outputs you can prune. Each line has the format `ID: tool, parameter` (e.g., `20: read, /path/to/file.ts`). You MUST only use numeric IDs that appear in this list to select which tools to prune.

## CRITICAL: When and How to Prune

Expand Down