Skip to content

Commit c3e7576

Browse files
committed
Context pruner: remove all but last instructions prompt message
1 parent f4a6aa3 commit c3e7576

File tree

2 files changed

+98
-2
lines changed

2 files changed

+98
-2
lines changed

.agents/__tests__/context-pruner.test.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1634,7 +1634,7 @@ describe('context-pruner PASS 0 instructions removal', () => {
16341634
return results
16351635
}
16361636

1637-
test('removes messages with INSTRUCTIONS_PROMPT tag', () => {
1637+
test('removes last INSTRUCTIONS_PROMPT message in PASS 0', () => {
16381638
const messages: Message[] = [
16391639
{
16401640
role: 'user',
@@ -1721,6 +1721,90 @@ describe('context-pruner PASS 0 instructions removal', () => {
17211721
expect(texts).toContain('Start')
17221722
expect(texts).toContain('End')
17231723
})
1724+
1725+
test('keeps only last INSTRUCTIONS_PROMPT when pruning passes run (over token limit)', () => {
1726+
// Use a lower maxContextLength to trigger pruning without needing massive content
1727+
// This ensures PASS 0.5 runs (past the initial check)
1728+
const mockAgentState = {
1729+
messageHistory: [] as Message[],
1730+
}
1731+
1732+
const runHandleStepsWithLimit = (messages: Message[], maxContextLength: number) => {
1733+
mockAgentState.messageHistory = messages
1734+
const mockLogger = {
1735+
debug: () => {},
1736+
info: () => {},
1737+
warn: () => {},
1738+
error: () => {},
1739+
}
1740+
const generator = contextPruner.handleSteps!({
1741+
agentState: mockAgentState,
1742+
logger: mockLogger,
1743+
params: { maxContextLength },
1744+
})
1745+
const results: any[] = []
1746+
let result = generator.next()
1747+
while (!result.done) {
1748+
if (typeof result.value === 'object') {
1749+
results.push(result.value)
1750+
}
1751+
result = generator.next()
1752+
}
1753+
return results
1754+
}
1755+
1756+
// Content that's ~1000 tokens each (3000 chars / 3)
1757+
const mediumContent = 'x'.repeat(3000)
1758+
1759+
const messages: Message[] = [
1760+
{
1761+
role: 'user',
1762+
content: [{ type: 'text', text: mediumContent }],
1763+
},
1764+
{
1765+
role: 'user',
1766+
content: [{ type: 'text', text: 'Old instructions 1' }],
1767+
tags: ['INSTRUCTIONS_PROMPT'],
1768+
},
1769+
{
1770+
role: 'user',
1771+
content: [{ type: 'text', text: mediumContent }],
1772+
},
1773+
{
1774+
role: 'user',
1775+
content: [{ type: 'text', text: 'Old instructions 2' }],
1776+
tags: ['INSTRUCTIONS_PROMPT'],
1777+
},
1778+
{
1779+
role: 'user',
1780+
content: [{ type: 'text', text: mediumContent }],
1781+
},
1782+
{
1783+
role: 'user',
1784+
content: [{ type: 'text', text: 'Latest instructions' }],
1785+
tags: ['INSTRUCTIONS_PROMPT'],
1786+
},
1787+
]
1788+
1789+
// Use a very low limit (1000 tokens) to ensure we exceed it and trigger PASS 0.5
1790+
// Messages are ~3000+ tokens total, so 1000 limit will be exceeded
1791+
const results = runHandleStepsWithLimit(messages, 1000)
1792+
const resultMessages = results[0].input.messages
1793+
1794+
// PASS 0 removes the last INSTRUCTIONS_PROMPT ('Latest instructions')
1795+
// PASS 0.5 keeps only the last remaining one ('Old instructions 2') and removes 'Old instructions 1'
1796+
// So we should have at most 1 INSTRUCTIONS_PROMPT message remaining
1797+
const instructionsPrompts = resultMessages.filter(
1798+
(m: any) => m.tags?.includes('INSTRUCTIONS_PROMPT'),
1799+
)
1800+
// Either 0 (if aggressive pruning removed it) or 1 (kept the last one)
1801+
// The key assertion is that we don't have 2 (both old ones kept)
1802+
expect(instructionsPrompts.length).toBeLessThanOrEqual(1)
1803+
if (instructionsPrompts.length === 1) {
1804+
// If one remains, it should be 'Old instructions 2' (the last remaining after PASS 0)
1805+
expect(instructionsPrompts[0].content[0].text).toBe('Old instructions 2')
1806+
}
1807+
})
17241808
})
17251809

17261810
describe('context-pruner orphan removal', () => {

.agents/context-pruner.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ const definition: AgentDefinition = {
225225
return indices
226226
}
227227

228-
// PASS 0: Remove last instructions prompt message.
228+
// PASS 0: Remove last instructions prompt and subagent spawn messages.
229229
let currentMessages = [...messages]
230230
const lastInstructionsPromptIndex = currentMessages.findLastIndex(
231231
(message) => message.tags?.includes('INSTRUCTIONS_PROMPT'),
@@ -251,6 +251,18 @@ const definition: AgentDefinition = {
251251
return
252252
}
253253

254+
// PASS 0.5: Remove all remaining INSTRUCTIONS_PROMPT messages except the last one
255+
const remainingInstructionsPromptIndex = currentMessages.findLastIndex(
256+
(message) => message.tags?.includes('INSTRUCTIONS_PROMPT'),
257+
)
258+
if (remainingInstructionsPromptIndex !== -1) {
259+
currentMessages = currentMessages.filter(
260+
(message, index) =>
261+
!message.tags?.includes('INSTRUCTIONS_PROMPT') ||
262+
index === remainingInstructionsPromptIndex,
263+
)
264+
}
265+
254266
// PASS 1: Truncate large tool results
255267
// Only prune the tool result content, keeping the tool-call/tool-result pairs intact
256268
const afterPass1 = currentMessages.map((message) => {

0 commit comments

Comments
 (0)