claw-code 源码详细分析:Compaction 前置课——上下文压缩在接口层要预留哪些旋钮,避免后期全局返工?

张开发
2026/4/6 18:24:08 15 分钟阅读

分享文章

claw-code 源码详细分析:Compaction 前置课——上下文压缩在接口层要预留哪些旋钮,避免后期全局返工?
涉及源码Pythonsrc/query_engine.py、src/transcript.py对照 Rustrust/crates/runtime/src/compact.rs与ConversationMessage会话模型。1. 为什么要「接口层先留旋钮」上下文压缩compaction在工程上一定会演进从截断 → 按 token 触发 → 模型摘要 → 分层记忆 → 外部向量库。若早期把逻辑写死在「字符串列表 pop」里后面每升一级都要改调用点散落各处无法统一观测「何时压、压掉什么、压完 token 多少」持久化 / 重放与内存结构绑死迁移成本爆炸测试无法注入策略只能集成测大段流程。正确姿势是在公开 API 上固定「策略输入Config 生命周期钩子何时调用 结果对象Result」实现可以先是 no-op 或 tail-truncate但边界不动。2. Python 移植层已经埋下的旋钮与钩子2.1 配置项QueryEngineConfig.compact_after_turns# 15:21:src/query_engine.pydataclass(frozenTrue)classQueryEngineConfig:max_turns:int8max_budget_tokens:int2000compact_after_turns:int12structured_output:boolFalsestructured_retry_limit:int2意义把「窗口大小」从代码魔法数提升为可注入配置便于测试与 CLI/环境覆盖。注意默认max_turns8小于compact_after_turns12在不做配置覆盖时往往先触达轮次上限压缩逻辑很少被执行——这是移植期优先级取舍也提醒多个闸门要一起在配置里审。2.2 生命周期钩子每轮成功后compact_messages_if_needed()# 91:96:src/query_engine.pyself.mutable_messages.append(prompt)self.transcript_store.append(prompt)self.permission_denials.extend(denied_tools)self.total_usageprojected_usage self.compact_messages_if_needed()意义压缩或未来摘要的触发点钉在「一轮提交完成、状态已更新」之后而不是散落在 UI 或网络回调里——后续换实现只需改方法体或委托给CompactionEngine。2.3 双缓冲一致性mutable_messages与TranscriptStore# 129:132:src/query_engine.pydefcompact_messages_if_needed(self)-None:iflen(self.mutable_messages)self.config.compact_after_turns:self.mutable_messages[:]self.mutable_messages[-self.config.compact_after_turns:]self.transcript_store.compact(self.config.compact_after_turns)# 15:17:src/transcript.pydefcompact(self,keep_last:int10)-None:iflen(self.entries)keep_last:self.entries[:]self.entries[-keep_last:]意义接口层已经承认「会话里不止一种载体」此处两条线同步截断。将来若引入「摘要 system 消息 原文 recent」也要在同一钩子里更新所有衍生视图避免 persist/replay 分叉。2.4 可观测message_stop带transcript_size# 122:127:src/query_engine.pyyield{type:message_stop,usage:{input_tokens:result.usage.input_tokens,output_tokens:result.usage.output_tokens},stop_reason:result.stop_reason,transcript_size:len(self.transcript_store.entries),}意义为运维/前端预留压后尺寸信号后续可扩展compacted: bool、removed_turns等而不改事件主类型。3. Python 当前实现的边界避免误当「产品级 compaction」策略纯尾部截断无摘要、无 role 区分、无工具结果特判。触发条件仅消息条数 compact_after_turns无真实 token、无软预算。产物不生成独立 summary 块不写入「continuation」系统提示与 Rust 侧对比见下。持久化StoredSession只存截断后的messages见result/06.md无法从磁盘恢复被截断内容。这些在移植期合理返工风险在于若业务代码直接依赖「只有 user 字符串列表」将来要加ConversationMessage { role, blocks }时会痛。旋钮上已经预留config 单钩子下一步是把mutable_messages的元素类型抽象成统一 Message而不是到处传str。4. Rust runtime更完整的「旋钮组」参考同仓库 Rust 实现里CompactionConfig与should_compact/compact_session展示了接口层应暴露的另一维度#8:21:rust/crates/runtime/src/compact.rs#[derive(Debug, Clone, Copy, PartialEq, Eq)]pubstructCompactionConfig{pubpreserve_recent_messages:usize,pubmax_estimated_tokens:usize,}implDefaultforCompactionConfig{fndefault()-Self{Self{preserve_recent_messages:4,max_estimated_tokens:10_000,}}}#37:47:rust/crates/runtime/src/compact.rspubfnshould_compact(session:Session,config:CompactionConfig)-bool{letstartcompacted_summary_prefix_len(session);letcompactablesession.messages[start..];compactable.len()config.preserve_recent_messagescompactable.iter().map(estimate_message_tokens).sum::usize()config.max_estimated_tokens}与 Python 对照旋钮PythonQueryEngineConfigRustCompactionConfig保留最近 K 条compact_after_turns截断窗口 Kpreserve_recent_messages按 token 触发无仅有max_budget_tokens用于 stop_reasonmax_estimated_tokens压缩产物无摘要CompactionResult { summary, formatted_summary, compacted_session, removed_message_count }续写语义无get_compact_continuation_message注入 system 前文学习点产品级 compaction 通常要同时暴露「保留窗口」与「触发阈值token/字符」仅条数一项不足以避免浪费上下文或过早压碎长工具输出。5. 接口层建议预留的旋钮清单防全局返工下列项不必第一天全实现但建议在类型与配置结构上留位或文档契约上承诺避免日后改签名。5.1 触发Whenpreserve_recent_messages/keep_last_turns永远原样保留的尾部轮次或消息数。max_context_tokens/compact_threshold_tokens估算 token 超阈再压Rust 已有雏形。min_messages_before_compact避免极短会话反复压。cooldown_turns压完 N 轮内不再压防止震荡。手动触发斜杠命令/compact、APIcompact_now()Rust CLI/commands 已接线。5.2 策略WhatCompactionStrategy枚举或 traitTailDrop|SummarizeModel|Hybrid|ExternalMemory便于 A/B。可插入的summarize_fn输入被移除消息区间输出 summary 文本 可选结构化「决策/待办」。角色策略user/assistant/tool/system是否可进入摘要、工具结果是否单独截断。Pinned messages用户或系统钉住的条目不参与压缩。5.3 产物与提示How it affects the modelSummary 消息角色通常system或专用context槽避免与 user 混淆Rust 用MessageRole::System包 continuation。Continuation 文案可配置如COMPACT_DIRECT_RESUME_INSTRUCTION类常量便于多语言与产品调性。多次压缩合并merge_compact_summaries避免摘要无限膨胀Rust 已实现层级合并思路。5.4 一致性与持久化CompactionResult式结果至少包含removed_count、new_token_estimate、compacted_session或等价快照便于日志与测试断言。版本号session.version或 schema便于迁移RustSession带 version 字段方向。原子写压完再save避免半压状态落盘。5.5 可观测与合规事件compaction_started/compaction_finished/compaction_skipped含 reason。审计谁触发、用了哪版策略、摘要是否含 PII脱敏钩子。与max_turns/ budget 关系文档化优先级谁先触发、是否重置计数。5.6 测试钩子纯函数should_compact(session, config)Rust 已拆出Python 可对照抽取便于表驱动单测。固定随机种子 / fixture 会话对compact_session做 golden file。6. 小结claw-codePython已在QueryEngineConfig 每轮末尾compact_messages_if_needed上做了最小正确预埋配置可调、触发点单一、双缓冲同步、流式里带transcript_size。缺口是触发维度单一仅条数、无摘要与续写语义、消息模型过窄str——接产品前应优先抽象 Message 与 CompactionResult并参考RustCompactionConfig/should_compact/compact_session补全 token 与产物旋钮。避免全局返工的核心不是一次写全功能而是把「何时压、压什么、压完长什么样、如何持久化与观测」变成稳定 API 面实现可从 tail-truncate 平滑升级到模型摘要。

更多文章