mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
- Add Computer Use architecture deep dive documentation (CN/EN) with 4 generated diagrams (architecture, security gates, Python Bridge, patch environment comparison) - Move Computer Use section below Skills in sidebar and README - Fix 153 broken anchor links across 18 documentation files: - CN docs: add missing `、` after Chinese numeral prefixes - EN docs: add missing `_` prefix for number-starting heading IDs - Fix em-dash and special character encoding in anchor hrefs
473 lines
17 KiB
Markdown
473 lines
17 KiB
Markdown
# Claude Code 记忆系统 — 实现原理
|
||
|
||
> 从系统提示词注入到后台自动提取,拆解记忆系统的每一个技术细节。
|
||
|
||
<p align="center">
|
||
<a href="#一、整体架构">整体架构</a> · <a href="#二、路径解析系统">路径解析</a> · <a href="#三、系统提示词注入">提示词注入</a> · <a href="#四、自动记忆提取">自动提取</a> · <a href="#五、智能记忆检索">智能检索</a> · <a href="#六、记忆扫描详解">记忆扫描</a> · <a href="#七、代理记忆-agent-memory">代理记忆</a> · <a href="#八、团队记忆同步">团队同步</a> · <a href="#九、关键常量速查">常量速查</a> · <a href="#十、数据流总览">数据流总览</a>
|
||
</p>
|
||
|
||

|
||
|
||
---
|
||
|
||
## 一、整体架构
|
||
|
||
记忆系统由 5 个核心模块协作完成:
|
||
|
||
| 模块 | 源码位置 | 职责 |
|
||
|------|----------|------|
|
||
| **路径解析** | `src/memdir/paths.ts` | 计算记忆存储目录,处理覆盖和安全校验 |
|
||
| **提示词构建** | `src/memdir/memdir.ts` | 将记忆指令注入系统提示词 |
|
||
| **记忆扫描** | `src/memdir/memoryScan.ts` | 扫描目录、解析 frontmatter、排序 |
|
||
| **智能检索** | `src/memdir/findRelevantMemories.ts` | 用 Sonnet 选择与当前查询相关的记忆 |
|
||
| **自动提取** | `src/services/extractMemories/` | 后台分叉代理,从对话中提取记忆 |
|
||
|
||
辅助模块:
|
||
|
||
| 模块 | 源码位置 | 职责 |
|
||
|------|----------|------|
|
||
| **AutoDream** | `src/services/autoDream/` | 后台记忆整合("做梦"),详见 [03-autodream.md](./03-autodream.md) |
|
||
| **类型定义** | `src/memdir/memoryTypes.ts` | 四种记忆类型的分类法和提示词模板 |
|
||
| **新鲜度** | `src/memdir/memoryAge.ts` | 计算记忆年龄、生成陈旧警告 |
|
||
| **文件检测** | `src/utils/memoryFileDetection.ts` | 判断路径是否属于记忆系统 |
|
||
| **代理记忆** | `src/tools/AgentTool/agentMemory.ts` | 子代理专属的三级记忆目录 |
|
||
| **团队同步** | `src/services/teamMemorySync/` | 记忆的远程上传/下载 |
|
||
|
||
---
|
||
|
||
## 二、路径解析系统
|
||
|
||

|
||
|
||
### 核心函数:`getAutoMemPath()`
|
||
|
||
```
|
||
路径解析优先级(从高到低):
|
||
|
||
1. CLAUDE_COWORK_MEMORY_PATH_OVERRIDE ← Cowork 环境变量(完整路径)
|
||
2. settings.json → autoMemoryDirectory ← 用户设置(支持 ~/ 展开)
|
||
3. {memoryBase}/projects/{sanitized-git-root}/memory/ ← 默认计算路径
|
||
```
|
||
|
||
**关键源码** `src/memdir/paths.ts:223`:
|
||
|
||
```typescript
|
||
export const getAutoMemPath = memoize(
|
||
(): string => {
|
||
const override = getAutoMemPathOverride() ?? getAutoMemPathSetting()
|
||
if (override) return override
|
||
const projectsDir = join(getMemoryBaseDir(), 'projects')
|
||
return join(projectsDir, sanitizePath(getAutoMemBase()), AUTO_MEM_DIRNAME) + sep
|
||
},
|
||
() => getProjectRoot(), // 缓存 key = 项目根目录
|
||
)
|
||
```
|
||
|
||
### 路径安全校验
|
||
|
||
`validateMemoryPath()` 拒绝危险路径:
|
||
|
||
| 被拒路径 | 原因 |
|
||
|----------|------|
|
||
| `../foo` | 相对路径,CWD 相关 |
|
||
| `/` 或 `/a` | 根路径或过短路径 |
|
||
| `C:\` | Windows 驱动器根目录 |
|
||
| `\\server\share` | UNC 网络路径 |
|
||
| 含 `\0` | 空字节,可在系统调用中截断 |
|
||
|
||
**安全限制**:项目级 `.claude/settings.json` **不允许**设置 `autoMemoryDirectory`,防止恶意仓库通过此项获取对 `~/.ssh` 等敏感目录的写权限。
|
||
|
||
### 启用条件
|
||
|
||
`isAutoMemoryEnabled()` 的判断链:
|
||
|
||
```
|
||
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 → 关闭
|
||
CLAUDE_CODE_SIMPLE (--bare) → 关闭
|
||
远程模式 且 无 REMOTE_MEMORY_DIR → 关闭
|
||
settings.json autoMemoryEnabled → 跟随设置
|
||
默认 → 开启
|
||
```
|
||
|
||
---
|
||
|
||
## 三、系统提示词注入
|
||
|
||

|
||
|
||
### 入口:`loadMemoryPrompt()`
|
||
|
||
这是记忆系统与系统提示词的接口。启动时调用一次(通过 `systemPromptSection` 缓存)。
|
||
|
||
```
|
||
loadMemoryPrompt()
|
||
├─ KAIROS 模式?→ buildAssistantDailyLogPrompt() [日志追加模式]
|
||
├─ TEAMMEM 开启?→ buildCombinedMemoryPrompt() [个人+团队双目录]
|
||
├─ 正常模式 → buildMemoryLines() [单目录]
|
||
└─ 禁用 → return null
|
||
```
|
||
|
||
### `buildMemoryLines()` 构建的提示词结构
|
||
|
||
```
|
||
# auto memory
|
||
|
||
你有一个持久化的文件记忆系统,位于 `{memoryDir}`...
|
||
|
||
## Types of memory ← 四种类型的定义和示例
|
||
## What NOT to save ← 排除规则
|
||
## How to save memories ← 两步保存流程
|
||
## When to access memories ← 何时查阅
|
||
## Before recommending ← 引用前验证
|
||
## Memory and other forms ← 与 Plan/Task 的区别
|
||
|
||
## MEMORY.md ← 索引内容(或"当前为空")
|
||
```
|
||
|
||
### MEMORY.md 截断策略
|
||
|
||
`truncateEntrypointContent()` 执行双重限制:
|
||
|
||
```typescript
|
||
// 先截断行数
|
||
if (lineCount > 200) → 截断到 200 行
|
||
|
||
// 再截断字节数(处理超长行)
|
||
if (bytes > 25,000) → 在最后一个换行符处截断
|
||
|
||
// 附加警告
|
||
"WARNING: MEMORY.md is {reason}. Only part of it was loaded."
|
||
```
|
||
|
||
### 目录自动创建
|
||
|
||
`ensureMemoryDirExists()` 在加载提示词时确保目录存在:
|
||
- 递归创建 `mkdir`(处理整个父链)
|
||
- 吞掉 `EEXIST`(幂等)
|
||
- 真正的权限错误只记日志不中断(Write 工具会显示真实错误)
|
||
|
||
提示词中明确告知模型目录已存在,避免浪费 turn 执行 `ls` 或 `mkdir`。
|
||
|
||
---
|
||
|
||
## 四、自动记忆提取
|
||
|
||

|
||
|
||
### 触发时机
|
||
|
||
在 `handleStopHooks` 中,当模型产生最终响应(无工具调用)时触发。
|
||
|
||
**关键源码** `src/services/extractMemories/extractMemories.ts`
|
||
|
||
### 完整提取流程
|
||
|
||
```
|
||
1. 模型完成回复(无 tool_use)
|
||
↓
|
||
2. executeExtractMemories() 被调用
|
||
↓
|
||
3. 守卫检查:
|
||
- 是主代理?(子代理不提取)
|
||
- 功能门控开启?
|
||
- 自动记忆启用?
|
||
- 非远程模式?
|
||
- 没有并行提取在进行?
|
||
↓
|
||
4. 频率控制:
|
||
turnsSinceLastExtraction++
|
||
if < tengu_bramble_lintel → 跳过
|
||
↓
|
||
5. 互斥检查:
|
||
主代理自己写了记忆?→ 跳过,推进游标
|
||
↓
|
||
6. 扫描现有记忆目录(scanMemoryFiles)
|
||
生成清单(formatMemoryManifest)
|
||
↓
|
||
7. 构建提取提示词(buildExtractAutoOnlyPrompt)
|
||
↓
|
||
8. 运行分叉代理(runForkedAgent)
|
||
- 共享父会话的提示词缓存
|
||
- 最多 5 个 turn
|
||
- 限制工具权限
|
||
↓
|
||
9. 提取写入的文件路径
|
||
推进游标到最新消息
|
||
↓
|
||
10. 通知用户:"Memory updated in ..."
|
||
```
|
||
|
||
### 分叉代理(Forked Agent)
|
||
|
||
自动提取使用 `runForkedAgent` — 这是对主会话的完美分叉:
|
||
|
||
- **共享提示词缓存**:避免重复的 API 缓存创建费用
|
||
- **隔离执行**:不影响主会话的消息历史
|
||
- **受限工具**:只允许 Read、Grep、Glob、只读 Bash、以及对记忆目录的 Edit/Write
|
||
- **不记录转录**:防止与主线程的竞争条件
|
||
|
||
### 工具权限(`createAutoMemCanUseTool`)
|
||
|
||
```
|
||
✅ 允许:Read, Grep, Glob(无限制)
|
||
✅ 允许:Bash(仅只读命令:ls, find, grep, cat, stat...)
|
||
✅ 允许:Edit/Write(仅 auto-memory 目录内)
|
||
❌ 拒绝:MCP, Agent, 非只读 Bash, 其他写操作
|
||
```
|
||
|
||
### 互斥机制
|
||
|
||
主代理和提取代理是**互斥的**:
|
||
|
||
```typescript
|
||
function hasMemoryWritesSince(messages, sinceUuid): boolean {
|
||
// 扫描 sinceUuid 之后的所有 assistant 消息
|
||
// 如果有任何 Edit/Write 的 tool_use 指向 auto-memory 目录
|
||
// → return true(跳过提取,推进游标)
|
||
}
|
||
```
|
||
|
||
这避免了重复保存:主代理已经写了记忆时,后台提取直接跳过。
|
||
|
||
### 合并机制
|
||
|
||
如果前一次提取还在运行:
|
||
1. 新的上下文被暂存(`pendingContext`)
|
||
2. 旧提取完成后,立即启动一次**尾部提取**
|
||
3. 尾部提取只处理两次调用之间新增的消息
|
||
|
||
---
|
||
|
||
## 五、智能记忆检索
|
||
|
||

|
||
|
||
### 工作原理
|
||
|
||
每次用户发送查询时,`findRelevantMemories()` 被触发:
|
||
|
||
```
|
||
1. scanMemoryFiles(memoryDir)
|
||
- 递归读取所有 .md 文件(排除 MEMORY.md)
|
||
- 解析 frontmatter(前 30 行)
|
||
- 按修改时间降序排序
|
||
- 最多 200 个文件
|
||
↓
|
||
2. 过滤已展示过的记忆(alreadySurfaced)
|
||
↓
|
||
3. 格式化清单(formatMemoryManifest)
|
||
- [type] filename (ISO timestamp): description
|
||
↓
|
||
4. Sonnet 模型选择(sideQuery)
|
||
- 系统提示:你是记忆选择器...
|
||
- 用户消息:Query + Available memories + Recently used tools
|
||
- 输出:JSON { selected_memories: string[] }
|
||
- 最多选 5 个
|
||
↓
|
||
5. 返回选中记忆的 { path, mtimeMs }
|
||
```
|
||
|
||
### Sonnet 选择器的提示词
|
||
|
||
```
|
||
你正在选择对 Claude Code 处理用户查询有用的记忆。
|
||
你将收到用户查询和可用记忆文件列表(含文件名和描述)。
|
||
|
||
返回最多 5 个明确有用的记忆文件名。
|
||
- 不确定是否有用就不要选
|
||
- 没有明确有用的就返回空列表
|
||
- 如果提供了最近使用的工具列表,不要选这些工具的使用文档
|
||
(但 DO 选择关于这些工具的警告/陷阱/已知问题)
|
||
```
|
||
|
||
### 新鲜度警告
|
||
|
||
选中的记忆在注入上下文时会附带新鲜度信息:
|
||
|
||
```typescript
|
||
function memoryFreshnessText(mtimeMs: number): string {
|
||
const d = memoryAgeDays(mtimeMs)
|
||
if (d <= 1) return '' // 今天/昨天:无警告
|
||
return `This memory is ${d} days old. Memories are point-in-time observations...
|
||
Verify against current code before asserting as fact.`
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 六、记忆扫描详解
|
||
|
||
### `scanMemoryFiles()`
|
||
|
||
**关键设计**:单次遍历(read-then-sort),避免双重 stat 系统调用。
|
||
|
||
```typescript
|
||
async function scanMemoryFiles(memoryDir, signal): Promise<MemoryHeader[]> {
|
||
const entries = await readdir(memoryDir, { recursive: true })
|
||
const mdFiles = entries.filter(f => f.endsWith('.md') && basename(f) !== 'MEMORY.md')
|
||
|
||
// 并行读取所有文件的 frontmatter(前 30 行)
|
||
const headerResults = await Promise.allSettled(
|
||
mdFiles.map(async (relativePath) => {
|
||
const { content, mtimeMs } = await readFileInRange(filePath, 0, 30)
|
||
const { frontmatter } = parseFrontmatter(content)
|
||
return { filename, filePath, mtimeMs, description, type }
|
||
})
|
||
)
|
||
|
||
// 过滤成功的、按时间降序排序、取前 200
|
||
return fulfilled.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, 200)
|
||
}
|
||
```
|
||
|
||
### `formatMemoryManifest()`
|
||
|
||
生成供 Sonnet 或提取代理消费的清单格式:
|
||
|
||
```
|
||
- [feedback] testing_policy.md (2026-03-15T10:30:00.000Z): 集成测试用真实数据库
|
||
- [user] role.md (2026-03-14T08:00:00.000Z): 数据科学家,关注日志
|
||
- [project] freeze.md (2026-03-10T15:00:00.000Z): 3/5 起合并冻结
|
||
```
|
||
|
||
---
|
||
|
||
## 七、代理记忆(Agent Memory)
|
||
|
||

|
||
|
||
子代理(通过 Agent 工具启动的)有独立的三级记忆系统:
|
||
|
||
| 作用域 | 路径 | 说明 |
|
||
|--------|------|------|
|
||
| **user** | `~/.claude/agent-memory/{agentType}/` | 全局用户级 |
|
||
| **project** | `.claude/agent-memory/{agentType}/` | 项目级(提交到 VCS) |
|
||
| **local** | `.claude/agent-memory-local/{agentType}/` | 本地级(不提交) |
|
||
|
||
代理记忆与主记忆的差异:
|
||
- 无 MEMORY.md 索引步骤(`skipIndex = true`)
|
||
- 直接写文件即可,无需两步操作
|
||
- 各代理类型隔离(explorer、planner 等各有各的目录)
|
||
|
||
---
|
||
|
||
## 八、团队记忆同步
|
||
|
||
当 `TEAMMEM` feature flag 开启时:
|
||
|
||
### 目录结构
|
||
|
||
```
|
||
~/.claude/projects/{hash}/memory/
|
||
├── MEMORY.md ← 个人记忆索引
|
||
├── user_*.md ← 个人记忆
|
||
└── team/ ← 团队共享目录
|
||
├── MEMORY.md ← 团队记忆索引
|
||
└── *.md ← 团队记忆
|
||
```
|
||
|
||
### 同步 API
|
||
|
||
```
|
||
GET /api/claude_code/team_memory?repo={owner/repo} ← 拉取
|
||
PUT /api/claude_code/team_memory?repo={owner/repo} ← 推送
|
||
```
|
||
|
||
### 同步语义
|
||
|
||
- **Pull**:服务器内容覆盖本地文件
|
||
- **Push**:仅上传内容哈希不同的键(delta 上传)
|
||
- **删除不传播**:本地删除不会删除远程
|
||
- **限制**:单文件最大 250KB,上传体最大 200KB(分批)
|
||
|
||
### 团队 vs 个人的路由规则
|
||
|
||
在 `memoryTypes.ts` 中,每种类型有 `<scope>` 指导:
|
||
|
||
| 类型 | 默认作用域 |
|
||
|------|-----------|
|
||
| user | 始终个人 |
|
||
| feedback | 默认个人,项目级规范放团队 |
|
||
| project | 偏向团队 |
|
||
| reference | 通常团队 |
|
||
|
||
---
|
||
|
||
## 九、关键常量速查
|
||
|
||
```typescript
|
||
// 索引文件
|
||
ENTRYPOINT_NAME = 'MEMORY.md'
|
||
MAX_ENTRYPOINT_LINES = 200
|
||
MAX_ENTRYPOINT_BYTES = 25_000
|
||
|
||
// 扫描
|
||
MAX_MEMORY_FILES = 200
|
||
FRONTMATTER_MAX_LINES = 30
|
||
|
||
// 路径
|
||
AUTO_MEM_DIRNAME = 'memory'
|
||
|
||
// 提取
|
||
maxTurns = 5 // 分叉代理最多 5 个 turn
|
||
|
||
// 检索
|
||
最多返回 5 个相关记忆
|
||
```
|
||
|
||
---
|
||
|
||
## 十、数据流总览
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ 会话启动 │
|
||
│ │
|
||
│ loadMemoryPrompt() │
|
||
│ → ensureMemoryDirExists() │
|
||
│ → buildMemoryLines() + MEMORY.md 内容 │
|
||
│ → 注入系统提示词 │
|
||
└────────────────────────┬────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ 用户提问 │
|
||
│ │
|
||
│ findRelevantMemories() │
|
||
│ → scanMemoryFiles() [扫描 + frontmatter] │
|
||
│ → Sonnet 选择最多 5 个相关记忆 │
|
||
│ → 注入对话上下文 + 新鲜度警告 │
|
||
└────────────────────────┬────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ Claude 回复 │
|
||
│ │
|
||
│ 模型可能直接写记忆(遵循系统提示词指导) │
|
||
│ 或者不写 → 触发后台提取 │
|
||
└────────────────────────┬────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ 后台自动提取 │
|
||
│ │
|
||
│ executeExtractMemories() │
|
||
│ → 互斥检查(主代理已写?跳过) │
|
||
│ → 构建提取提示词 + 记忆清单 │
|
||
│ → runForkedAgent() [共享缓存, 限制工具, 5 turns] │
|
||
│ → 写入新记忆文件 + 更新 MEMORY.md │
|
||
│ → 通知用户 │
|
||
└────────────────────────┬────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ 后台记忆整合(AutoDream) │
|
||
│ │
|
||
│ executeAutoDream() [每 24h + 5个会话触发] │
|
||
│ → 五重门控检查(开关/时间/节流/会话/锁) │
|
||
│ → buildConsolidationPrompt() │
|
||
│ → runForkedAgent() [只读 Bash, 仅记忆目录写入] │
|
||
│ → 四阶段:定向 → 收集 → 整合 → 修剪 │
|
||
│ → 合并重复 / 修正过时 / 压缩索引 │
|
||
│ → 通知用户:"Improved N memories" │
|
||
│ │
|
||
│ 详见 03-autodream.md │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|