尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【Harness Engineering】05_上下文治理:Memory、CLAUDE.md 与 Compact 是预算制度

【Harness Engineering】05_上下文治理:Memory、CLAUDE.md 与 Compact 是预算制度 源码地址GitHub - wquguru/harness-books: Two books on harness engineering — the design philosophies behind Claude Code Codex: constraints, query loops, context governance, multi-agent verification. harness-books.agentway.dev · GitHub一、信息越多 ≠ 系统越聪明1.1 一个低级幻觉信息越多系统越聪明是一个危险的神话。代理系统不是图书馆上下文不是存进去就算拥有的仓库它首先是一笔昂贵、易膨胀、会自我污染的预算。Claude Code 在这件事上很不浪漫该加载什么、该截断什么、什么东西要长期保留、什么东西只能短期摘要全部是运行时要严肃治理的事。这一章要讨论的是Claude Code 怎样防止自己被记住的东西拖死。记住更多和治理记忆看起来相近工程上是两种制度。二、CLAUDE.md 体系长期指令不能和临场对话混在一起2.1 分层记忆架构Claude Code 在src/utils/claudemd.ts开头就把记忆层次说得很清楚。它把 instruction source 分成几层层级位置用途managed memory/etc/claude-code/CLAUDE.md系统级管理记忆user memory~/.claude/CLAUDE.md用户个人偏好project memory项目根目录CLAUDE.md、.claude/CLAUDE.md、.claude/rules/*.md仓库级约束local memoryCLAUDE.local.md本地私有规则2.2 优先级规则这些文件会按优先级和目录距离加载。离当前工作目录越近的 project 规则优先级越高越偏向私有、越偏向本地的规则越晚加载因而越靠近模型的注意力前沿。这件事特别要紧。因为它说明 Claude Code 从一开始就拒绝把长期协作规则和本轮临时对话混成一锅粥。团队规范、个人偏好、仓库约束这些东西的寿命远长于某一轮用户消息如果把它们全都塞进聊天记录里系统就会在两个极端之间摇摆要么每轮都重复注入浪费上下文要么靠模型自己回忆迟早失手。2.3 include 机制与克制claudemd.ts给出的答案是把这些稳定规则做成可发现、可分层、可组合的持久指令系统。还有个细节很有意思它支持include并且只允许一大批明确列出的文本扩展名。这说明工程师除了追求 include 的便利也在提防另一种常见事故有人把二进制、巨型文档、甚至不该进 prompt 的东西糊里糊涂带进来了。这是正经工程师才有的克制。系统会先问什么东西值得进入系统记忆什么东西一旦进入就是污染。三、MEMORY.md 是索引不是日记本3.1 入口文件 索引如果CLAUDE.md管的是规则层那么memdir处理的就是另一类更细的长期记忆。src/memdir/memdir.ts里有一段设计很值得反复看ENTRYPOINT_NAME被定义成MEMORY.md但这个文件并不被鼓励用来直接堆内容它被定义为index。源码里写得很实在。buildMemoryLines()明确告诉模型保存 memory 是两步把具体 memory 写进独立文件再在MEMORY.md里加一个一行指针3.2 为什么非要这么麻烦因为系统知道入口文件天然会被频繁加载而频繁加载的东西一旦变胖整套上下文就会被它慢慢拖成一个不好收拾的胖子。这也是为什么memdir.ts里专门有MAX_ENTRYPOINT_LINES 200和MAX_ENTRYPOINT_BYTES 25_000。超过了系统会直接truncateEntrypointContent()并在结尾追加明确警告只加载了一部分请把细节移到 topic files。3.3 硬约束的价值这套做法特别像一个见过太多失控索引的人。它不相信大家会天然克制所以把入口必须短做成硬约束。因为入口文件一旦既当目录又当正文最后就既不是目录也不是正文只是一个谁都不愿再读第二遍的烂尾摘要。从 Harness Engineering 的角度看这里抽出来的原则非常清楚长期记忆必须分成入口和正文。入口负责低成本寻址正文负责高密度承载。把两者混为一谈最终一定是入口失效随后整套记忆系统退化成摆设。四、Session Memory短期连续性也不能靠聊天记录硬扛4.1 会话连续性问题只有长期 memory 还不够。代理系统真正难受的地方常常在于这轮之前我们到底做到哪一步了。这是一次会话内部的连续性问题。4.2 结构化模板Claude Code 在src/services/SessionMemory/prompts.ts里专门给这件事建了一套模板。默认模板里有这些栏目栏目作用Current State当前做到哪了Task specification任务是什么Files and Functions改过哪些文件Workflow工作流程Errors Corrections踩过什么坑Codebase and System Documentation代码库相关信息Learnings学到了什么Key results关键结果Worklog工作日志你一看就知道这不是给人抒情的。它关心的是现在做到哪了踩过什么坑改过哪些文件后面该接什么。4.3 更新纪律更有意思的是更新 prompt 的语气。源码里明确要求只能用 Edit tool 更新 notes file不要提 note-taking 这件事本身不要改模板结构Current State必须始终反映最近工作每节都要信息密集但要控制预算这说明 session memory 在 Claude Code 里并非另存一份聊天记录它会把当前会话萃取成一种可继续工作的操作说明书。它不求完整复刻对话而求压缩出未来继续干活所必需的骨架。4.4 预算硬约束这里有个极其工程化的细节。prompts.ts里定义了MAX_SECTION_LENGTH 2000和MAX_TOTAL_SESSION_MEMORY_TOKENS 12000。超过预算系统不会夸你记得细而是要求你有针对性的浓缩aggressively condense尤其优先保留Current State和Errors Corrections。这很能说明问题。真正成熟的系统会把为继续工作保留最有用的部分当成美德。因为上下文预算是工作内存。工作内存的第一职责是可操作。五、自动 Compact上下文治理首先是预算治理5.1 承认膨胀是必然到这里长期规则、持久 memory、session memory 都有了但上下文还是会膨胀。于是 Claude Code 在src/services/compact/autoCompact.ts里进一步承认一个现实不管你多会整理只要对话够长总会逼近窗口边缘。5.2 为失败预留空间getEffectiveContextWindowSize()先把模型 context window 减去一笔保留给 summary 输出的预算。MAX_OUTPUT_TOKENS_FOR_SUMMARY直接预留了 20,000 tokens。也就是说系统先假定 compact 本身要花钱绝不把窗口吃到只剩一口气时才想起求生。接着getAutoCompactThreshold()又在有效窗口上再扣掉AUTOCOMPACT_BUFFER_TOKENS 13_000。警告阈值、错误阈值、手动 compact 预留空间也都各自分出 buffer。这套数字背后有个很朴素的道理上下文治理需要提前为失败和恢复留出余地。不留余地的系统平时看着像节俭出事时才暴露真相——不过是把风险账单留给了下一轮。5.3 预算阈值表名称值作用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连续失败熔断阈值5.4 熔断机制更有意思的是AutoCompactTrackingState。它不仅记compacted还记turnCounter、turnId和consecutiveFailures。这说明 autocompact 是一段会被追踪、会失败、会被限流的运行时行为。源码甚至写了一个很直白的注释全球每天曾经浪费大量 API calls 在连续失败的 autocompact 上所以MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES 3再失败就触发circuit breaker。这里的气质非常好像一个终于受不了浪费的人你可以失败但不能无限次、无记忆地失败。六、Compact 的目标是重建可继续工作的上下文6.1 不是简单摘要很多人一听 compact会以为就是把前面聊天摘要一下。Claude Code 的实现要复杂得多。src/services/compact/compact.ts里的compactConversation()真正做的是把原有上下文拆开、摘要、再注入必要附件重新搭出一个还能工作的后 compact 世界。6.2 压缩前的清洗先看压缩前的清洗stripImagesFromMessages()会把图片、文档替成[image]、[document]之类的标记stripReinjectedAttachments()会把反正之后还要重新注入的 attachment 先剥掉免得浪费 token仅这两个动作就说明compact 会有选择地丢掉那些对摘要没用、但 token 开销极大的部分。6.3 救火工具也会爆再看摘要失败时的处理。源码里有truncateHeadForPTLRetry()专门应对compact 请求自己都 prompt too long的尴尬场面。也就是说系统不仅承认主流程会爆还承认救火工具本身也会爆。这很像真实世界而不是 demo。6.4 压缩后的重建工作而在 compact 成功之后Claude Code 做的不是简单保留一条 summary。它还会清空旧的readFileState已读取文件的状态缓存因为这些文件内容在压缩后需要重新按需加载而不是继续占用旧缓存重新生成 post-compact file attachments压缩后的文件附件把当前工作相关的文件内容重新挂载到上下文中把 plan attachment计划附件补回来确保模型还记得当前正在执行的计划把 plan mode attachment计划模式附件补回来确保模型记得自己还处于计划模式下需要遵守计划纪律把 invoked skills attachment已调用的技能附件补回来让模型记得哪些技能已经被激活同时给每个技能设置 token 上限防止技能本身反客为主把 deferred tools延迟工具、agent listing代理列表、MCP instructionsMCP 指令的 delta attachment增量附件重新补回来执行 session start hooks会话启动钩子和 post-compact hooks压缩后钩子让外部扩展能感知到压缩事件并做相应处理写 compact boundary message压缩边界消息记录 pre-compact token 数压缩前的 token 数量与边界信息方便追踪上下文压缩的历史这些动作合在一起意思很明确compact 的目标是把继续干活所需的运行时环境重新铺平。摘要只是中间产物不是最终目的。所以 compact 在 Claude Code 里更像一次受控重启而不是一次聊天总结。旧上下文会被转译成新的工作底座。这种设计很值得记住因为很多系统只做前半截结果 compact 之后虽然还记得大概却已经失去了工具状态、计划状态、附件状态接下来还得再花几轮找回自己。七、上下文治理的关键保留工作语义7.1 保住什么比砍掉什么更重要如果只看compact.ts的后半段会发现一个贯穿始终的倾向Claude Code 真正在意的是把工作语义保住。例如它会恢复最近访问文件的 attachment附件因为这些文件往往构成当前工作面的局部现实它会恢复 plan mode因为否则模型压缩完以后可能忘了自己还处在 plan discipline 里它会保留 invoked skills 的内容但又给每个 skill 设置 token cap避免 skill 本身在 post-compact 阶段反客为主源码里这句话很有味道per-skill truncation beats dropping。意思是即使要裁也优先保住开头那一段最关键指令而不是整个扔掉。这就是治理不是纯粹节流。纯节流是砍治理是知道该砍哪里、该保什么。7.2 优先级判断从这里可以抽出一个相当稳妥的经验上下文系统应该优先保留能维持行动语义的东西而不是优先保留看起来信息量最大的东西。文件细节、当前计划、错误修正、技能约束这些都直接决定下一步能不能做对。反过来冗长的历史对话、重复出现的附件、运行时随时可以重新拿到的东西就没必要再占着座位。八、总结8.1 核心原则这一章可以归纳成一句话上下文是工作内存。治理它的目标是支持系统继续工作。8.2 五个源码位置指向同一结论源码位置结论claudemd.ts长期指令分层加载稳定规则要和临时对话分开治理memdir.tsMEMORY.md是索引并强行截断入口必须短而可寻址SessionMemory/prompts.ts用固定模板提炼会话连续性对 section 和总量设预算autoCompact.ts为 compact 预留输出预算、缓冲区和失败熔断compact.ts摘要后恢复计划、文件、技能、工具附件和 hook 状态8.3 最终结论抽成工程原则长期规则 / 长期记忆 / 会话连续性应分层不混写入口型记忆必须短小session summary 服务继续工作不是回忆完整compact 是主路径不是异常压缩后必须保住运行语义。
返回列表