Skip to content

第 5 章:上下文治理 — Memory、CLAUDE.md 与 Compact 是预算制度 ​

出处

来源:原网页 · 状态:已读 · PDF:book1-claude-code.pdf p.42–48

一句话 ​

上下文是工作内存。治理它的目标是支持系统继续工作。 「信息越多越聪明」是危险神话。记住更多和治理记忆看起来相近,工程上是两种制度。Compact 是受控重启,不是聊天总结。

核心概念 ​

代理系统不是图书馆。上下文首先是一笔昂贵、易膨胀、会自我污染的预算。该加载什么、该截断什么、什么长期保留、什么只能短期摘要,全部是运行时要严肃治理的事。

CLAUDE.md:长期指令不能和临场对话混在一起 ​

claudemd.ts 把 instruction source 分层,按优先级和目录距离加载。离当前工作目录越近的 project 规则优先级越高;越私有、越本地的规则越晚加载,因而越靠近模型的注意力前沿:

  • managed memory:/etc/claude-code/CLAUDE.md
  • user memory:~/.claude/CLAUDE.md
  • project memory:项目根 CLAUDE.md、.claude/CLAUDE.md、.claude/rules/*.md
  • local memory:CLAUDE.local.md

团队规范、个人偏好、仓库约束的寿命远长于某一轮用户消息。全塞进聊天记录会在两个极端摇摆:每轮重复注入浪费上下文,或靠模型自己回忆迟早失手。答案是把稳定规则做成可发现、可分层、可组合的持久指令系统。支持 @include,但只允许明确列出的文本扩展名——提防二进制、巨型文档、不该进 prompt 的东西被糊里糊涂带进来。系统会先问:什么东西值得进入系统记忆,什么东西一旦进入就是污染。

MEMORY.md 是索引,不是日记本 ​

ENTRYPOINT_NAME = MEMORY.md,但不鼓励直接堆内容,它被定义为 index。保存 memory 两步:具体 memory 写进独立文件 → 在 MEMORY.md 里加一个一行指针。入口文件天然会被频繁加载,一旦变胖,整套上下文就会被慢慢拖死。

硬约束:MAX_ENTRYPOINT_LINES = 200、MAX_ENTRYPOINT_BYTES = 25_000。超过就 truncateEntrypointContent(),并在结尾警告:只加载了一部分,请把细节移到 topic files。入口一旦既当目录又当正文,最后就既不是目录也不是正文,只是一个谁都不愿再读第二遍的烂尾摘要。

原则:长期记忆必须分成「入口」和「正文」。入口负责低成本寻址,正文负责高密度承载。两者混为一谈,入口失效,整套记忆退化成摆设。

Session memory:短期连续性也不能靠聊天记录硬扛 ​

真正难受的常常是「这轮之前我们到底做到哪一步了」。SessionMemory/prompts.ts 用固定模板萃取可继续工作的操作说明书,不求完整复刻对话:

Current State / Task specification / Files and Functions / Workflow / Errors & Corrections / Codebase and System Documentation / Learnings / Key results / Worklog

更新纪律:只能用 Edit tool 更新 notes file;不要提 note-taking 本身;不要改模板结构;Current State 必须始终反映最近工作;每节信息密集但控制预算。预算:MAX_SECTION_LENGTH = 2000、MAX_TOTAL_SESSION_MEMORY_TOKENS = 12000。超过不夸你记得细,而是要求 aggressively condense,尤其优先保留 Current State 和 Errors & Corrections。工作内存的第一职责是可操作。

自动 compact:上下文治理首先是预算治理 ​

不管多会整理,对话够长总会逼近窗口边缘。getEffectiveContextWindowSize() 先把 context window 减去留给 summary 输出的预算(MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000)——系统先假定 compact 本身要花钱,绝不把窗口吃到只剩一口气才想起求生。再扣 AUTOCOMPACT_BUFFER_TOKENS = 13_000。警告阈值、错误阈值、手动 compact 预留空间也都各自分出 buffer。不留余地的系统平时像节俭,出事时才暴露:不过是把风险账单留给了下一轮。

AutoCompactTrackingState 记 compacted、turnCounter、turnId、consecutiveFailures。源码注释直白:全球每天曾经浪费大量 API calls 在连续失败的 autocompact 上,所以 MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3,再失败就 circuit breaker。可以失败,但不能无限次、无记忆地失败。

名称值作用
MAX_ENTRYPOINT_LINES200MEMORY.md 入口行数上限
MAX_ENTRYPOINT_BYTES25_000入口字节上限
MAX_SECTION_LENGTH2_000session memory 单节上限
MAX_TOTAL_SESSION_MEMORY_TOKENS12_000session memory 总预算
MAX_OUTPUT_TOKENS_FOR_SUMMARY20_000compact 预留输出空间
AUTOCOMPACT_BUFFER_TOKENS13_000autocompact 警戒缓冲
MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES3连续失败熔断

compactConversation():摘要要重建可继续工作的上下文 ​

Compact 不是「把前面聊天摘要一下」。真正做的是把原有上下文拆开、摘要、再注入必要附件,重新搭出一个还能工作的后-compact 世界。

压缩前清洗:stripImagesFromMessages() 把图片/文档替成标记;stripReinjectedAttachments() 把反正之后还要重新注入的 attachment 先剥掉。承认救火工具本身也会爆:truncateHeadForPTLRetry() 专门应对 compact 请求自己都 prompt too long。

成功之后不是只留一条 summary,还会:清空旧 readFileState;重新生成 post-compact file attachments;把 plan / plan mode / invoked skills / deferred tools / agent listing / MCP instructions 的 delta attachment 补回来;执行 session start hooks 和 post-compact hooks;写 compact boundary message,记录 pre-compact token 数与边界信息。

Compact 更像一次受控重启。 摘要只是中间产物。很多系统只做前半截,结果 compact 之后「还记得大概」,却已失去工具状态、计划状态、附件状态,还得再花几轮找回自己。

关键是保留工作语义 ​

真正在意的是把工作语义保住:恢复最近访问文件(构成当前工作面的局部现实);恢复 plan mode(否则可能忘了自己还处在 plan discipline 里);保留 invoked skills,但给每个 skill 设 token cap,避免 skill 在 post-compact 阶段反客为主。源码味道:per-skill truncation beats dropping——即使要裁,也优先保住开头那一段最关键指令。纯节流是砍;治理是知道该砍哪里、该保什么。

优先保留能维持行动语义的东西,而不是看起来信息量最大的东西。文件细节、当前计划、错误修正、技能约束直接决定下一步能不能做对。冗长历史对话、重复附件、运行时随时可以重新拿到的东西,没必要再占着座位。

关键洞察 ​

抽成工程原则:长期规则 / 长期记忆 / 会话连续性应分层,不混写;入口型记忆必须短小;session summary 服务「继续工作」,不是「回忆完整」;compact 是主路径,不是异常;压缩后必须保住运行语义。

源码证据:claudemd.ts 分层加载;memdir.ts 把 MEMORY.md 当索引并强行截断;SessionMemory/prompts.ts 固定模板 + section/总量预算;autoCompact.ts 预留输出预算、缓冲区和失败熔断;compact.ts 摘要后恢复计划、文件、技能、工具附件和 hook 状态。

个人思考 ​

这章直接解释了为什么本仓库的笔记模板强调「一句话结论」而不是把聊天记录贴进去:入口必须短,正文另放。Claude Code 自己的 MEMORY.md 协议和我们的 00-Overview 是同一类制度。下一章:治理碰到极限时怎么办——真系统终究会出错。

相关 ​

Copyright © 2026 openalon. All rights reserved.