diff --git a/src/agents/pi-extensions/compaction-safeguard.test.ts b/src/agents/pi-extensions/compaction-safeguard.test.ts index 3b6ba42dcad..36c6bc753ae 100644 --- a/src/agents/pi-extensions/compaction-safeguard.test.ts +++ b/src/agents/pi-extensions/compaction-safeguard.test.ts @@ -13,6 +13,7 @@ const { formatToolFailuresSection, splitPreservedRecentTurns, formatPreservedTurnsSection, + buildCompactionStructureInstructions, resolveRecentTurnsPreserve, computeAdaptiveChunkRatio, isOversizedForSummary, @@ -400,6 +401,13 @@ describe("compaction-safeguard recent-turn preservation", () => { expect(resolveRecentTurnsPreserve(-1)).toBe(0); expect(resolveRecentTurnsPreserve(99)).toBe(12); }); + + it("builds structured instructions with required sections", () => { + const instructions = buildCompactionStructureInstructions("Keep security caveats."); + expect(instructions).toContain("## Decisions"); + expect(instructions).toContain("## Exact identifiers"); + expect(instructions).toContain("Keep security caveats."); + }); }); describe("compaction-safeguard extension model fallback", () => { diff --git a/src/agents/pi-extensions/compaction-safeguard.ts b/src/agents/pi-extensions/compaction-safeguard.ts index 8eb613da42e..a90df969068 100644 --- a/src/agents/pi-extensions/compaction-safeguard.ts +++ b/src/agents/pi-extensions/compaction-safeguard.ts @@ -31,6 +31,13 @@ const MAX_TOOL_FAILURE_CHARS = 240; const DEFAULT_RECENT_TURNS_PRESERVE = 3; const MAX_RECENT_TURNS_PRESERVE = 12; const MAX_RECENT_TURN_TEXT_CHARS = 600; +const REQUIRED_SUMMARY_SECTIONS = [ + "## Decisions", + "## Open TODOs", + "## Constraints/Rules", + "## Pending user asks", + "## Exact identifiers", +] as const; type ToolFailure = { toolCallId: string; @@ -254,6 +261,20 @@ function formatPreservedTurnsSection(messages: AgentMessage[]): string { return `\n\n## Recent turns preserved verbatim\n${lines.join("\n")}`; } +function buildCompactionStructureInstructions(customInstructions?: string): string { + const sectionsTemplate = [ + "Produce a compact, factual summary with these exact section headings:", + ...REQUIRED_SUMMARY_SECTIONS, + "For ## Exact identifiers, preserve literal values exactly as seen (IDs, URLs, file paths, ports, hashes, dates, times).", + "Do not omit unresolved asks from the user.", + ].join("\n"); + const custom = customInstructions?.trim(); + if (!custom) { + return sectionsTemplate; + } + return `${sectionsTemplate}\n\nAdditional focus:\n${custom}`; +} + /** * Read and format critical workspace context for compaction summary. * Extracts "Session Startup" and "Red Lines" from AGENTS.md. @@ -415,6 +436,7 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void { }); messagesToSummarize = summaryTargetMessages; const preservedTurnsSection = formatPreservedTurnsSection(preservedRecentMessages); + const structuredInstructions = buildCompactionStructureInstructions(customInstructions); // Use adaptive chunk ratio based on message sizes, reserving headroom for // the summarization prompt, system prompt, previous summary, and reasoning budget @@ -439,7 +461,7 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void { reserveTokens, maxChunkTokens, contextWindow: contextWindowTokens, - customInstructions, + customInstructions: structuredInstructions, previousSummary: effectivePreviousSummary, }); @@ -453,7 +475,7 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void { reserveTokens, maxChunkTokens, contextWindow: contextWindowTokens, - customInstructions: TURN_PREFIX_INSTRUCTIONS, + customInstructions: `${TURN_PREFIX_INSTRUCTIONS}\n\n${structuredInstructions}`, previousSummary: undefined, }); summary = `${historySummary}\n\n---\n\n**Turn Context (split turn):**\n\n${prefixSummary}`; @@ -493,6 +515,7 @@ export const __testing = { formatToolFailuresSection, splitPreservedRecentTurns, formatPreservedTurnsSection, + buildCompactionStructureInstructions, resolveRecentTurnsPreserve, computeAdaptiveChunkRatio, isOversizedForSummary,