Long agent runs do not fail only because tools are wrong. They also fail because the context becomes a landfill: every thought, every tool dump, every retry lands in the same window until the model gets slower, noisier, and more expensive.
Solon AI treats that as a runtime concern, not a prompt-writing problem. ContextCompressionInterceptor watches the ReAct trace, and when history crosses a threshold it compresses the oldest slice with a pluggable CompressionStrategy.
This post is about the strategy layer: what each built-in strategy keeps, when to use it, and how to stack them for production.
The product problem
A naive agent loop stores everything:
- user goal
- model thoughts
- tool calls + raw observations
- more thoughts
- more tool dumps
After enough turns you hit three failure modes:
- token tax — you pay for history that no longer changes the decision
- attention rot — the model re-reads noise and misses the hard facts
- hard truncation — the provider silently clips the window and you lose the wrong half
Compression is the controlled alternative: keep the decision-critical residue, drop or archive the rest.
Core pieces
| Piece | Role |
|---|---|
ContextCompressionInterceptor |
Watches message / token thresholds and triggers compression |
CompressionStrategy |
Decides how expired messages become residual context |
ReActAgent.defaultInterceptorAdd(...) |
Mounts the guard on the agent loop |
Official docs:
- Context compression: article/1378
- Built-in interceptors: article/1379
- ReAct interceptor lifecycle: article/1316
Dependency:
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-ai-agent</artifactId>
</dependency>
How the interceptor works
From the docs, the flow is intentionally simple:
- set thresholds such as
maxMessages(for example 12 or 40) - when history exceeds the threshold, take the oldest slice (Expired Messages)
- run the configured strategy on that slice
- inject the resulting summary back into the context head
- remove the original detail messages
That is not “delete history.” It is summarize and re-anchor. The agent still has a continuous memory chain; it just no longer carries every intermediate observation at full fidelity.
Constructor shape used in the official sample:
ContextCompressionInterceptor memoryGuard =
new ContextCompressionInterceptor(40, 40000, myStrategy);
The first number is the message threshold. The second is a content / token-related budget. The third is the strategy.
Four built-in strategies
1) LLMCompressionStrategy — one-shot semantic summary
- Keeps: a short narrative of what happened
- Drops: verbose thoughts and raw dumps
- Best for: general chatty agents where exact tool payloads do not matter later
- Trade-off: cheap and simple; may blur exact parameters
Use this when “what did we decide?” is enough and “what exact JSON did the API return?” is not.
2) KeyInfoExtractionStrategy — fact board
- Keeps: facts, parameters, conclusions, verified failed attempts
- Drops: long reasoning prose
- Best for: SQL generation, ops runbooks, form-filling, any vertical task where a missed ID breaks the next tool call
- Trade-off: less readable as a story, more useful as a checklist
This is the strategy I reach for first in production tool loops. Models recover better from a dense fact board than from a polite but vague summary.
3) HierarchicalCompressionStrategy — rolling summary
-
Keeps:
Summary_N-1 + new history -> Summary_N - Drops: nothing critical from the chain of progress; it folds old summary into new summary
- Best for: multi-hour tasks, multi-step research, long support threads
- Trade-off: needs a model call on each compression; worth it when the task must survive many windows
This is the “infinite endurance” option. Memory never fully resets; it densifies.
4) VectorStoreCompressionStrategy — cold archive + anchor
- Keeps in context: a retrieval anchor
- Keeps outside context: original detail in a vector store
- Best for: audit, compliance, or agents that may need to re-open raw evidence later
- Trade-off: needs a vector backend; pair it with a retrieval tool / talent so the agent can fetch archives on demand
The official sample even mounts the vector strategy as a talent so the agent can actively query archived memory:
.defaultTalentAdd(vectorStoreCompression)
That turns compression from pure lossy reduction into hot summary + cold recall.
Production pattern: composite stack
Single strategies are easy. Production usually wants a stack. Solon provides CompositeCompressionStrategy for that.
Recommended order from the docs:
- VectorStore first — archive raw detail so nothing is physically lost
- KeyInfo next — keep a hard fact board in the working window
- Hierarchical last — maintain a rolling global progress summary
VectorStoreCompressionStrategy vectorStoreCompression =
new VectorStoreCompressionStrategy(vectorRepo);
CompressionStrategy myStrategy = new CompositeCompressionStrategy()
.addStrategy(vectorStoreCompression)
.addStrategy(new KeyInfoExtractionStrategy(chatModel))
.addStrategy(new HierarchicalCompressionStrategy(chatModel));
ContextCompressionInterceptor memoryGuard =
new ContextCompressionInterceptor(40, 40000, myStrategy);
Agent agent = ReActAgent.of(chatModel)
.defaultInterceptorAdd(memoryGuard)
.defaultTalentAdd(vectorStoreCompression) // optional active archive lookup
.build();
Why this order works:
- archive first so later strategies can be aggressive
- extract facts so tool parameters survive
- roll a hierarchical summary so the agent still knows the mission story
Thresholds that do not fight the model
The docs give practical ranges by context size:
| Context size | maxMessages |
content budget | Notes |
|---|---|---|---|
| ~20k | 10–15 | 8k–12k | compress often; small windows punish laziness |
| ~100k | 30–40 | 24k–32k | balanced; 32k is a good high-recall line for many models |
| ~200k | 50–60 | 48k–64k | fewer compressions; preserve tool-call chains longer |
| ~1m | 100–150 | 128k+ | budget for cost/latency more than hard capacity |
Two reminders from the official guide:
- a larger summary window helps the LLM understand history, but costs more
- a window that is too small makes the model clumsy — for example a file just read can get compressed before it is used
So do not set thresholds only from provider max tokens. Set them from task shape:
- short tool loops with critical IDs → smaller window + KeyInfo
- long research / multi-agent sessions → larger window + Hierarchical
- regulated workloads → VectorStore in the stack
How this pairs with other guards
Compression is one layer of runtime hygiene. It works best with the rest of the ReAct production kit:
| Guard | What it stops |
|---|---|
maxTurns / autoRethink
|
unbounded reasoning depth |
StopLoopInterceptor |
repeated same-action loops |
ToolRetryInterceptor |
flaky tool physics |
ToolSanitizerInterceptor |
giant / sensitive observations before they pollute memory |
sessionWindowSize |
silent history bloat across turns |
ContextCompressionInterceptor |
long-run context landfill inside one run |
A practical recipe for a durable agent:
ReActAgent agent = ReActAgent.of(chatModel)
.maxTurns(12)
.autoRethink(true)
.sessionWindowSize(8)
.defaultInterceptorAdd(new StopLoopInterceptor())
.defaultInterceptorAdd(new ToolRetryInterceptor())
.defaultInterceptorAdd(new ToolSanitizerInterceptor())
.defaultInterceptorAdd(memoryGuard)
.build();
Sanitize before you compress. If you archive garbage, you only create a more durable garbage pile.
What not to do
- Do not treat compression as a substitute for better tools. If every observation is a 50 KB blob, fix the tool output first.
- Do not compress so aggressively that path parameters and entity IDs disappear. Prefer KeyInfo when tools are stateful.
- Do not ignore cold storage for audit paths. Hierarchical summary is great for progress, weak as a compliance record.
- Do not invent a second memory system in application code when the interceptor already owns the lifecycle hook (
onReasonStartis exactly where this kind of work belongs).
Minimal decision guide
| If your agent... | Start with |
|---|---|
| chats and rarely reuses exact tool dumps | LLMCompressionStrategy |
| depends on IDs, filters, SQL, configs | KeyInfoExtractionStrategy |
| runs for many windows / hours | HierarchicalCompressionStrategy |
| may need original evidence later | VectorStoreCompressionStrategy |
| is shipping to production | Composite: Vector + KeyInfo + Hierarchical |
Closing
Context compression is not a clever prompt trick. It is an operating policy for long agent runs:
- decide what is hot
- decide what is cold
- decide when the window is full enough to force a rewrite of history
Solon’s strategy model makes that policy explicit. Start with KeyInfo, add Hierarchical when tasks get long, and put VectorStore underneath when losing the original record is unacceptable.
Docs to keep open while you tune:
Top comments (1)
Context compression is really governance for attention. The agent should not read everything just because it can. A good compression strategy preserves decisions, constraints, and evidence while letting stale detail fall out of the active path.