Summary
Double compaction is triggered by two related issues:
_checkCompaction() runs both after agent_end and again before the next prompt. The pre-prompt path uses the last assistant message’s original usage (kept region), which can still exceed the threshold even after compaction.
prepareCompaction() only blocks when the last entry is compaction. When a custom entry (like openclaw.cache-ttl) is appended after compaction, the guard misses the prior compaction and allows another compaction immediately.
Evidence (session JSONL)
Session: /Users/clawd/.openclaw/agents/main/sessions/93259adc-570a-4234-9e69-81ab7b1ba744.jsonl
2026-02-02T18:14:29.008Z compaction id bd55f1c2 tokensBefore 170231 parent f78129f3
2026-02-02T18:14:30.600Z compaction id 08b283f4 tokensBefore 170231 parent 96aed843
- Same
tokensBefore within ~1.6s indicates the same assistant usage triggered threshold twice.
2026-02-02T22:45:18.470Z compaction id 31d6859f tokensBefore 171992 parent 685f68df
2026-02-02T22:45:18.632Z compaction id 39121230 tokensBefore 2933 parent bfc4bdb2
- Second compaction fires ~0.16s later even after context was already reduced (tiny
tokensBefore).
Root Cause
_checkCompaction() doesn’t skip threshold-based compaction when the last assistant message predates the latest compaction entry. The kept-region message retains large usage and causes an immediate second compaction in the pre-prompt check.
prepareCompaction() only checks the last entry for compaction, so a trailing custom entry (e.g. openclaw.cache-ttl) bypasses the guard and allows compaction to run again.
Proposed Fix
- In
_checkCompaction(), find the latest compaction entry in the branch and return early for threshold compaction if assistantMessage.timestamp < lastCompaction.timestamp.
- In
prepareCompaction(), ignore trailing custom entries when deciding whether a compaction just occurred.
- Add a regression test that:
- Ensures pre-prompt threshold compaction is skipped when the last assistant message predates the latest compaction.
- Ensures
prepareCompaction() returns undefined if a cache-ttl custom entry follows a compaction.
Summary
Double compaction is triggered by two related issues:
_checkCompaction()runs both afteragent_endand again before the next prompt. The pre-prompt path uses the last assistant message’s originalusage(kept region), which can still exceed the threshold even after compaction.prepareCompaction()only blocks when the last entry iscompaction. When acustomentry (likeopenclaw.cache-ttl) is appended after compaction, the guard misses the prior compaction and allows another compaction immediately.Evidence (session JSONL)
Session:
/Users/clawd/.openclaw/agents/main/sessions/93259adc-570a-4234-9e69-81ab7b1ba744.jsonl2026-02-02T18:14:29.008Zcompaction idbd55f1c2tokensBefore170231parentf78129f32026-02-02T18:14:30.600Zcompaction id08b283f4tokensBefore170231parent96aed843tokensBeforewithin ~1.6s indicates the same assistant usage triggered threshold twice.2026-02-02T22:45:18.470Zcompaction id31d6859ftokensBefore171992parent685f68df2026-02-02T22:45:18.632Zcompaction id39121230tokensBefore2933parentbfc4bdb2tokensBefore).Root Cause
_checkCompaction()doesn’t skip threshold-based compaction when the last assistant message predates the latest compaction entry. The kept-region message retains largeusageand causes an immediate second compaction in the pre-prompt check.prepareCompaction()only checks the last entry forcompaction, so a trailingcustomentry (e.g.openclaw.cache-ttl) bypasses the guard and allows compaction to run again.Proposed Fix
_checkCompaction(), find the latest compaction entry in the branch and return early for threshold compaction ifassistantMessage.timestamp < lastCompaction.timestamp.prepareCompaction(), ignore trailingcustomentries when deciding whether a compaction just occurred.prepareCompaction()returnsundefinedif a cache-ttl custom entry follows a compaction.