新增 docs/agentic-design/ 教育文档,全中文+英文技术术语,覆盖: - README.md: 总索引,整体架构图,阅读指南 - 00-codebase-tour.md: 代码库全景,递归覆盖所有重要目录 - 01-agent-loop.md: Query loop 状态机,token budget,compaction - 02-tool-system.md: Tool interface,权限检查,并发执行,sibling abort - 03-multi-agent-coordination.md: Agent 类型,coordinator 4 阶段,XML 通信 - 04-permission-system.md: Permission mode,rule system,YOLO 分类器,denial tracking - 05-context-and-memory.md: System prompt 分层,compaction,auto-dream 后台巩固 - 06-feature-gating.md: Compile-time feature,runtime flags,Bun dead-code elimination 总计 ~2.5 小时阅读,每篇含 Design Decision 专栏和常见误解纠正 https://claude.ai/code/session_017Vdqo9B8eTXiEDcqnv9DxM
12 KiB
Context 和 Memory:有限的脑容量
为什么这很重要?
Claude 的 context window(比如 200K tokens)是有限的。一个长时间运行的 agent task:
第 1 轮:用户输入(200 tokens)+ 响应(1000 tokens)
第 2 轮:新输入(300 tokens)+ 响应(2000 tokens)
...
第 100 轮:?
总 tokens: 200 + 300 + 400 + ... + 整个历史
在第 50 轮时:messages 数组有 100 条消息,超过 context window 的 80%
一旦超限,系统必须做些什么。Claude Code 的答案是:
- System prompt 优化:区分静态和动态部分,利用 prompt cache
- 主动 compaction:在超限前压缩历史
- 长期 memory:用户的持久化知识库
System Prompt 架构
不是简单的一个大 string,而是模块化的:
const systemPrompt = [
// 第 1 部分:静态,会被缓存
SYSTEM_PROMPT_PREFIX, // 关于 Claude Code 的基础指导
TOOL_DESCRIPTIONS, // 所有 tool 的描述
SYSTEM_PROMPT_SAFETY, // 安全指导
// 缓存边界 ← 这之后的部分会变化,不会被缓存
"SYSTEM_PROMPT_DYNAMIC_BOUNDARY",
// 第 2 部分:动态,每个 session 不同
USER_MEMORY, // 用户的个人知识库
RECENT_CONTEXT, // 最近的几轮对话(总结)
CURRENT_TASK, // 当前任务的上下文
]
Cache boundary marker 的妙处:
初始化时: │首个请求:
cache_key = MD5(prefix) │所有文本
缓存大小:10 KB │2000 tokens (40 KB)
│
│缓存命中 10 KB
│处理 30 KB 新文本
│总成本:40 KB 请求
│
同一 session 中再次使用: │再次请求:
cache 仍有效 │新文本 1000 tokens (20 KB)
直接跳过了 10 KB 的重复传输 │缓存 hit(10 KB)
成本:20 KB │总成本:20 KB 请求(节省 10 KB)
所以,通过把 system prompt 分为静态和动态两部分,我们:
- 减少了重复 token 消耗
- 加快了推理速度(缓存的部分直接使用)
- 提高了吞吐量(多个请求共享缓存)
相关文件:utils/messages/systemInit.ts, context.ts
Token Budget 生命周期
阶段 1:初始化
const CONTEXT_WINDOW = 200_000 // 比如 200K tokens
const SYSTEM_PROMPT_TOKENS = 5_000
const RESERVED_BUFFER = 10_000 // 总是留出来,防止意外超限
available = CONTEXT_WINDOW - SYSTEM_PROMPT_TOKENS - RESERVED_BUFFER
= 185_000 tokens // 工作预算
阶段 2:追踪使用
每一轮 API 调用后,系统获得实际使用数据:
response = await claude.messages.create({...})
remaining = available - response.usage.input_tokens - response.usage.output_tokens
if (remaining < available * 0.2) { // 还剩 20% 以下?
console.warn("Token budget 即将耗尽,触发 compaction")
}
if (remaining < 10_000) { // 紧急状态?
console.error("无法继续,context 严重超限")
return ERROR_OUT_OF_TOKENS
}
阶段 3:警告和恢复
Threshold 1(80% 已用):显示警告,继续
↓
Agent 执行更多 tool ← 看不到警告,继续工作
↓
Threshold 2(90% 已用):触发 compaction
← 在后台自动压缩,不中断 agent 工作
↓
Threshold 3(99% 已用):停止接受新请求
← 返回错误,等待用户决定
相关文件:query/tokenBudget.ts
Compaction:自动历史压缩
当 token 预算吃紧时,系统自动压缩历史。这是一个 4 步流程:
第 1 步:分析历史
// 扫描所有消息,找出"能压缩的部分"
messages = [
system_prompt, // 不能压缩
message_1, // 用户说:"帮我读个文件" → 不压缩(用户消息)
message_2, // Claude 说:"我读了,代码如下..." → 能压缩(输出很长)
message_3, // 用户说:"现在修复 bug" → 不压缩
message_4, // Claude 说:"修复完,改了 5 处..." → 能压缩
...
]
优先级:越早的消息优先级越高(假设最早的对话不再相关)
第 2 步:优先排序
对能压缩的消息排序:
1. 最早的长消息(4 messages ago)
2. 次早的长消息(6 messages ago)
3. ...
实际压缩的可能是:
messages 1-10(最早的 10 条)被替换为 1 条 summary
第 3 步:调用 Claude 做总结
const summary = await claude.messages.create({
messages: [
{ role: "user", content: "Summarize this conversation in 200 lines max:\n" + messagesText }
],
system: "You are a memory consolidation assistant..."
})
// 返回:
// "用户让我读了 package.json 和 tsconfig.json,发现项目是 TypeScript monorepo。"
第 4 步:替换
// 替换前:
messages = [msg_1, msg_2, ..., msg_100] // 100 条消息
// 替换后:
messages = [msg_summary, msg_51, msg_52, ..., msg_100]
// ^被压缩成1条 ^保留后面的消息(近期的)
结果:从 100 条消息减少到 51 条,token 预算恢复。
相关文件:query.ts 中的 compactMessages() 函数,大约 200 行
Memory 系统:长期知识库
Compaction 是临时的(压缩 session 内的历史)。但如果用户有多个 session,同一信息会被重复压缩。
解决方案:持久化 memory。
Memory 文件结构
~/.claude/memory/
├── MEMORY.md ← 用户的长期知识库(最多 200 行)
├── projects/
│ └── claude-code.md ← 项目特定的知识
├── patterns/
│ └── async-patterns.md ← 学到的模式
└── people/
└── team.md ← 关于人的信息
MEMORY.md 的内容示例:
# 我的知识库
## 工作偏好
- 不喜欢在 main 分支上直接提交
- 总是用 TypeScript,避免 JavaScript
## 项目信息
- Claude Code 仓库: ~/projects/claude-code
- 技术栈: TypeScript + React + Ink
## 编码习惯
- 函数 <100 行为最佳
- 避免 deep nesting,最多 3 层
- 总是加 error handling
Auto-Dream:后台内存巩固
不是用户手动维护 MEMORY.md,而是系统自动从 session 中提取和更新。
过程("dream"):
触发条件(三门齐全):
1. 时间门:距离上次 dream ≥ 24 小时?
2. 会话门:距离上次 dream,产生了 ≥ 5 个新 session?
3. Lock 门:能获得 consolidation lock(防并发)?
都满足 → 执行 dream:
[1] ORIENT
读取用户的 MEMORY.md
扫描最近 N 个 session 的 transcripts
[2] GATHER
提取新的学习点:
- "用户在这个 session 中学到了异步 Rust"
- "发现项目从 4.1 升级到了 4.2"
- "优化了 CI/CD,速度提升 30%"
[3] CONSOLIDATE
合并到 MEMORY.md:
- 如果已经有同类笔记,合并
- 如果是新的,添加
- 保持在 200 行以内(做优先级选择,删除过时的)
[4] PRUNE & INDEX
- 删除 24 小时内不用的 session transcript
- 更新搜索索引(便于后续 memory 查询)
相关文件:
services/autoDream/autoDream.ts:主逻辑(~11 KB)services/autoDream/consolidationPrompt.ts:告诉 Claude 怎么总结memdir/memdir.ts:Memory 目录管理
为什么自动化很关键?
手动维护:
用户要记得更新 MEMORY.md
→ 大部分人会忘记
→ Memory 逐渐变陈旧
自动化:
后台每 24 小时自动 consolidate
→ 用户无感
→ Memory 总是最新的
Context 使用的优化层级
当 context 变紧时,系统依次采取行动:
Tier 1(还有 70% token)
✓ 正常工作
✗ 触发自动 compaction
Tier 2(还有 20% token)
✓ 继续工作(使用压缩后的历史)
✗ 不接受新的大型工具输出(如读 10 MB 文件)
Tier 3(还有 10% token)
✓ 只允许低 token 成本的操作
✗ 禁止高 token 成本的操作
Tier 4(还有 < 5% token)
✗ 停止工作,返回错误
→ 用户可以:
a) 启动新 session(重置 token 预算)
b) 手动编辑 MEMORY.md,删除过时信息
Design Decision 专栏
为什么区分静态和动态 system prompt?
不区分(一大块):
发第 1 个请求:发 5000 tokens 的 system prompt + 输入
发第 2 个请求:又发 5000 tokens 的 system prompt + 输入
...
浪费了大量 token 在重复发送
区分(cache boundary):
发第 1 个请求:发 5000 tokens(静态)+ 1000 tokens(动态)
API 缓存这 5000 tokens
发第 2 个请求:直接用缓存的 5000 tokens + 1000 tokens(新的动态)
节省了 5000 tokens!
如果 session 很长(100 次请求),节省 = 5000 * 99 = 495K tokens!
为什么 Compaction 触发阈值是 20%,而不是 1%?
等到 1% 时才 compact:
已用 99%,只有 1% 剩余(~2000 tokens)
这时启动 compaction,自己就需要消耗大量 tokens(调用 Claude 做总结)
可能没有足够 tokens 来完成 compaction
→ 失败,系统崩溃
提前到 20% 时 compact:
已用 80%,还有 20% 剩余(~40K tokens)
这时启动 compaction,有充足 tokens 来做总结
完成后,token 预算恢复到 50%
→ 继续工作,很多时间都有富足的 token 可用
权衡:早点 compact 意味着多付出一些 token(压缩的成本),但换来系统稳定性。
为什么用后台 dream 而不是 inline compaction?
Inline(同步):
在 query loop 中调用 compaction
↓
等待 compaction 完成(5-10 秒)
↓
用户看到卡顿
后台 dream:
query loop 继续运行
↓
在空闲时或定时触发 dream(24 小时一次)
↓
用户感觉不到延迟
代价:增加系统复杂性(需要处理并发)、需要文件锁(防同时 dream)。但值得,因为用户体验好得多。
常见误解
误解 1:"Context window 满了就完蛋?"
实际:有多层缓冲和恢复机制。系统会逐步:
- 显示警告
- 触发 compaction
- 拒绝新操作
- 要求用户决定
完全"卡住"很少发生。
误解 2:"Compaction 会丢失所有细节?"
实际:Compaction 由 Claude 做,所以关键信息被保留。丢失的是:
- 冗余的解释
- 尝试失败的细节(对后续不相关)
- 已解决的问题的讨论过程
误解 3:"MEMORY.md 会无限增长?"
实际:维持在 ~200 行(可配置),通过 dream 的 prune 阶段定期清理过时信息。
关键要点
- System prompt 分两部分:静态(可缓存)+ 动态(每次变化)
- Token budget 主动追踪:到达 80% 时触发 compaction
- Compaction 自动压缩历史:用 Claude 总结 N 条消息为 1 条
- Memory 系统持久化知识:MEMORY.md 最多 200 行,用户的长期知识库
- Auto-dream 后台巩固:每 24 小时自动更新 MEMORY.md,无需用户介入
- 优化层级:70% → 20% → 10% → 5% → 停止,每个阈值有对应策略
深入阅读
query/tokenBudget.ts:Token 追踪逻辑services/autoDream/autoDream.ts:Dream 实现memdir/memdir.ts:Memory 目录管理utils/messages/systemInit.ts:System prompt 组装
下一步:了解Feature Gating 系统,看看 Claude Code 怎么区分内部功能和公开功能。