🔥 claude code auto compact源码解析

触发与位置

每次模型请求前, query.ts 的治理顺序是:

rust 复制代码
Tool Result Budget
→ History Snip
→ Microcompact
→ Context Collapse
→ AutoCompact
→ 主模型 API 调用

见 query.ts:L396-L468 。

autoCompact 以"有效上下文窗口 - 13K buffer"为阈值。有效窗口还预留了最多 20K token 给"生成摘要的模型输出",避免摘要请求自己溢出。见 autoCompact.ts:L28-L90 。

它会在以下情况下跳过:配置关闭、摘要子 Agent 自己运行、Context Collapse 接管、Reactive Compact-only 模式等,见 autoCompact.ts:L160-L238 。

传统 AutoCompact 的执行

超过阈值后, autoCompactIfNeeded 先尝试 Session Memory 路径;不可用时才调用 compactConversation(...) 做传统全量摘要,见 autoCompact.ts:L287-L333 。

传统路径的关键点:

  1. 将当前窗口的 messages 全部交给专用 compact Agent。

  2. compact Agent 被强制只能输出文本,不能调用工具。

  3. Prompt 要它按固定结构输出:用户意图、技术概念、文件、错误、当前工作、待办、下一步等。

  4. 只是摘要 Agent 的草稿,写回上下文前会被剥离。

  5. 原消息被替换成:

    compact_boundary
    → compact_summary
    → 文件 / plan / skill / tool 等恢复附件
    → SessionStart hook 结果

对应 compact.ts:L440-L490 、 compact.ts:L596-L645 和 prompt.ts:L305-L374 。

compact_boundary 是明确的切断点;下次模型侧只取最近 boundary 之后的消息,见 messages.ts:L4614-L4655 。

为什么它不是逐轮归档

假设第 1 次压缩后: B1 → S1(覆盖 Turn 1~20)

随后又运行了 Turn 21~40。第 2 次传统 autoCompact 会做: compact(S1 + Turn 21~40) → B2 → S2

因此 S1 不会作为一个独立归档节点保留在模型上下文中,而是被重新吸收到 S2 。这是为了快速把整段前缀压到最小,而不是维持日志式摘要历史。

它仍保留了两个连续性保护:

  • 摘要中附带 transcript 路径,模型需要精确片段时可回读。
  • 压缩后明确要求模型"不问候、不复述、直接接续上一任务",见 prompt.ts:L345-L370 。

代码的 Session Memory 是更接近"结构化归档"的分支:

markdown 复制代码
    每隔一定上下文增长 / 工具调用次数
→ fork Agent 更新 session-memory.md
→ autoCompact 时直接把该 memory 当作摘要
→ 同时保留最近 10K~40K tokens 的原始消息 

其模板本身就是结构化的:

sql 复制代码
    Current State
    Task specification
    Files and Functions
    Workflow
    Errors & Corrections
    Codebase and System Documentation
    Learnings
    Key results
    Worklog

见 prompts.ts:L11-L41 。

Session Memory 会在上下文增量达到阈值、且工具调用或自然对话断点满足条件时更新,见 sessionMemory.ts:L134-L170 。更新 Agent 被要求只编辑这一个 memory 文件,保留既有标题与模板结构,并持续刷新 Current State ,见 prompts.ts:L43-L80 。

autoCompact 采用该路径时,会:

复制代码
B2
→ session-memory 结构化摘要
→ 最近未归档的原始消息(至少 10K token、至少 5 条文本消息,最多约 40K)

见 sessionMemoryCompact.ts:L316-L396 与 sessionMemoryCompact.ts:L459-L502 。

所以更准确地说:

一个关键纠正

图中"每轮独立记录、保留对话逻辑脉络"可以作为你希望的架构目标,但当前 CC 的实现并不严格保证"每轮独立"。Session Memory 的 Worklog 会被持续编辑和压缩,超过每节约 2K token或全局约 12K token 时,旧信息会被合并/删减,见 prompts.ts:L161-L195 。

一个具体的demo

我用一个具体的消息数组 demo 走一遍默认(传统)AutoCompact 的全过程。为保持可读性,token 数是示意值,模型用 Sonnet(200K 窗口)。

阶段 0:压缩前的上下文

假设第 N 轮开始时 messagesForQuery 是这样(已经过 Snip/Microcompact,没帮上忙):

阶段 1:阈值判定

shouldAutoCompact 用最后一条 assistant 的 usage 估算当前 token,减去 snip 释放量后跟阈值比较:

对应 autoCompact.ts:L72-L90 (你光标停的那行 return autocompactThreshold 就是返回这个 167K)和 autoCompact.ts:L225-L238 。

阶段 2:fork 一个「只会写字」的摘要 Agent

compactConversation 把 上面整段 messages + 一条摘要指令喂给一个分叉 Agent。这条指令消息长这样(节选自真实 prompt):

关键点:

  • 这个 fork 复用主对话的 prompt cache 前缀(省钱),见 compact.ts:L431-L443 。
  • canUseTool 硬性拒绝任何工具调用,逼它只吐文本。
  • 如果摘要请求本身又 prompt-too-long,会砍最旧的消息重试( truncateHeadForPTLRetry ),见 compact.ts:L450-L491 。

阶段 3:摘要 Agent 的回复

formatCompactSummary 会剥掉 、把
换成可读标题,见 prompt.ts:L311-L335 。

阶段 4:重建 messages(核心变身)

buildPostCompactMessages 按固定顺序拼出新数组,见 compact.ts:L330-L339 :

sql 复制代码
postCompactMessages = [
  // ① 边界标记(系统消息,切断点)
  { uuid: "B1", type: "system", subtype: "compact_boundary",
    compactMetadata: { trigger: "auto", preTokens: 187000 },
    logicalParentUuid: "a40" },

  // ② 摘要(伪装成一条 user 消息喂回模型)
  { uuid: "S1", type: "user", isCompactSummary: true,
    content: `This session is being continued from a previous conversation
              that ran out of context. The summary below covers...
              Summary:
              1. Primary Request and Intent: 重构 src/auth.ts...
              ...
              9. ...
              If you need specific details...read the full transcript at: /path/xxx.jsonl
              Continue the conversation from where it left off without asking...` },

  // ③ 恢复附件:把摘要里提到的文件重新读回来(最多 5 个 / 各 5K token)
  { uuid: "att1", type: "user", isMeta: true, content: "[src/auth.ts 最新内容]" },

  // ④ SessionStart hook 结果(如 CLAUDE.md 重新注入)
  { uuid: "h1", type: "user", isMeta: true, content: "[project memory]" }
]

之后主循环用这个新数组继续本轮请求( query.ts:L470-L535 )。原来的 40 轮消息在 模型侧 消失了。

阶段 5:下一轮模型看到什么

getMessagesAfterCompactBoundary 只取最后一个 compact_boundary 之后的内容,见 messages.ts:L4643-L4656 。所以模型的实际输入变成:

css 复制代码
[S1 摘要] + [att1 文件] + [h1 memory] + 新用户消息
≈ 8K token,而不是 187K

一句话对照你要的「demo 结论」

css 复制代码
压缩前:u1,a1,t1,...,a40           (187K, 40+ 条真实消息)
        │  fork 出「只写字」Agent,喂全量 + 摘要指令
        ▼
摘要:   <summary> 9 段结构化文本 </summary>
        │  buildPostCompactMessages 重排
        ▼
压缩后:[compact_boundary] + [summary(user)] + [文件附件] + [hook]   (~8K)

要注意的一点:这是 默认路径 。如果启用了实验性的 Session Memory 开关, autoCompactIfNeeded 会先走 trySessionMemoryCompaction ,用一份持续维护的结构化 session-memory.md 当摘要、并额外保留最近 10K--40K 的原始消息尾部( autoCompact.ts:L287-L310 ),那才更接近你上一轮图里「结构化归档」的形态。

相关推荐
王中阳Go1 小时前
用TRAE Work批量优化学员简历,原来2天的活现在2小时就干完了
后端·面试·agent
安逸sgr1 小时前
Zero-shot、Few-shot 和 One-shot Prompt 有什么区别?
人工智能·ai·大模型·agent·智能体
Bolt1 小时前
一个超级简单的 coding agent,100 行就可以做任何事
llm·agent·bun
孙启超3 小时前
【AI应用开发】Agent 无限 loop(反复调用同一个工具)如何规避?
人工智能·python·算法·llm·agent·loop·ai应用开发
JaydenAI3 小时前
[AG-UI详解-08]AG-UI客户端工具 V.S. LangChain的Headless工具
ai·langchain·agent·ag-ui·maf
IDIOT___IDIOT3 小时前
JSON-RPC 2.0 与 MCP 协议:从消息格式到进程通信 大模型MCP
rpc·大模型·json·agent·mcp
吃饱了得干活3 小时前
向量数据库 Milvus:从零搭建 RAG 向量数据库实战
数据库·langchain·agent
wangruofeng3 小时前
从 QQ 机器人到多租户 Agent 平台,LangBot 用一条流水线接住了 17 个 IM
aigc·agent·ai编程
大地之灯3 小时前
从零做一个自己的 CLI
python·agent