
The History Collapse Pattern: Keep Long-Running Claude API Agents Focused and Affordable
Chris Harper
3 min read
Aug 9, 2026 · 20:07 UTC
After N turns, inject one structured summary and truncate the raw message history above it — cost stays flat, the agent stays sharp, and you never hit the token wall mid-task.
Claude Code has /compact to compress an interactive session. If you're building your own multi-turn agent with the Claude API, you need to wire this yourself. Here's the pattern.
Why message history grows into a problem
Each API call sends the full messages array. A 10-turn coding agent with long file reads can easily accumulate 40,000–80,000 input tokens. At Claude Sonnet's current pricing, that's $0.12–$0.24 per turn just in history — and the longer the session runs, the worse it gets. Beyond cost, context quality degrades: early context crowds out attention for recent work.
The fix is not to blindly truncate (you lose decisions made 30 turns ago). The fix is to compress semantically: extract what matters and discard the rest.
The compression call
def compress_history(client, messages: list, model: str) -> list:
"""Collapses message history into one summary + system context."""
if len(messages) < 2:
return messages
compression_prompt = """You are summarizing a coding agent's work session.
Produce a concise technical summary covering:
1. The task being worked on and its current state
2. Files created, modified, or deleted (with paths)
3. Key decisions made and why
4. Errors encountered and how they were resolved
5. Immediate next steps
Be specific and preserve exact file paths, function names, and error messages.
Format as structured markdown."""
response = client.messages.create(
model=model,
max_tokens=2000,
system=compression_prompt,
messages=messages,
)
summary = response.content[0].text
return [
{"role": "user",
"content": f"[SESSION CONTEXT — compressed from {len(messages)} messages]
{summary}"},
{"role": "assistant",
"content": "Understood. I'll continue from where we left off."},
]
When to trigger it
TOKEN_THRESHOLD = 60_000 # compress before you hit 80k+ expensive territory
TURN_THRESHOLD = 20 # or every 20 turns
def should_compress(messages: list) -> bool:
# Rough token estimate: ~4 chars per token
approx_tokens = sum(len(str(m)) // 4 for m in messages)
return approx_tokens > TOKEN_THRESHOLD or len(messages) > TURN_THRESHOLD * 2
Full agent loop
from anthropic import Anthropic
client = Anthropic()
MODEL = "claude-sonnet-4-6-20261001"
messages = []
while True:
user_input = input("You: ")
if user_input.lower() == "exit":
break
messages.append({"role": "user", "content": user_input})
if should_compress(messages[:-1]): # compress everything except the new turn
messages = compress_history(client, messages[:-1], MODEL) + [messages[-1]]
print("[context compressed]")
response = client.messages.create(
model=MODEL,
max_tokens=8096,
messages=messages,
)
reply = response.content[0].text
messages.append({"role": "assistant", "content": reply})
print(f"Claude: {reply}")
What to preserve in the summary
The compression prompt matters more than the timing. Include: current task state, file paths and their contents' purpose, key design decisions, open errors. Exclude: full file reads that can be re-read, exploratory dead ends.
For longer sessions, run two levels: compress every 20 turns into a "session chunk," then once per hour compress all chunks into a "project context." The pattern scales to sessions hours or days long.
Sources: Compaction — Claude Platform Docs · Context Window Management — Claude Code Docs · Claude Code /compact Deep-Dive — claudefa.st