Three trigger levels, newest-first retention, and the guards that keep a summary from eating the conversation三级触发、最近优先保留,以及防止摘要吞掉对话的守卫层
A long tool loop grows the prompt on every turn. Without intervention the agent bills 500K–800K-token prompts before it hits any wall.长工具循环每一轮都在撑大 prompt。不干预的话,Agent 会在撞墙之前反复以 50–80 万 token 的 prompt 计费。
Two constraints have to hold at once. Capability ceiling: never exceed the model's context window. Working-set ceiling: never carry more context than the task actually needs, regardless of how big the window is. A 1M-context model that respects only the first constraint is an expensive way to be wrong.两个约束必须同时成立。能力上限:不能超过模型上下文窗口。工作集上限:不管窗口多大,都不该携带超出任务所需的上下文。只守第一条的 1M 窗口模型,只是把错误变得更贵。
So the effective limit is min(compact.max_tokens, window × ratio) — 80K by default. Small-window models compact by ratio; large-window models compact at the absolute working-set limit.因此有效阈值是 min(compact.max_tokens, window × ratio),默认 80K。小窗口模型按比例压缩,大窗口模型按绝对工作集上限压缩。
Compaction is not one event. It is a ladder — the cheapest intervention fires first.压缩不是单一事件,而是一个阶梯——最便宜的干预先触发。
Fig 1.图 1. Escalating interventions as a fraction of the model's context window.按上下文窗口占比递进的干预层级。
| Level层级 | Ratio比例 | Cost代价 | What it does做什么 |
|---|---|---|---|
| hygiene | 0.50 | free免费 | No-LLM pass. Prunes old tool_result bodies outside the last 10 messages. Runs once before the tool loop so tool-heavy growth is caught early.无 LLM 清理。裁剪最近 10 条之外的旧 tool_result 正文。在工具循环前跑一次,提前吃掉工具输出的膨胀。 |
| entry / mid-loop | 0.78 | 1 summarizer call1 次摘要调用 | Real compaction: keep the recent tail verbatim, summarize everything older. Entry = before the turn; mid-loop = inside a long tool loop.真正的压缩:逐字保留近期尾部,把更早的内容摘要。entry 在回合前触发,mid-loop 在长工具循环内触发。 |
| hard limit | 0.92 | forced强制 | Last line of defence — compaction happens whether or not the soft trigger already ran this turn.最后防线——无论本回合软触发是否已跑过,都强制压缩。 |
All three are overridable in workspace/config/agent.yaml under compact.*. The summarizer runs on a cheap dedicated model (anthropic/claude-haiku-4.5), never on the conversation model.三者都可在 workspace/config/agent.yaml 的 compact.* 下覆盖。摘要器跑在专用的廉价模型(anthropic/claude-haiku-4.5)上,绝不占用对话模型。
The old design kept a fixed count of recent messages. The current one keeps a fixed token budget, walking newest → oldest.旧设计保留固定条数的近期消息,现设计保留固定token 预算,从新到旧回溯。
Why the change: message count is a terrible proxy for size. Ten screenshots and ten one-liners are both "10 messages" but differ by two orders of magnitude in tokens. The budget (compact.retention_budget_tokens, default 64K, clamped to 80% of max_tokens) makes retention size-aware.为什么要改:消息条数完全不能代表体积。十张截图和十句短话都是「10 条」,token 差两个数量级。预算制(compact.retention_budget_tokens,默认 64K,且被夹到 max_tokens 的 80%)让保留策略对体积敏感。
# walk newest → oldest until budget exhausted keep verbatim msg[n], msg[n-1], … while budget remains keep truncated first over-budget message # never dropped outright summarize everything older than the boundary # invariants - boundary never splits a tool_use / tool_result pair (moves back) - messages containing images or tool blocks are never truncated - images cost a flat 1,100 tokens each, not their byte size - post-compaction sweep strips orphan tool_results
The truncate-don't-drop rule matters: the message straddling the boundary usually holds the user's actual instruction. Dropping it loses the task; truncating it keeps the intent at a fraction of the cost.「截断而非丢弃」很关键:跨越边界的那条消息往往正是用户的实际指令。丢掉它就丢了任务,截断则以极小代价保住意图。
The flat image cost is a similar piece of realism. A base64 screenshot is enormous as text but roughly constant as tokens, so charging it by byte size would evict a whole conversation to make room for one picture.图片按固定 token 计价同理。base64 截图作为文本极其庞大,但作为 token 大致恒定;按字节计价会为了一张图把整段对话赶出去。
Compaction replaces real history with generated text. If that text is junk, the failure is silent and the conversation is already gone.压缩用生成文本替换真实历史。如果这段文本是垃圾,失败是静默的,而对话已经没了。
| Failure故障 | Observed实际观测 | Guard守卫 |
|---|---|---|
| Placeholder占位符 | External writers set "Generating…" into the summary field while the real one is still in flight.外部写入方在真实摘要生成中时,把 "Generating…" 写进摘要字段。 | Regex whitelist of placeholder shapes, matched against the whole stripped string — a summary that merely mentions "loading" is still valid.对整串(strip 后)做占位符正则匹配——仅在正文中提到「loading」的摘要仍然有效。 |
| Marker echo标记回显 | A model returned an 11-token output that was only the END-OF-SUMMARY marker — silently replacing ~23K tokens of history with nothing.某模型只输出了 11 token —— 全是 END-OF-SUMMARY 标记本身,静默地把约 23K token 的历史替换成了空。 | Normalize first: strip the echoed marker and any wrapping code fence, then length-check. Marker-only collapses to empty and is rejected.先归一化:剥掉回显的标记与包裹的代码围栏,然后再做长度检查。只有标记的输出会塌缩为空并被拒绝。 |
| Too short过短 | Any output below the section-scaffold floor is not a summary.低于分节骨架下限的输出不可能是摘要。 | 50-char minimum — comfortably above every plausible placeholder, far below a real summary.50 字符下限——远高于任何合理占位符,远低于真实摘要。 |
| Orphan pairs孤儿配对 | A tool_result whose tool_use was summarized away — most providers hard-error on this.对应 tool_use 已被摘要掉的 tool_result——多数供应商会直接报错。 | Post-compaction pairing sweep, in message order, strips unmatched results.压缩后按消息顺序做配对清扫,剥掉未配对的结果块。 |
Every rejection falls back to full message replay rather than poisoning the context. Losing the cost saving is recoverable; feeding the model noise labelled "[Previous conversation summary]" is not.任一拒绝都回退到完整消息重放,而不是污染上下文。省不下钱可以接受;把噪声贴上「[Previous conversation summary]」标签喂给模型不可接受。
| Principle原则 | Why理由 |
|---|---|
| Cheapest intervention first最便宜的干预优先 | Pruning stale tool output costs nothing and defers the summarizer call entirely on tool-heavy turns.裁剪陈旧工具输出零成本,且能在工具密集的回合彻底推迟摘要调用。 |
| Two ceilings, not one两个上限而非一个 | Window ratio alone lets 1M-context models burn money legally.只看窗口比例,会让 1M 上下文模型「合法地」烧钱。 |
| Budget over count预算优于条数 | Message count is uncorrelated with token cost.消息条数与 token 成本不相关。 |
| Truncate, don't drop截断而非丢弃 | The boundary message usually carries the live instruction.边界消息通常承载着当前指令。 |
| Fail to replay, not to noise失败回退到重放而非噪声 | A bad summary is worse than no summary, and it fails silently.坏摘要比没有摘要更糟,而且它静默失败。 |