In the previous chapter, Vector Memory Sync (Semantic Search), we gave Claude the ability to search for "concepts" in a massive pile of data.
But finding the data is only half the battle. Imagine you find 500 relevant documents. You cannot paste all of them into the chat window because LLMs (Large Language Models) have a limit on how much text they can read at once (the Context Window).
We need a way to take that mountain of data and condense it into a concise, highly relevant "Briefing Packet" for Claude.
By default, an LLM is stateless. Every time you send a message, you have to resend the entire history of the conversation so it knows what you are talking about.
If your project history is huge, you face two problems:
The Context Reconstruction Engine acts like an executive assistant. Before you send your message to Claude, this engine runs a quick process:
Imagine you worked on auth.ts on Monday. On Friday, you open a new terminal and type:
"Finish the TODOs in the auth file."
Without this engine, Claude would ask: "Which auth file? What TODOs?"
With this engine:
Think of the prompt as a Suitcase with a weight limit. We need to pack the most valuable items first.
The Engine uses a "Token Budget" strategy:
Let's look at how the sausage is made. The main logic resides in src/services/context/ContextBuilder.ts.
It functions like an assembly line, building the text string piece by piece.
generateContext)This is the entry point. It connects to the database we built in Chapter 2.
// From src/services/context/ContextBuilder.ts
export async function generateContext(input, useColors): Promise<string> {
// 1. Load configuration (how many items to fetch?)
const config = loadContextConfig();
// 2. Connect to the SQLite database
const db = initializeDatabase();
if (!db) return "";
// 3. Start the build process
// ... (continues below)
}
What is happening? We load a config file (which defines our "Suitcase size") and open a connection to the SQLite file where our memories are stored.
Next, we need to fetch the data. We don't just grab everything. We use specific queries to get the right mix of "Observations" (detailed actions) and "Summaries" (high-level recaps).
// Inside generateContext...
// Query detailed actions (e.g., "Read file X", "Ran test Y")
const observations = queryObservations(db, project, config);
// Query high-level summaries (e.g., "Session 4: Fixed login bug")
const summaries = querySummaries(db, project, config);
// If we have nothing, return an empty string
if (observations.length === 0 && summaries.length === 0) {
return renderEmptyState(project, useColors);
}
Explanation:
The queryObservations function (in ObservationCompiler.ts) executes a SQL query that sorts by time and limits the results based on our config (e.g., "Last 50 items").
Now we have the raw data arrays. We need to turn them into a single string formatted as Markdown. This happens in buildContextOutput.
// From src/services/context/ContextBuilder.ts
function buildContextOutput(project, observations, summaries, config) {
const output: string[] = [];
// 1. Calculate how much "room" we have left
const economics = calculateTokenEconomics(observations);
// 2. Add the Header (Project Name, Stats)
output.push(...renderHeader(project, economics, config));
// 3. Add the Timeline (The list of actions)
const timeline = buildTimeline(observations, summaries);
output.push(...renderTimeline(timeline, config));
return output.join("\n");
}
Explanation:
calculateTokenEconomics: Adds up the size of the text to ensure we are safe.renderHeader: Creates a pretty title block.renderTimeline: Loops through the data and creates the Markdown list items.
The heavy lifting of sorting the data happens in src/services/context/ObservationCompiler.ts.
It has to merge two different types of time:
It creates a unified "Timeline" so Claude sees a chronological story.
// From src/services/context/ObservationCompiler.ts
export function buildTimeline(observations, summaries): TimelineItem[] {
// 1. Combine both lists into one array
const timeline: TimelineItem[] = [
...observations.map((obs) => ({ type: "observation", data: obs })),
...summaries.map((sum) => ({ type: "summary", data: sum })),
];
// 2. Sort by timestamp (Oldest -> Newest)
timeline.sort((a, b) => {
return a.data.created_at_epoch - b.data.created_at_epoch;
});
return timeline;
}
Why sort? LLMs read top-to-bottom. We want the story to flow linearly: "First I planned the feature, then I wrote the code, then I ran the test." If the order is mixed up, the AI gets confused.
Here is how a simple request from you gets transformed into a rich prompt for Claude.
What does the text actually look like? The engine produces a hidden block of text that looks something like this:
# โ๏ธ PILOT CONTEXT: My-Web-App
## โฑ๏ธ Recent Timeline
- [Observation] 10:00 AM: Read file `src/app.ts` (Tokens: 150)
- [Observation] 10:02 AM: Ran command `npm test` (Failed)
- [Summary] Yesterday: Created the database schema.
## ๐ง Memory Bank
- Previous Session Goal: Refactor the API.
- Status: In Progress.
By injecting this invisibly into the prompt, Claude acts as if it has been sitting next to you for the last week, even though it just "woke up."
The Context Reconstruction Engine is the storyteller of the system.
This system allows claude-pilot to maintain continuity across days or weeks of work.
However, remembering the past is not enough. We also need to plan for the future. In the next chapter, we will look at how to guide Claude using strict specifications (Specs) rather than just open-ended chat.
Next: Spec-Driven Agent Workflow
Generated by Code IQ