Context compaction
chitta scores conversation turns by recency, query similarity, and coverage in stored memories, then selects turns within a token budget.
Status as of 2026-09-20 Hook lifecycle · Tool contract.
Context limits
Long-running agent sessions accumulate conversation history. Compaction selects which turns to retain when that history exceeds the context budget.
Selection can use age, summaries, or relevance scores. chitta adds an estimate of coverage in the memory store to its scoring formula.
A debugging turn may contain a root cause recorded nowhere else. A turn with a similar stored memory receives a lower novelty contribution; embedding similarity does not establish that every detail is preserved.
Compaction inputs
- Scores each turn purely by text relevance to next task
- No knowledge of what the agent has already learned
- Does not query a persistent memory store
- 50–70% reduction, verbatim survivors
- Filters conversation text
- Checks each turn against what’s already in memory
- High memory coverage → lower novelty score
- Low memory coverage → higher novelty score
- Recency and structural scoring when embedder unavailable
- Integrated with PreCompact hook automatically
Scoring pipeline
Every non-system message gets a score. System messages are always kept. The score balances three signals:
0.3 × recency
+ 0.4 × semantic_sim(msg, query)
+ 0.3 × (1 − memory_coverage)
)
// memory_coverage: cosine sim vs nearest 3 soul memories
// high coverage → already learned → cheaper to drop
| Component | What it measures | Weight |
|---|---|---|
| structural_weight | Role importance: system=2.0, user=1.0, assistant=0.8, tool=0.5 | multiplier |
| recency | Linear 0.1 (oldest) → 1.0 (most recent) | 0.3 |
| semantic_sim | Cosine similarity of turn embedding vs. upcoming query | 0.4 |
| memory_coverage | Max cosine sim vs. nearest 3 soul memories (higher = already learned) | 0.3 (inverted) |
field_store_→recall(emb, k=3) to get memory_coverage. Compute cosine similarity vs. query embedding for semantic_sim.target_ratio of original tokens is filled. System messages bypass the budget check entirely.Live scoring demo
Scroll to animate. Each bar shows the computed score. Green = kept, faded = memory-covered drop.
compact_context tool
| Parameter | Type | Required | Description |
|---|---|---|---|
| messages | array | yes | Conversation turns [{role, content}] — OpenAI/Claude format |
| query | string | no | Upcoming task hint — biases semantic scoring toward what’s needed next |
| target_ratio | float | no | Fraction of tokens to keep (default: 0.4, range: 0.05–1.0) |
| distill_novel | boolean | no | Optional daemon pass, default false; requires an embedder and store. Records a capped excerpt of qualifying dropped turns. The advertised schema still calls it reserved; the PreCompact hook does not enable it. |
{
"messages": [...], // kept messages, original order, verbatim
"stats": {
"before_tokens": 48200,
"after_tokens": 18600,
"dropped": 34,
"dropped_pct": 61.4,
"embedding": true // false if embedder unavailable (graceful degradation)
}
}
echo '{
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {
"name": "compact_context",
"arguments": {
"messages": [...],
"query": "implement the new feature",
"target_ratio": 0.4
}
}
}' | chitta
Hook integration
The PreCompact hook calls compact_context for memory-aware scoring and records statistics. It does not replace the harness transcript with returned messages or control the harness's own compaction. Its current flow:
query to bias scoring toward what’s contextually important.You can also trigger compaction manually from any hook or script:
# In UserPromptSubmit hook — compact before long-session turns
MESSAGES=$(jq -sc '[.[] | select(.type=="user" or .type=="assistant") | ...] | .[-60:]' "$TRANSCRIPT_PATH")
echo "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"compact_context\",\"arguments\":{\"messages\":$MESSAGES,\"query\":\"$QUERY\",\"target_ratio\":0.4}}}" \
| chitta
Optional recording of novel drops
The daemon implements distill_novel: true in compact.cpp, default false. With an embedder and store, it classifies dropped turns by a substring heuristic, skips turns shorter than 40 characters or already covered by memory, and records at most the first 600 characters with a provenance prefix.
This is a bounded excerpt, not lossless preservation or LLM summarization. The hook does not enable the option. Retain the source transcript when exact evidence matters.