Skip to content
Merged
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
3 changes: 2 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 6 additions & 5 deletions lib/fetch-wrapper/formats/bedrock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,22 @@ 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') {
if (isNudgeMessage(msg, nudgeText)) continue

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
}
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 5 additions & 4 deletions lib/fetch-wrapper/formats/gemini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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 {
Expand Down
11 changes: 6 additions & 5 deletions lib/fetch-wrapper/formats/openai-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,22 @@ 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') {
if (isNudgeMessage(msg, nudgeText)) continue

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
}
Expand All @@ -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 {
Expand Down
11 changes: 6 additions & 5 deletions lib/fetch-wrapper/formats/openai-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,22 @@ 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') {
if (isNudgeItem(item, nudgeText)) continue

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
}
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion lib/fetch-wrapper/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
3 changes: 2 additions & 1 deletion lib/fetch-wrapper/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,6 +29,7 @@ export interface FormatDescriptor {
export interface SynthPrompts {
synthInstruction: string
nudgeInstruction: string
systemReminder: string
}

export interface FetchHandlerContext {
Expand Down
6 changes: 1 addition & 5 deletions lib/prompts/nudge.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
<system-reminder>
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.
</system-reminder>

<instruction name=agent_nudge>
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.
</instruction>
3 changes: 3 additions & 0 deletions lib/prompts/system-reminder.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<system-reminder>
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 <prunable-tools> list) in your response or thinking/reasoning blocks, as this will confuse the user who cannot see them.
</system-reminder>