claude-code/docs/agentic-design/05-context-and-memory.md
Claude 808d5a61b3
docs: Add comprehensive agentic design documentation (2.5-hour learning session)
新增 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
2026-03-31 16:07:29 +00:00

12 KiB
Raw Blame History

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 的答案是:

  1. System prompt 优化:区分静态和动态部分,利用 prompt cache
  2. 主动 compaction:在超限前压缩历史
  3. 长期 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 的重复传输   │缓存 hit10 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 180% 已用):显示警告,继续
  ↓
Agent 执行更多 tool  ← 看不到警告,继续工作
  ↓
Threshold 290% 已用):触发 compaction
  ← 在后台自动压缩,不中断 agent 工作
  ↓
Threshold 399% 已用):停止接受新请求
  ← 返回错误,等待用户决定

相关文件: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.tsMemory 目录管理

为什么自动化很关键?

手动维护:
  用户要记得更新 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 继续运行
  ↓
在空闲时或定时触发 dream24 小时一次)
  ↓
用户感觉不到延迟

代价:增加系统复杂性(需要处理并发)、需要文件锁(防同时 dream。但值得因为用户体验好得多。


常见误解

误解 1"Context window 满了就完蛋?"

实际:有多层缓冲和恢复机制。系统会逐步:

  1. 显示警告
  2. 触发 compaction
  3. 拒绝新操作
  4. 要求用户决定

完全"卡住"很少发生。

误解 2"Compaction 会丢失所有细节?"

实际Compaction 由 Claude 做,所以关键信息被保留。丢失的是:

  • 冗余的解释
  • 尝试失败的细节(对后续不相关)
  • 已解决的问题的讨论过程

误解 3"MEMORY.md 会无限增长?"

实际:维持在 ~200 行(可配置),通过 dream 的 prune 阶段定期清理过时信息。


关键要点

  1. System prompt 分两部分:静态(可缓存)+ 动态(每次变化)
  2. Token budget 主动追踪:到达 80% 时触发 compaction
  3. Compaction 自动压缩历史:用 Claude 总结 N 条消息为 1 条
  4. Memory 系统持久化知识MEMORY.md 最多 200 行,用户的长期知识库
  5. Auto-dream 后台巩固:每 24 小时自动更新 MEMORY.md无需用户介入
  6. 优化层级70% → 20% → 10% → 5% → 停止,每个阈值有对应策略

深入阅读

  • query/tokenBudget.tsToken 追踪逻辑
  • services/autoDream/autoDream.tsDream 实现
  • memdir/memdir.tsMemory 目录管理
  • utils/messages/systemInit.tsSystem prompt 组装

下一步:了解Feature Gating 系统,看看 Claude Code 怎么区分内部功能和公开功能。