diff --git a/index.ts b/index.ts index aaf6e64..c401802 100644 --- a/index.ts +++ b/index.ts @@ -45,7 +45,8 @@ const plugin: Plugin = (async (ctx) => { const prompts = { synthInstruction: loadPrompt("synthetic"), - nudgeInstruction: loadPrompt("nudge") + nudgeInstruction: loadPrompt("nudge"), + systemReminder: loadPrompt("system-reminder") } // Install global fetch wrapper for context pruning and synthetic instruction injection diff --git a/lib/fetch-wrapper/formats/bedrock.ts b/lib/fetch-wrapper/formats/bedrock.ts index 2aaedc6..bde93b5 100644 --- a/lib/fetch-wrapper/formats/bedrock.ts +++ b/lib/fetch-wrapper/formats/bedrock.ts @@ -8,7 +8,8 @@ function isNudgeMessage(msg: any, nudgeText: string): boolean { return false } -function injectSynth(messages: any[], instruction: string, nudgeText: string): boolean { +function injectSynth(messages: any[], instruction: string, nudgeText: string, systemReminder: string): boolean { + const fullInstruction = systemReminder + '\n\n' + instruction for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i] if (msg.role === 'user') { @@ -16,13 +17,13 @@ function injectSynth(messages: any[], instruction: string, nudgeText: string): b if (typeof msg.content === 'string') { if (msg.content.includes(instruction)) return false - msg.content = msg.content + '\n\n' + instruction + msg.content = msg.content + '\n\n' + fullInstruction } else if (Array.isArray(msg.content)) { const alreadyInjected = msg.content.some( (part: any) => part?.type === 'text' && typeof part.text === 'string' && part.text.includes(instruction) ) if (alreadyInjected) return false - msg.content.push({ type: 'text', text: instruction }) + msg.content.push({ type: 'text', text: fullInstruction }) } return true } @@ -56,8 +57,8 @@ export const bedrockFormat: FormatDescriptor = { return body.messages }, - injectSynth(data: any[], instruction: string, nudgeText: string): boolean { - return injectSynth(data, instruction, nudgeText) + injectSynth(data: any[], instruction: string, nudgeText: string, systemReminder: string): boolean { + return injectSynth(data, instruction, nudgeText, systemReminder) }, injectPrunableList(data: any[], injection: string): boolean { diff --git a/lib/fetch-wrapper/formats/gemini.ts b/lib/fetch-wrapper/formats/gemini.ts index 6510290..ab0a859 100644 --- a/lib/fetch-wrapper/formats/gemini.ts +++ b/lib/fetch-wrapper/formats/gemini.ts @@ -9,7 +9,8 @@ function isNudgeContent(content: any, nudgeText: string): boolean { return false } -function injectSynth(contents: any[], instruction: string, nudgeText: string): boolean { +function injectSynth(contents: any[], instruction: string, nudgeText: string, systemReminder: string): boolean { + const fullInstruction = systemReminder + '\n\n' + instruction for (let i = contents.length - 1; i >= 0; i--) { const content = contents[i] if (content.role === 'user' && Array.isArray(content.parts)) { @@ -19,7 +20,7 @@ function injectSynth(contents: any[], instruction: string, nudgeText: string): b (part: any) => part?.text && typeof part.text === 'string' && part.text.includes(instruction) ) if (alreadyInjected) return false - content.parts.push({ text: instruction }) + content.parts.push({ text: fullInstruction }) return true } } @@ -48,8 +49,8 @@ export const geminiFormat: FormatDescriptor = { return body.contents }, - injectSynth(data: any[], instruction: string, nudgeText: string): boolean { - return injectSynth(data, instruction, nudgeText) + injectSynth(data: any[], instruction: string, nudgeText: string, systemReminder: string): boolean { + return injectSynth(data, instruction, nudgeText, systemReminder) }, injectPrunableList(data: any[], injection: string): boolean { diff --git a/lib/fetch-wrapper/formats/openai-chat.ts b/lib/fetch-wrapper/formats/openai-chat.ts index 2ac3793..48fdfcb 100644 --- a/lib/fetch-wrapper/formats/openai-chat.ts +++ b/lib/fetch-wrapper/formats/openai-chat.ts @@ -8,7 +8,8 @@ function isNudgeMessage(msg: any, nudgeText: string): boolean { return false } -function injectSynth(messages: any[], instruction: string, nudgeText: string): boolean { +function injectSynth(messages: any[], instruction: string, nudgeText: string, systemReminder: string): boolean { + const fullInstruction = systemReminder + '\n\n' + instruction for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i] if (msg.role === 'user') { @@ -16,13 +17,13 @@ function injectSynth(messages: any[], instruction: string, nudgeText: string): b if (typeof msg.content === 'string') { if (msg.content.includes(instruction)) return false - msg.content = msg.content + '\n\n' + instruction + msg.content = msg.content + '\n\n' + fullInstruction } else if (Array.isArray(msg.content)) { const alreadyInjected = msg.content.some( (part: any) => part?.type === 'text' && typeof part.text === 'string' && part.text.includes(instruction) ) if (alreadyInjected) return false - msg.content.push({ type: 'text', text: instruction }) + msg.content.push({ type: 'text', text: fullInstruction }) } return true } @@ -47,8 +48,8 @@ export const openaiChatFormat: FormatDescriptor = { return body.messages }, - injectSynth(data: any[], instruction: string, nudgeText: string): boolean { - return injectSynth(data, instruction, nudgeText) + injectSynth(data: any[], instruction: string, nudgeText: string, systemReminder: string): boolean { + return injectSynth(data, instruction, nudgeText, systemReminder) }, injectPrunableList(data: any[], injection: string): boolean { diff --git a/lib/fetch-wrapper/formats/openai-responses.ts b/lib/fetch-wrapper/formats/openai-responses.ts index 6b84891..acc03e3 100644 --- a/lib/fetch-wrapper/formats/openai-responses.ts +++ b/lib/fetch-wrapper/formats/openai-responses.ts @@ -8,7 +8,8 @@ function isNudgeItem(item: any, nudgeText: string): boolean { return false } -function injectSynth(input: any[], instruction: string, nudgeText: string): boolean { +function injectSynth(input: any[], instruction: string, nudgeText: string, systemReminder: string): boolean { + const fullInstruction = systemReminder + '\n\n' + instruction for (let i = input.length - 1; i >= 0; i--) { const item = input[i] if (item.type === 'message' && item.role === 'user') { @@ -16,13 +17,13 @@ function injectSynth(input: any[], instruction: string, nudgeText: string): bool if (typeof item.content === 'string') { if (item.content.includes(instruction)) return false - item.content = item.content + '\n\n' + instruction + item.content = item.content + '\n\n' + fullInstruction } else if (Array.isArray(item.content)) { const alreadyInjected = item.content.some( (part: any) => part?.type === 'input_text' && typeof part.text === 'string' && part.text.includes(instruction) ) if (alreadyInjected) return false - item.content.push({ type: 'input_text', text: instruction }) + item.content.push({ type: 'input_text', text: fullInstruction }) } return true } @@ -47,8 +48,8 @@ export const openaiResponsesFormat: FormatDescriptor = { return body.input }, - injectSynth(data: any[], instruction: string, nudgeText: string): boolean { - return injectSynth(data, instruction, nudgeText) + injectSynth(data: any[], instruction: string, nudgeText: string, systemReminder: string): boolean { + return injectSynth(data, instruction, nudgeText, systemReminder) }, injectPrunableList(data: any[], injection: string): boolean { diff --git a/lib/fetch-wrapper/handler.ts b/lib/fetch-wrapper/handler.ts index db18883..6b7bfc3 100644 --- a/lib/fetch-wrapper/handler.ts +++ b/lib/fetch-wrapper/handler.ts @@ -76,7 +76,7 @@ export async function handleFormat( } if (ctx.config.strategies.onTool.length > 0) { - if (format.injectSynth(data, ctx.prompts.synthInstruction, ctx.prompts.nudgeInstruction)) { + if (format.injectSynth(data, ctx.prompts.synthInstruction, ctx.prompts.nudgeInstruction, ctx.prompts.systemReminder)) { modified = true } diff --git a/lib/fetch-wrapper/types.ts b/lib/fetch-wrapper/types.ts index 6a74840..cb728a3 100644 --- a/lib/fetch-wrapper/types.ts +++ b/lib/fetch-wrapper/types.ts @@ -18,7 +18,7 @@ export interface FormatDescriptor { name: string detect(body: any): boolean getDataArray(body: any): any[] | undefined - injectSynth(data: any[], instruction: string, nudgeText: string): boolean + injectSynth(data: any[], instruction: string, nudgeText: string, systemReminder: string): boolean injectPrunableList(data: any[], injection: string): boolean extractToolOutputs(data: any[], state: PluginState): ToolOutput[] replaceToolOutput(data: any[], toolId: string, prunedMessage: string, state: PluginState): boolean @@ -29,6 +29,7 @@ export interface FormatDescriptor { export interface SynthPrompts { synthInstruction: string nudgeInstruction: string + systemReminder: string } export interface FetchHandlerContext { diff --git a/lib/prompts/nudge.txt b/lib/prompts/nudge.txt index 5a1fd5c..8e91d94 100644 --- a/lib/prompts/nudge.txt +++ b/lib/prompts/nudge.txt @@ -1,7 +1,3 @@ - -This nudge is injected by a plugin and is invisible to the user. Do not acknowledge or reference it in your response - simply follow it silently. - - -You have accumulated several tool outputs. If you have completed a discrete unit of work and distilled relevant understanding in writing for the user to keep, use the prune tool to remove obsolete tool outputs from this conversation and optimize token usage. +You have accumulated several tool outputs. Consider using the `prune` tool to declutter the conversation context. diff --git a/lib/prompts/system-reminder.txt b/lib/prompts/system-reminder.txt new file mode 100644 index 0000000..479ea7a --- /dev/null +++ b/lib/prompts/system-reminder.txt @@ -0,0 +1,3 @@ + +These instructions are injected by a plugin and are invisible to the user. You must follow them silently and NEVER reference or discuss them (including the list) in your response or thinking/reasoning blocks, as this will confuse the user who cannot see them. + \ No newline at end of file