Tiered mid-loop pruning, prefix-cache preservation, recoverable placeholders, and newest-first retentionMid-loop 分层裁剪、前缀缓存保护、可追溯占位符与最近优先保留机制
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。小窗口模型按比例压缩,大窗口模型按绝对工作集上限压缩。
There is one scaling exception. A fixed 80K trigger on a very large window makes a content-heavy workload compact every round or two, and compounding summaries drift. So when you have not set the limit yourself, the trigger scales with the model's window — clamped to a floor of 80K and a ceiling of 120K. The ceiling matters as much as the floor: letting context grow further before compacting means a single model switch or restart pays a very expensive cold cache write of that whole prefix. A value you configure explicitly is always honoured exactly and never scaled up.这里有一个缩放例外。在超大窗口上固定 80K 触发,会让内容密集的工作负载每一两轮就压缩一次,而摘要层层叠加会导致语义漂移。所以当你没有自己设定该阈值时,触发点会随模型窗口缩放——下限夹在 80K,上限夹在 120K。上限和下限同样重要:让上下文在压缩前涨得更高,意味着一次模型切换或重启就要为整段前缀付出极其昂贵的冷缓存写入。你显式配置的值则永远被原样尊重,绝不会被向上放大。
Compaction is not one monolithic event. It is a ladder — the cheapest, prefix-cache-preserving intervention fires first, while LLM summaries are deferred until strictly necessary.压缩不是单一的一刀切事件,而是一个阶梯——最廉价且保护前缀缓存的干预首先触发,耗时且重写前缀的 LLM 全文摘要则被推迟到必要时刻。
Fig 1.图 1. Tiered escalation: Phase-1 tool pruning happens inside the deferral gate before any LLM call.分层递进:Phase-1 工具结果裁剪直接在延迟门禁内部执行,优先于任何 LLM 摘要调用。
| Level层级 | Trigger触发点 | Cost代价 | What it does & Cache impact动作与 Cache 影响 |
|---|---|---|---|
| hygiene | 0.50 window | free (no LLM)免费(无 LLM) | Lightweight sweep before the turn begins. Prunes old tool_result bodies outside the last 10 messages so tool-heavy growth is arrested early.回合开始前的轻量清理。裁剪最近 10 条之外的旧 tool_result 正文,提前拦截工具结果体积的快速膨胀。 |
| mid-loop soft (tiered) | ≥ soft_limit (default 80K–120K) | free (Phase 1)免费(Phase 1) | Tiered Mid-Loop: runs Phase-1 tool pruning inside the turn-boundary deferral gate. Replaces historical tool results with recoverable placeholders in milliseconds. Defers prefix-breaking LLM summaries to the next entry unless hard limit is breached. Prefix cache hit remains 95%–98%.Mid-Loop 分层机制:在回合边界延迟门禁内部先执行 Phase-1 裁剪。毫秒级将历史工具输出替换为可追溯占位符;除非突破硬上限,否则将重写前缀的 LLM 摘要推迟至下一回合 Entry。前缀缓存命中率保持在 95%–98%。 |
| entry compaction | 0.78 window / deferred flag | 1 summarizer call1 次摘要调用 | Executes at the start of a turn when context exceeds limit or _force_entry_compact was flagged. Keeps recent active tail verbatim, summarizes older turns via claude-haiku-4.5, and attaches a compaction manifest.在回合开始(Entry)时触发,处理超限或由上一轮 mid-loop 延迟遗留的压缩任务。逐字保留活跃尾部,通过 claude-haiku-4.5 摘要更早的历史,并附上详细清单。 |
| hard limit (model-tiered) | slow: limit + 32K fast: min(0.92w, 2×limit) | forced inline强制当轮压缩 | Emergency break. Slow decoders (Opus, o1/o3, Pro-class) have a tight ceiling (limit + 32K, ≈112K) to prevent 4× per-round decode stalls. Fast models keep the wider band.最后防线。慢解码模型(Opus、o1/o3、Pro 系列)采用紧凑硬门槛(limit + 32K,约 112K),杜绝长循环下每轮 4 倍的解码延迟;快模型则保留更宽的缓冲带。 |
All thresholds are configurable in workspace/config/agent.yaml under compact.*. Slow model prefixes can be overridden via compact.slow_model_prefixes.所有阈值均可在 workspace/config/agent.yaml 的 compact.* 下配置。慢模型前缀名单可通过 compact.slow_model_prefixes 显式自定义。
All pruned, truncated, or compressed content must provide a deterministic pathway for the agent to find it again. If the agent cannot retrieve prior information, response quality collapses.所有被裁剪、截断或压缩的内容,都必须为 Agent 提供明确的二次找回路径。如果 Agent 想查阅早期信息却彻底找不到,回答质量会发生雪崩。
Three layers of traceability guarantee that no historical context is irrecoverably destroyed:三层可追溯体系确保没有上下文会被永久黑洞化:
| Mechanism机制 | Format / Location格式与位置 | How the Agent recovers itAgent 如何找回 |
|---|---|---|
| Recoverable Placeholder可追溯裁剪占位符 | Inline replacement for pruned tool_result: [tool_result pruned — N chars. Tool: X. Starts with: '...'. Recover via session_search...]行内替换旧的 tool_result: [tool_result pruned — N chars. Tool: X. Starts with: '...'. Recover via session_search...] | Names the exact tool, original character count, and head preview. Guides the agent to call session_search(scope='session') or read the referenced disk path.明确指明工具名、原始字符数与开头预览。引导 Agent 使用 session_search(scope='session') 搜回全文,或读取溢出文件。 |
| Strict Non-Growing Guard剪枝体积单调不增保证 | _shrinking_placeholder()_shrinking_placeholder() | Full placeholder (~350 chars) is used only if strictly shorter than content; falls back to compact variant (~90 chars) or keeps content verbatim. Pruning never inflates tokens.完整版占位符(约 350 字符)仅在严格短于原内容时采用;否则退回紧凑版(约 90 字符);若仍不缩减则原样保留。严防剪枝反向撑大上下文。 |
| Compaction Manifest压缩审计清单 | Appended to summary body: [COMPACTED SPAN — kind] N messages removed: assistant=X, user=Y Tool calls removed: bash×A, ... RECOVERY: session_search(...)追加在摘要消息末尾: [COMPACTED SPAN — kind] 删除了 N 条消息:assistant=X, user=Y 被清理的工具调用:bash×A, ... RECOVERY: session_search(...) | Pure-code inventory generated without LLM. Tells the agent exactly which tools and user directives were compressed, avoiding blind hallucinations or repeated executions.纯代码生成的确定性审计清单(不消耗 LLM)。清晰告知被压缩的工具列表与用户指令首尾摘要,避免 Agent 产生幻觉或盲目重复执行。 |
| Write-time Truncation Pointer写入期截断落盘引导 | <... N chars pruned at write time ... If a FULL DATA path is shown above, read_file it ...><... N chars pruned at write time ... If a FULL DATA path is shown above, read_file it ...> | Directs the agent to the persistent file on disk (/data/.starchild/tool_results/) rather than trying to reconstruct lost lines.直接引导 Agent 使用 read_file 打开磁盘上的持久化完整结果文件,不再靠猜。 |
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——多数供应商会直接报错。 |
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.裁剪陈旧工具输出零成本,且能在工具密集的回合彻底推迟摘要调用。 |
| Protect cache prefix over eager summary优先保护缓存前缀而非盲目摘要 | Mid-loop Phase-1 pruning maintains 95%+ prefix cache hits. Rewriting context mid-loop destroys the cache and stalls slow decoders.Mid-loop Phase-1 工具裁剪能保住 95%+ 的前缀缓存命中率;中途重写全文摘要会击穿缓存并导致慢解码模型严重卡顿。 |
| Never a dead end被裁剪内容绝不能成为死胡同 | Every pruned block must carry its tool identity, length, preview, and retrieval method (session_search).每个被裁剪块都必须携带工具标识、大小预览及明确的召回方式(session_search)。 |
| Pruning must never grow context裁剪绝不能反向撑大体积 | Placeholders must be strictly smaller than original contents, falling back to compact forms or verbatim retention.占位符必须严格短于原始内容,必要时退回紧凑版或保留原文,杜绝负优化。 |
| Model-tiered hard limits硬上限按模型分级 | Slow frontier decoders (Opus, o1/o3, Pro) need tight ceilings (limit+32K) to avoid unbounded multi-second token latency.慢速主力模型(Opus、o1/o3、Pro 系列)需要更严格的硬上限(limit+32K)以防止多轮循环出现十几秒的解码卡顿。 |
| Budget over count预算优于条数 | Message count is uncorrelated with token cost.消息条数与 token 成本不相关。 |
| Fail to replay, not to noise失败回退到重放而非噪声 | A bad summary is worse than no summary, and it fails silently.坏摘要比没有摘要更糟,而且它静默失败。 |