diff --git a/lib/fetch-wrapper/formats/anthropic.ts b/lib/fetch-wrapper/formats/anthropic.ts
index c1a99d8..a409e3e 100644
--- a/lib/fetch-wrapper/formats/anthropic.ts
+++ b/lib/fetch-wrapper/formats/anthropic.ts
@@ -33,34 +33,10 @@ export const anthropicFormat: FormatDescriptor = {
return true
},
- appendToLastAssistantMessage(body: any, injection: string): boolean {
- if (!injection || !body.messages || body.messages.length === 0) return false
-
- // Find the last assistant message
- for (let i = body.messages.length - 1; i >= 0; i--) {
- const msg = body.messages[i]
- if (msg.role === 'assistant') {
- // Append to existing content array
- if (Array.isArray(msg.content)) {
- const firstToolUseIndex = msg.content.findIndex((block: any) => block.type === 'tool_use')
- if (firstToolUseIndex !== -1) {
- msg.content.splice(firstToolUseIndex, 0, { type: 'text', text: injection })
- } else {
- msg.content.push({ type: 'text', text: injection })
- }
- } else if (typeof msg.content === 'string') {
- // Convert string content to array format
- msg.content = [
- { type: 'text', text: msg.content },
- { type: 'text', text: injection }
- ]
- } else {
- msg.content = [{ type: 'text', text: injection }]
- }
- return true
- }
- }
- return false
+ appendUserMessage(body: any, injection: string): boolean {
+ if (!injection || !body.messages) return false
+ body.messages.push({ role: 'user', content: [{ type: 'text', text: injection }] })
+ return true
},
extractToolOutputs(data: any[], state: PluginState): ToolOutput[] {
diff --git a/lib/fetch-wrapper/formats/bedrock.ts b/lib/fetch-wrapper/formats/bedrock.ts
index 6a62a38..4f4f7ce 100644
--- a/lib/fetch-wrapper/formats/bedrock.ts
+++ b/lib/fetch-wrapper/formats/bedrock.ts
@@ -32,21 +32,10 @@ export const bedrockFormat: FormatDescriptor = {
return true
},
- appendToLastAssistantMessage(body: any, injection: string): boolean {
- if (!injection || !body.messages || body.messages.length === 0) return false
-
- for (let i = body.messages.length - 1; i >= 0; i--) {
- const msg = body.messages[i]
- if (msg.role === 'assistant') {
- if (Array.isArray(msg.content)) {
- msg.content.push({ text: injection })
- } else {
- msg.content = [{ text: injection }]
- }
- return true
- }
- }
- return false
+ appendUserMessage(body: any, injection: string): boolean {
+ if (!injection || !body.messages) return false
+ body.messages.push({ role: 'user', content: [{ text: injection }] })
+ return true
},
extractToolOutputs(data: any[], state: PluginState): ToolOutput[] {
diff --git a/lib/fetch-wrapper/formats/gemini.ts b/lib/fetch-wrapper/formats/gemini.ts
index a01eed8..46ec2ad 100644
--- a/lib/fetch-wrapper/formats/gemini.ts
+++ b/lib/fetch-wrapper/formats/gemini.ts
@@ -31,21 +31,10 @@ export const geminiFormat: FormatDescriptor = {
return true
},
- appendToLastAssistantMessage(body: any, injection: string): boolean {
- if (!injection || !body.contents || body.contents.length === 0) return false
-
- for (let i = body.contents.length - 1; i >= 0; i--) {
- const content = body.contents[i]
- if (content.role === 'model') {
- if (Array.isArray(content.parts)) {
- content.parts.push({ text: injection })
- } else {
- content.parts = [{ text: injection }]
- }
- return true
- }
- }
- return false
+ appendUserMessage(body: any, injection: string): boolean {
+ if (!injection || !body.contents) return false
+ body.contents.push({ role: 'user', parts: [{ text: injection }] })
+ return true
},
extractToolOutputs(data: any[], state: PluginState): ToolOutput[] {
diff --git a/lib/fetch-wrapper/formats/openai-chat.ts b/lib/fetch-wrapper/formats/openai-chat.ts
index 0ea6be6..ca41dbf 100644
--- a/lib/fetch-wrapper/formats/openai-chat.ts
+++ b/lib/fetch-wrapper/formats/openai-chat.ts
@@ -27,23 +27,10 @@ export const openaiChatFormat: FormatDescriptor = {
return true
},
- appendToLastAssistantMessage(body: any, injection: string): boolean {
- if (!injection || !body.messages || body.messages.length === 0) return false
-
- for (let i = body.messages.length - 1; i >= 0; i--) {
- const msg = body.messages[i]
- if (msg.role === 'assistant') {
- if (typeof msg.content === 'string') {
- msg.content = msg.content + '\n\n' + injection
- } else if (Array.isArray(msg.content)) {
- msg.content.push({ type: 'text', text: injection })
- } else {
- msg.content = injection
- }
- return true
- }
- }
- return false
+ appendUserMessage(body: any, injection: string): boolean {
+ if (!injection || !body.messages) return false
+ body.messages.push({ role: 'user', content: injection })
+ return true
},
extractToolOutputs(data: any[], state: PluginState): ToolOutput[] {
diff --git a/lib/fetch-wrapper/formats/openai-responses.ts b/lib/fetch-wrapper/formats/openai-responses.ts
index cd7681a..2cabafe 100644
--- a/lib/fetch-wrapper/formats/openai-responses.ts
+++ b/lib/fetch-wrapper/formats/openai-responses.ts
@@ -23,23 +23,10 @@ export const openaiResponsesFormat: FormatDescriptor = {
return true
},
- appendToLastAssistantMessage(body: any, injection: string): boolean {
- if (!injection || !body.input || body.input.length === 0) return false
-
- for (let i = body.input.length - 1; i >= 0; i--) {
- const item = body.input[i]
- if (item.type === 'message' && item.role === 'assistant') {
- if (typeof item.content === 'string') {
- item.content = item.content + '\n\n' + injection
- } else if (Array.isArray(item.content)) {
- item.content.push({ type: 'output_text', text: injection })
- } else {
- item.content = injection
- }
- return true
- }
- }
- return false
+ appendUserMessage(body: any, injection: string): boolean {
+ if (!injection || !body.input) return false
+ body.input.push({ type: 'message', role: 'user', content: injection })
+ return true
},
extractToolOutputs(data: any[], state: PluginState): ToolOutput[] {
diff --git a/lib/fetch-wrapper/handler.ts b/lib/fetch-wrapper/handler.ts
index 10824b1..a4cd693 100644
--- a/lib/fetch-wrapper/handler.ts
+++ b/lib/fetch-wrapper/handler.ts
@@ -1,7 +1,7 @@
import type { FetchHandlerContext, FetchHandlerResult, FormatDescriptor, PrunedIdData } from "./types"
import { type PluginState, ensureSessionRestored } from "../state"
import type { Logger } from "../logger"
-import { buildPrunableToolsList, buildAssistantInjection } from "./prunable-list"
+import { buildPrunableToolsList, buildEndInjection } from "./prunable-list"
import { syncToolCache } from "../state/tool-cache"
import { loadPrompt } from "../core/prompt"
@@ -96,11 +96,11 @@ export async function handleFormat(
modified = true
}
- const assistantInjection = buildAssistantInjection(prunableList, includeNudge)
+ const endInjection = buildEndInjection(prunableList, includeNudge)
- if (format.appendToLastAssistantMessage && format.appendToLastAssistantMessage(body, assistantInjection)) {
+ if (format.appendUserMessage && format.appendUserMessage(body, endInjection)) {
const nudgeMsg = includeNudge ? " with nudge" : ""
- ctx.logger.debug("fetch", `Appended prunable tools list${nudgeMsg} to last assistant message (${format.name})`, {
+ ctx.logger.debug("fetch", `Appended prunable tools list${nudgeMsg} as user message (${format.name})`, {
ids: numericIds,
nudge: includeNudge,
toolsSincePrune: ctx.toolTracker.toolResultCount
diff --git a/lib/fetch-wrapper/prunable-list.ts b/lib/fetch-wrapper/prunable-list.ts
index dcdca71..7354b9c 100644
--- a/lib/fetch-wrapper/prunable-list.ts
+++ b/lib/fetch-wrapper/prunable-list.ts
@@ -42,7 +42,7 @@ export function buildPrunableToolsList(
}
}
-export function buildAssistantInjection(
+export function buildEndInjection(
prunableList: string,
includeNudge: boolean
): string {
diff --git a/lib/fetch-wrapper/types.ts b/lib/fetch-wrapper/types.ts
index fc49ef1..d15b640 100644
--- a/lib/fetch-wrapper/types.ts
+++ b/lib/fetch-wrapper/types.ts
@@ -19,7 +19,7 @@ export interface FormatDescriptor {
detect(body: any): boolean
getDataArray(body: any): any[] | undefined
injectSystemMessage(body: any, injection: string): boolean
- appendToLastAssistantMessage?(body: any, injection: string): boolean
+ appendUserMessage?(body: any, injection: string): boolean
extractToolOutputs(data: any[], state: PluginState): ToolOutput[]
replaceToolOutput(data: any[], toolId: string, prunedMessage: string, state: PluginState): boolean
hasToolOutputs(data: any[]): boolean
diff --git a/lib/prompts/synthetic.txt b/lib/prompts/synthetic.txt
index bd20aa0..ccb3174 100644
--- a/lib/prompts/synthetic.txt
+++ b/lib/prompts/synthetic.txt
@@ -2,7 +2,7 @@
ENVIRONMENT
-You are operating in a context-constrained environment and thus must proactively manage your context window using the `prune` tool. A list is injected by the environment as assistant message, 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 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.
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.
@@ -22,5 +22,19 @@ NOTES
When in doubt, prune out. Prune often yet remain strategic about it.
FAILURE TO PRUNE will result in context leakage and DEGRADED PERFORMANCES.
+
+
+
+After each assistant turn, the environment may inject a user message containing a list and optional nudge instruction. This injected message is NOT from the user and is invisible to them.
+
+CRITICAL REQUIREMENTS - VIOLATION IS UNACCEPTABLE:
+- NEVER acknowledge the 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 mention this injection mechanism to the user
+- NEVER let these injections influence your response content or tone
+- Process this information SILENTLY - use it only to inform your pruning decisions
+- 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.
diff --git a/lib/prompts/tool.txt b/lib/prompts/tool.txt
index 8903f2c..d727a11 100644
--- a/lib/prompts/tool.txt
+++ b/lib/prompts/tool.txt
@@ -1,7 +1,7 @@
Prunes tool outputs from context to manage conversation size and reduce noise.
## IMPORTANT: The Prunable List
-A `` 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`). Use these numeric IDs to select which tools to prune.
+A `` 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`). Use these numeric IDs to select which tools to prune.
## CRITICAL: When and How to Prune