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

416 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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而是**模块化的**
```typescript
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初始化
```typescript
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 调用后,系统获得实际使用数据:
```typescript
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 步:分析历史
```typescript
// 扫描所有消息,找出"能压缩的部分"
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 做总结
```typescript
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 步:替换
```typescript
// 替换前:
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** 的内容示例:
```markdown
# 我的知识库
## 工作偏好
- 不喜欢在 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 继续运行
在空闲时或定时触发 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.ts`Token 追踪逻辑
- `services/autoDream/autoDream.ts`Dream 实现
- `memdir/memdir.ts`Memory 目录管理
- `utils/messages/systemInit.ts`System prompt 组装
下一步:了解**Feature Gating 系统**,看看 Claude Code 怎么区分内部功能和公开功能。