Session Context Assembly: The Hydration Budget
Every recalled token competes with the task itself. Load the index cheaply; fetch depth only for what the session needs.
The Context Window Is Not Free Storage
At the start of every session, an agent faces a loading decision: what from the memory store goes into context, and how much? The wrong answer to this question is "everything, to be safe." The right answer requires understanding what a context window actually is.
A context window is working memory, not a database. Every token loaded into it is a token not available for the task itself — for reasoning, for tool calls, for generated output, for conversation history. An agent with a 200,000-token context window that loads 40,000 tokens of memories at session start has committed 20% of its cognitive budget to hydration before the task begins. If the task is narrow and most of those memories are irrelevant, that is 40,000 tokens of overhead that slows the model and displaces task context.
The discipline is the hydration budget: a fixed token allocation for memory hydration, managed through a two-tier architecture that separates index discovery from content depth.
The Two-Tier Approach
The session assembly strategy has two tiers with different cost profiles.
Tier 1: The Compact Index. A one-line entry per memory — heading, type, and confidence. No full content. This is the table of contents: it tells the agent what it knows without loading what it knows. At roughly 15 tokens per entry — a realistic price for a heading like "Deploy pipeline: use gh workflow run deploy.yml" plus its type and confidence markers — an index of 200 memories costs 3,000 tokens. (The recall lesson's Stage 1 search summaries cost roughly 30 tokens per line because each carries a 120-character snippet; the index line carries no snippet.) The agent can read the full index, understand its knowledge landscape, and make informed decisions about what to fetch.
Tier 2: Top-K Relevant Full Content. After reading the index, the agent uses semantic search against the declared task context to identify the 4-6 memories most relevant to this session. Those are fetched in full. At roughly 250 tokens per full memory, top-5 costs 1,250 tokens. Total hydration: 4,250 tokens for 200 memories of accumulated knowledge.
Compare this to the dump-everything approach: 200 memories at 250 tokens each is 50,000 tokens before the task begins. The two-tier approach delivers the same knowledge accessibility at under 9% of the cost.
Building the Compact Index
The compact index is a database query that returns heading, type, and confidence for all live memories in the authorized namespaces. Nothing more.
async function buildCompactIndex(namespaces: string[]): Promise<IndexEntry[]> {
const result = await pool.query<IndexEntry>(
`SELECT memory_id, heading, memory_type, confidence
FROM memories
WHERE namespace = ANY($1)
AND deleted_at IS NULL
AND (expires_at IS NULL OR expires_at > NOW())
ORDER BY confidence DESC`,
[namespaces]
)
return result.rows
}
The content column is intentionally absent from this query. Fetching content here defeats the purpose: you pay the transmission cost for content you have not decided to use yet. The index is a discovery tool. Content is fetched after the discovery decision is made.
The ordering by confidence DESC puts the most reliable memories at the top of the index, which makes the agent's scanning more efficient: the first entries it reads are the ones most worth fetching in full.
Fetching Top-K Relevant
The semantic search for top-k relevant is the same pgvector query the rest of the track uses, scoped to the allowed namespaces:
async function fetchTopKRelevant(
queryEmbedding: number[],
namespaces: string[],
topK: number
): Promise<MemoryRecord[]> {
const vectorStr = "[" + queryEmbedding.join(",") + "]"
const result = await pool.query<MemoryRecord>(
`SELECT memory_id, namespace, memory_type, heading, content, confidence,
source_type, supersedes, expires_at, deleted_at
FROM memories
WHERE namespace = ANY($2)
AND deleted_at IS NULL
AND (expires_at IS NULL OR expires_at > NOW())
ORDER BY embedding <=> $1::vector ASC
LIMIT $3`,
[vectorStr, namespaces, topK]
)
return result.rows
}
The two queries — index and top-k — can run in parallel with Promise.all. They are independent queries on the same table; there is no dependency between them.
Both queries carry the same liveness guards as the canonical recall query from the indexing lesson: soft-deleted rows and expired working memories never enter the session preamble. A hydrator that drops the expires_at clause will hydrate expired task-state as if it were current — exactly the contamination the hygiene lesson warns about.
Enforcing the Budget
The budget enforcement is a greedy knapsack: always include the compact index (it is the discovery layer and cannot be dropped), then add relevant memories in relevance order until adding the next one would exceed the token cap.
async function hydrateSession(
queryEmbedding: number[],
namespaces: string[],
topK: number,
tokenBudget: number
): Promise<HydrationResult> {
const [index, relevant] = await Promise.all([
buildCompactIndex(namespaces),
fetchTopKRelevant(queryEmbedding, namespaces, topK),
])
// Index always loads — estimate its token cost
const indexTokens = index.reduce(
(sum, entry) => sum + Math.ceil(entry.heading.length / 4),
0
)
// Add relevant memories greedily until budget is hit
let relevantTokens = 0
const accepted: MemoryRecord[] = []
for (const mem of relevant) {
const cost = Math.ceil((mem.heading + " " + mem.content).length / 4)
if (indexTokens + relevantTokens + cost > tokenBudget) break
accepted.push(mem)
relevantTokens += cost
}
return {
index,
relevant: accepted,
tokensUsed: indexTokens + relevantTokens,
tokenBudget,
truncated: accepted.length < relevant.length,
}
}
The token estimate — chars / 4 — is an approximation. Real tokenizers vary by model; this convention understates cost for code and overstates it for dense prose, but it is close enough for budget enforcement without requiring a live tokenizer call per memory.
The Index-File Pattern
The compact index at session start reflects a broader pattern: maintain a one-line pointer index alongside full detail files. This academy's CLAUDE.md context files use it. Each entry in a memory index file is a single line under 200 characters — the heading and a type marker. Full detail lives in the store, fetched on demand.
When a new session starts and the task is "refactor the payment module," the agent reads the index and sees entries like "Payment module uses Stripe Connect" and "Legacy payment processor deprecated Q1." Those two are the ones worth fetching in full. The 190 other memories in the index are visible as headings, confirming they exist, but not loaded. If the task shifts and something else becomes relevant, the agent can fetch that entry without restarting the session.
This is the progressive disclosure principle from the recall lesson applied to session start: search returns summaries; agents fetch full content only for what matters. The index makes the first step — knowing what to fetch — cheap and fast.
Hydrate-Then-Verify
A final discipline: hydrated context is background, not instruction. Memories loaded into session context inform the agent about what was true and what was learned. They do not override the task specification, the operator's instructions, or the current evidence from tool calls.
The verify-before-trust rule from the memory-hygiene lesson applies here too: a memory that arrives via hydration is still a snapshot of a past observation. If it makes a claim about a live artifact — a file path, an API endpoint, a configuration key — the agent should confirm that claim against the live system before acting on it. Hydration makes the memory available; it does not make the memory current.
This is why hydrated context is background context, not an instruction set. The task declared at session start is the authority. Memories are the evidence base. The agent uses both, but does not let an old memory override a current observation.
What's Next
The compound-learning lesson closes the loop that makes memory systems compound. The session ends. The agent made mistakes, corrected them, and learned something new. The compounding loop — retro extracts the lesson, the lesson becomes a memory, the memory survives into the next session, behavior changes — is what separates an agent that improves from an agent that resets. One rule added to the build spec that every future run reads: that is the compounding loop producing a receipt.