In the previous chapter, Session Memory Optimization, we looked at the "fast path"βloading a pre-made "cheat sheet" from your hard drive to save space.
But what happens if that cheat sheet doesn't exist? Or what if you've done so much work since the last save that the cheat sheet is outdated?
In these cases, we have to do things the hard way. We must read the entire conversation and rewrite it into a concise summary. This is the core engine of the project, simply called Compaction.
Imagine you are a CEO. Your team hands you a 200-page transcript of every meeting they've had for the last month. You don't have time (or memory space) to read every single word, joke, or "um, wait, let me fix that typo" comment.
You need an Executive Summary. You want a single page that says:
Use Case: You are 100 messages deep into refactoring a complex database class. You hit the token limit. The system triggers Compaction. It takes your 100 messages (the "transcript") and replaces them with 1 message (the "summary") that preserves all the technical context but discards the chatter.
To pull this off, we use three main components:
When compactConversation is called, the system pauses your main chat and runs a background task.
The secret sauce of this system is how we ask for the summary. If we aren't careful, the AI might say, "The user worked on code," which is useless.
We use a Structured Prompt to force specific details.
Inside prompt.ts, we instruct the AI to output specific sections:
Here is a simplified look at how the prompt is constructed:
// From prompt.ts
export function getCompactPrompt(customInstructions?: string): string {
// We explicitly forbid tool use - we just want text.
let prompt = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.
Your task is to create a detailed summary...
Include:
1. Primary Request and Intent
2. Key Technical Concepts
3. Files and Code Sections (Include full snippets!)
...`
return prompt
}
Explanation: We explicitly tell the AI not to use tools (like reading files) because we just want it to process the text history it already has.
Now, let's look at the main engine in compact.ts. This function coordinates the whole process.
First, we send the history to the AI to get our summary text.
// From compact.ts (Simplified)
async function streamCompactSummary({ messages, summaryRequest, ... }) {
// We create a temporary "fork" of the agent to do this work
// so we don't mess up the main conversation state.
const response = await queryModelWithStreaming({
messages: [...messages, summaryRequest], // History + "Please summarize"
systemPrompt: "You are a helpful AI assistant...",
// ... other options
});
return response; // Contains the summary text
}
Once we have the text, we don't just paste it in. We wrap it in a UserMessage so the AI thinks the user provided this context.
// From compact.ts (Simplified)
const summaryMessage = createUserMessage({
content: `This session is continued from a previous conversation.
Summary of earlier history: \n\n${summaryText}`,
isCompactSummary: true // Flags this for the UI
});
We also create a "Boundary" message. This is a system message that acts as a divider. It helps the system calculate token usage later (we know everything above this marker is compressed).
// From compact.ts (Simplified)
const boundaryMarker = createCompactBoundaryMessage(
'auto', // Triggered automatically
preCompactTokenCount, // How many tokens we saved
lastMessageUuid // Where we made the cut
);
Finally, the function returns the new, clean state of the world.
// From compact.ts
return {
boundaryMarker, // The "Wall"
summaryMessages, // The "Executive Summary"
attachments, // Files we need to keep open (more on this later)
messagesToKeep, // Recent messages we didn't delete
};
Explanation: This return object replaces your old 100-message array. The "backpack" is now light again, containing only the boundary, the summary, and perhaps the last 2 or 3 messages.
Sometimes, you don't want to summarize everything. You might want to summarize only the first half of the conversation but keep the detailed logs of the last 10 minutes.
This is called Partial Compaction.
The logic is similar, but instead of summarizing the whole array, we slice it:
// From compact.ts
export async function partialCompactConversation(allMessages, pivotIndex, ...) {
// Slice the array at the pivot point
const messagesToSummarize = allMessages.slice(0, pivotIndex);
const messagesToKeep = allMessages.slice(pivotIndex);
// Summarize only the first chunk
const summary = await streamCompactSummary({ messages: messagesToSummarize ... });
// Return the Summary combined with the kept messages
return {
summaryMessages: [createSummary(summary)],
messagesToKeep: messagesToKeep
};
}
In this chapter, you learned:
Now that we have a Summary and a Boundary Marker, how exactly do we organize these messages in the array? How do we ensure the AI knows which messages belong to which "era" of the conversation?
Next Chapter: Message Grouping & Boundaries
Generated by Code IQ