Every turn, your harness assembles the model's context from scratch — and the context window is a small, fixed budget that the growing conversation will blow through. Module 3 is about spending that budget well: deciding what goes in every turn and what gets evicted, compacting old history into summaries so a session survives hundreds of turns without losing the plot, layering memory (session state, persistent files, the CLAUDE.md pattern) so knowledge outlives a single window, and treating the system prompt as infrastructure rather than prose. You build compaction plus a persistent memory layer, so your harness survives a 200-turn session.
Modules 1–2 gave you a loop that acts. Run it on anything real and you hit a wall within twenty turns: the message array grows every iteration (Module 1), tool results are the bulk of it (Module 2), and the context window is finite. Eventually the conversation won't fit — and long before that, the model's reasoning degrades as the window fills with stale tool output and dead ends. Context is the harness's scarcest, most quality-critical resource. Managing it is managing agent quality.
Note the distinction from the loop's job. The loop decides when to call the model; the context engine decides what the model sees when it's called. This is the layer that turns a demo that works for ten turns into an agent that works for a thousand.
Context budgets: what goes in every turn
Think of the context window as a fixed budget of tokens you re-spend on every single turn. Each turn, your harness reconstructs the prompt from parts, and every part competes for the same space:
Fig 1 — Every turn re-spends the whole budget. System prompt and recent turns are high-value; old turns and giant tool dumps are what you evict or compact.
Two rules follow immediately. First, keep tool outputs small at the source (Module 2's paginated reads, surgical edits) — since tool results are ~two-thirds of tokens, controlling them is the cheapest win. Second, have an eviction policy: when the budget tightens, something must leave. The naive policy — drop the oldest messages — is dangerous, because the oldest message is often the goal. Better policies keep the system prompt, the goal, and the most recent turns, and compress the middle. That compression is the next section.
Compaction and summarization
The core technique for surviving long sessions is compaction: when the context approaches a threshold (a common trigger is ~80% of the window), summarize the older portion of the conversation into a compact form and continue from the summary. You trade lossy detail for room to keep going — and done well, the summary preserves the decisions and state that matter while dropping the verbose journey.
Fig 2 — Compaction replaces a long run of old turns with a dense summary, reclaiming budget while preserving what the agent needs to continue coherently.
The subtlety is what to preserve. A good compaction summary is not "here's a paragraph about the conversation" — it's a structured snapshot of the working state: the goal, decisions made and why, files changed, what's verified, what's still open, and any hard-won facts (that flaky test, that API quirk). The harness typically produces it by asking the model itself to summarize, against a template that forces those fields. Get the template right and a 200-turn session compacts two or three times without ever losing the thread; get it wrong and the agent "forgets" a decision and re-litigates it.
Hermes offers an elegant variant worth knowing: lineage-based compression. Instead of rewriting history in place, it closes the current session, spawns a child session seeded with the summary, rotates the session ID, and records the parent→child link. The compressed view is what the model sees; the full history is preserved on the parent for audit and replay. Compaction without losing the original — a nice property for durability (Module 4).
Memory systems: surviving beyond one window
Compaction keeps a single session alive. Memory keeps knowledge alive across sessions and windows. There's a hierarchy, from most ephemeral to most durable:
| Layer | Scope | Holds | Example |
|---|---|---|---|
| Working context | this turn | the live message array | the loop's state (Module 1) |
| Session state | this session | run so far, survives compaction | Hermes SQLite session rows |
| Persistent memory files | across sessions | curated durable knowledge | CLAUDE.md / AGENTS.md |
| Retrieval (vector/search) | a large corpus | facts pulled in on demand | RAG over docs, code search |
The CLAUDE.md pattern
The most important memory idea for practical harnesses is deceptively low-tech: a human-curated memory file checked into the project (CLAUDE.md, AGENTS.md, and similar). It holds the durable facts an agent needs every session — how to build and test, project conventions, gotchas, "always run the linter before committing." The harness injects it into context at the start of every session.
Why it beats fancier auto-generated memory: a human vetted it. Auto-summarized memory drifts, hallucinates, and accretes noise; a hand-written file stays true because a person maintains it. It's semantic memory as infrastructure — versioned, reviewed, diffable, shared across the team. pi, Claude Code, and Hermes all support this pattern precisely because it's the highest-signal, lowest-cost memory a harness can have. When your agent keeps making the same mistake, the fix is usually a line in the memory file, not a cleverer prompt.
--no-verify; add to CLAUDE.md?"), but a human approves the write. That keeps the durable memory human-curated — trustworthy — while still letting the agent accumulate lessons. Fully autonomous memory writes are where auto-memory rots; gated writes keep it clean.System prompts as infrastructure, not prose
The last piece of context is the one that's there every turn: the system prompt. Beginners write it like an essay — a friendly paragraph about being a helpful assistant. In a real harness, the system prompt is infrastructure: a precise, structured specification of the agent's identity, its rules, its tools, and its operating procedure, engineered and version-controlled like any other critical component.
What belongs in it: the agent's role and hard constraints, how and when to use each tool (reinforcing the schemas from Module 2), the output/format contract, safety rules ("never run destructive commands without approval"), and the operating loop the agent should follow ("plan, then act, then verify"). What does not belong: verbose backstory, redundant politeness, anything the model already knows, or dynamic content that changes per turn (that's context injection, not the system prompt).
pi's discipline here is instructive: its system prompt is under 1,000 tokens. That's a deliberate constraint — every token in the system prompt is paid on every turn for the whole session, and a bloated system prompt is a permanent tax on your budget and a dilution of the signal. A tight, structured system prompt outperforms a long chatty one, and it's cheaper. Treat it as code: keep it minimal, review changes, measure their effect.
You build — compaction + persistent memory
import pathlib
WINDOW = 200_000 # your model's context size (tokens)
COMPACT_AT = 0.80 # trigger threshold
MEM_FILE = pathlib.Path("CLAUDE.md")
def tokens(messages): # cheap estimate; use a real tokenizer in prod
return sum(len(str(m["content"])) for m in messages) // 4
def load_memory(): # inject persistent memory every session
return MEM_FILE.read_text() if MEM_FILE.exists() else ""
def build_system_prompt():
base = "You are a coding agent. Plan, act with tools, verify. "
mem = load_memory()
return base + (f"\n\n# Project memory (CLAUDE.md)\n{mem}" if mem else "")
def compact(client, messages):
"""Summarize everything except the last few turns into a state snapshot."""
head, tail = messages[:-6], messages[-6:] # keep recent turns verbatim
summary = client.messages.create(
model="claude-sonnet-5", max_tokens=1024,
messages=head + [{"role": "user", "content":
"Summarize the conversation so far as a STATE SNAPSHOT with sections: "
"GOAL, DECISIONS (+why), FILES CHANGED, VERIFIED, OPEN QUESTIONS, FACTS LEARNED. "
"Be dense; preserve anything needed to continue."}],
).content[0].text
return [{"role": "user", "content": f"[COMPACTED STATE]\n{summary}"}] + tail
def run(client, goal, max_turns=200):
system = build_system_prompt()
messages = [{"role": "user", "content": goal}]
for turn in range(max_turns):
if tokens(messages) > WINDOW * COMPACT_AT: # budget check each turn
messages = compact(client, messages) # reclaim space, keep the plot
resp = client.messages.create(model="claude-sonnet-5", max_tokens=2048,
system=system, tools=TOOLS, messages=messages)
messages.append({"role": "assistant", "content": resp.content})
calls = [b for b in resp.content if b.type == "tool_use"]
if not calls:
return resp
messages.append({"role": "user", "content":
[{"type": "tool_result", "tool_use_id": c.id, "content": str(run_tool(c.name, c.input))}
for c in calls]})
Everything from this module is in that code. build_system_prompt treats the prompt as infrastructure and folds in the CLAUDE.md memory. tokens tracks the budget; compact fires at 80%, summarizing the head into a structured state snapshot while keeping recent turns verbatim (so the agent never loses immediate context). Run it on a long task — a multi-file refactor with a test loop — and watch it compact once or twice and keep going, coherent, past the point where the naive Module 2 harness would have overflowed and crashed.
remember(fact) tool (human-approved, per the earlier callout). (2) Swap the length estimate for a real tokenizer so the threshold is exact. (3) Add offloading: when a tool returns >2,000 tokens, write it to a file and put only "wrote result to tmp/out.txt (N lines)" in context. (4) Log a diff of the message array before/after each compaction so you can verify no decision was dropped — your regression test for the summarizer.FAQ
Why not just use a model with a huge context window and skip compaction?
Bigger windows help but don't remove the problem: cost scales with context re-sent every turn, latency grows, and — most importantly — reasoning still degrades as the window fills with noise (context rot). Compaction keeps the signal-to-noise ratio high, which improves quality even when everything technically fits. A big window is a bigger budget, not an infinite one.
What makes a good compaction summary?
Structure and state, not narrative. Force sections — goal, decisions and why, files changed, what's verified, open questions, facts learned — so the summary captures what's needed to continue rather than a readable recap. Test it: after compaction, can the agent proceed without re-asking something it already decided? If it re-litigates, your template is missing a field.
Where should durable knowledge live — the system prompt or a memory file?
The system prompt for what's true every turn and rarely changes (role, hard rules, tool procedure). The memory file for project-specific, evolving knowledge (build commands, conventions, gotchas). The system prompt is paid on every turn regardless; the memory file can be injected selectively and is human-editable. When in doubt, prefer the memory file — it's cheaper and easier to maintain.
Isn't auto-generated memory better than a hand-written file?
Usually not, in practice. Auto-memory drifts and accumulates noise because nothing prunes it; a human-curated file stays true because a person maintains and reviews it. The strongest pattern is hybrid: the agent proposes additions, a human approves them — accumulating lessons without letting the memory rot.
Takeaways
- The context window is a fixed budget re-spent every turn; the engine decides what stays and what's evicted.
- Keep tool outputs small at the source (they're ~2/3 of tokens) and evict by value, never blindly by age (you'll drop the goal).
- Compaction at ~80% summarizes old turns into a structured state snapshot; Hermes's lineage compression preserves the original.
- Memory is a hierarchy: working context → session state → persistent files (CLAUDE.md) → retrieval. Human-curated files beat auto-memory.
- The system prompt is infrastructure — tight, structured, version-controlled; pi keeps it under 1k tokens because every token is paid every turn.
- Compaction, offloading, and isolation are three tactics against the same enemy: a bloated, low-signal window.
References & further reading
- Anthropic — Effective context engineering for AI agents — budgets, compaction, note-taking, sub-agent isolation.
- Arize — Hermes harness architecture — lineage-based context compression and SQLite sessions.
- LangChain — Anatomy of an Agent Harness — compaction, tool-output offloading, memory injection (AGENTS.md).
- pi (pi.dev) — the sub-1k-token system prompt discipline.
- KV cache internals — the hardware reason long contexts cost so much per turn.