add claude code analysis 20260404
This commit is contained in:
764
docs/cc_analysis/analysis_20260401/Claude_Code记忆系统深度分析.md
Normal file
764
docs/cc_analysis/analysis_20260401/Claude_Code记忆系统深度分析.md
Normal file
@@ -0,0 +1,764 @@
|
||||
# Claude Code Memory 记忆系统深度分析
|
||||
|
||||
> 基于 Claude Code 源码(2026-03-31 快照,512K 行 TypeScript)逆向分析
|
||||
> 核心文件:`src/memdir/`(8个文件)、`src/services/extractMemories/`(2个)、`src/services/SessionMemory/`(3个)、`src/services/teamMemorySync/`(5个)
|
||||
|
||||
---
|
||||
|
||||
## 1. 三层记忆架构总览
|
||||
|
||||
Claude Code 的记忆系统不是一个简单的文件存储,而是一个**三层递进的知识管理体系**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 第3层:团队记忆(Team Memory) │
|
||||
│ 跨用户、按仓库 │ 服务端同步 │ REST API + 乐观锁 │
|
||||
│ 秘密扫描保护 │ 文件监听器 │ 30种凭证检测规则 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 第2层:持久记忆(Persistent Memory / extractMemories) │
|
||||
│ 跨会话、按项目 │ 本地文件 │ 后台 Fork Agent 自动提取 │
|
||||
│ 4种类型分类 │ MEMORY.md 索引 │ AI 相关性召回 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 第1层:会话记忆(Session Memory) │
|
||||
│ 仅当前会话 │ 单个文件 │ 10个固定章节 │ 服务于压缩 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| 层级 | 范围 | 持久性 | 触发方式 | 存储位置 |
|
||||
|------|------|--------|----------|----------|
|
||||
| 会话记忆 | 当前会话 | 临时(会话结束即停用) | 后采样钩子(token+工具调用阈值) | 会话目录下单文件 |
|
||||
| 持久记忆 | 跨会话、按项目 | 永久(直到手动删除) | 每轮查询结束时(Stop Hook) | `~/.claude/projects/<项目>/memory/` |
|
||||
| 团队记忆 | 跨用户、按仓库 | 远程服务器 + 本地镜像 | 文件监听器(2秒去抖) | `memory/team/` + Anthropic API |
|
||||
|
||||
---
|
||||
|
||||
## 2. 持久记忆系统(核心)
|
||||
|
||||
### 2.1 记忆文件格式
|
||||
|
||||
每条记忆是一个独立的 Markdown 文件,带 YAML frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: 用户偏好-简洁回复
|
||||
description: 用户不希望在每次回复末尾加总结
|
||||
type: feedback
|
||||
---
|
||||
|
||||
不要在回复末尾总结刚做了什么,用户能看到 diff。
|
||||
|
||||
**Why:** 用户明确要求过"stop summarizing what you just did"
|
||||
**How to apply:** 所有回复结束时,直接结束,不加回顾性总结。
|
||||
```
|
||||
|
||||
### 2.2 四种记忆类型
|
||||
|
||||
```typescript
|
||||
const MEMORY_TYPES = ['user', 'feedback', 'project', 'reference'] as const
|
||||
```
|
||||
|
||||
| 类型 | 用途 | 保存时机 | 作用域(团队模式) |
|
||||
|------|------|----------|:------------------:|
|
||||
| **user** | 用户角色、目标、知识背景 | 了解到用户的身份信息时 | 始终私有 |
|
||||
| **feedback** | 用户对工作方式的指导 | 用户纠正或确认做法时 | 默认私有 |
|
||||
| **project** | 项目进展、目标、事件 | 了解到工作计划、截止日期时 | 偏向团队 |
|
||||
| **reference** | 外部系统指针 | 了解到 Linear/Grafana/Slack 等资源时 | 通常团队 |
|
||||
|
||||
**每种类型要求的结构:**
|
||||
- feedback 和 project 类型必须包含 `**Why:**` 和 `**How to apply:**` 行
|
||||
- 相对日期必须转换为绝对日期(如"周四"→"2026-03-05")
|
||||
- user 类型的示例:"深度 Go 经验,React 新手——用后端类比解释前端概念"
|
||||
|
||||
### 2.3 明确不保存的内容
|
||||
|
||||
源码中硬编码了 6 类排除项(即使用户明确要求也不保存):
|
||||
|
||||
1. 代码模式、架构、文件路径、项目结构——可从代码推导
|
||||
2. Git 历史、最近变更——`git log`/`git blame` 是权威来源
|
||||
3. 调试方案或修复步骤——修复在代码中,commit message 有上下文
|
||||
4. CLAUDE.md 中已有的内容——不重复
|
||||
5. 临时任务细节:进行中的工作、当前对话上下文
|
||||
6. 如果用户要求保存 PR 列表或活动摘要,要追问"什么是令人意外的或不明显的?"——只保存那部分
|
||||
|
||||
### 2.4 MEMORY.md 索引机制
|
||||
|
||||
```
|
||||
MEMORY.md 是索引,不是记忆本身
|
||||
```
|
||||
|
||||
**格式:** 纯 Markdown,无 frontmatter。每条一行,约 150 字符以内:
|
||||
```markdown
|
||||
- [用户偏好-简洁回复](feedback_concise.md) — 不在回复末尾加总结
|
||||
- [项目-合并冻结](project_freeze.md) — 3月5日起移动端发布冻结
|
||||
```
|
||||
|
||||
**限制:**
|
||||
- 最大 200 行(`MAX_ENTRYPOINT_LINES`)
|
||||
- 最大 25,000 字节(`MAX_ENTRYPOINT_BYTES`)
|
||||
- 超出时在末尾追加截断警告
|
||||
- 截断按行边界执行(不会切断一行的中间)
|
||||
|
||||
**两步保存流程:**
|
||||
1. 写入记忆文件(如 `feedback_concise.md`)
|
||||
2. 在 MEMORY.md 中添加索引行
|
||||
|
||||
可选的 `skipIndex` 模式(通过 `tengu_moth_copse` 特性开关)可跳过第2步。
|
||||
|
||||
### 2.5 存储目录结构
|
||||
|
||||
```
|
||||
~/.claude/
|
||||
projects/
|
||||
-Users-charlesqin-Desktop-myproject/ ← sanitizePath(项目根目录)
|
||||
memory/ ← AUTO_MEM_DIRNAME
|
||||
MEMORY.md ← 索引文件
|
||||
user_role.md ← 私有记忆文件
|
||||
feedback_testing.md
|
||||
project_deadline.md
|
||||
reference_linear.md
|
||||
team/ ← 团队记忆子目录
|
||||
MEMORY.md ← 团队索引
|
||||
project_api_migration.md
|
||||
reference_oncall_board.md
|
||||
logs/ ← KAIROS 每日日志
|
||||
2026/
|
||||
03/
|
||||
2026-03-31.md
|
||||
```
|
||||
|
||||
### 2.6 路径安全机制
|
||||
|
||||
`paths.ts` 包含多层安全验证:
|
||||
|
||||
| 安全层 | 检查内容 | 防御目标 |
|
||||
|--------|----------|----------|
|
||||
| 绝对路径检查 | 拒绝相对路径 | 路径遍历 |
|
||||
| 根路径检查 | 拒绝长度 < 3 的路径 | 写入系统根目录 |
|
||||
| UNC 路径检查 | 拒绝 `\\server\share` 和 `//server/share` | NTLM 凭证泄露 |
|
||||
| Null 字节检查 | 拒绝包含 `\0` 的路径 | 路径截断攻击 |
|
||||
| Tilde 展开限制 | 拒绝 `~`、`~/`、`~/.`、`~/..` | 匹配整个 HOME |
|
||||
| **项目设置排除** | `.claude/settings.json` 不能设 `autoMemoryDirectory` | **恶意仓库重定向写入到 `~/.ssh`** |
|
||||
| NFC 规范化 | Unicode NFC 标准化 | macOS 路径不一致 |
|
||||
| Git 根规范化 | worktree 共享主仓库的记忆 | 避免重复记忆目录 |
|
||||
|
||||
**关键安全设计:** `projectSettings`(提交到仓库的 `.claude/settings.json`)被有意排除在 `autoMemoryDirectory` 的信任来源之外。源码注释明确说明原因:
|
||||
|
||||
```typescript
|
||||
// SECURITY: projectSettings is intentionally excluded —
|
||||
// a malicious repo could set autoMemoryDirectory: "~/.ssh"
|
||||
// and gain write access to sensitive directories
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 自动记忆提取(extractMemories)
|
||||
|
||||
### 3.1 触发时机
|
||||
|
||||
```
|
||||
用户输入 → 模型响应 → 工具执行 → 循环... → 模型最终响应(无工具调用)
|
||||
↓
|
||||
Stop Hooks 执行
|
||||
↓
|
||||
executeExtractMemories() ← 即发即忘
|
||||
```
|
||||
|
||||
**前置条件(全部满足才执行):**
|
||||
1. `EXTRACT_MEMORIES` 编译时特性开关开启
|
||||
2. `isExtractModeActive()` = true(`tengu_passport_quail` GrowthBook 开关)
|
||||
3. `isAutoMemoryEnabled()` = true
|
||||
4. 非子代理(`!toolUseContext.agentId`)
|
||||
5. 非 bare 模式
|
||||
6. 自上次提取以来有新的模型可见消息
|
||||
|
||||
### 3.2 互斥机制
|
||||
|
||||
```typescript
|
||||
// 如果主代理在对话中已经直接写了记忆文件,跳过自动提取
|
||||
if (hasMemoryWritesSince(lastMemoryMessageUuid)) {
|
||||
// 推进游标但不执行提取——避免覆盖用户显式保存的记忆
|
||||
lastMemoryMessageUuid = messages[messages.length - 1].uuid
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
这防止了自动提取和用户手动保存之间的冲突。
|
||||
|
||||
### 3.3 Fork Agent 执行
|
||||
|
||||
```typescript
|
||||
const result = await runForkedAgent({
|
||||
messages: conversationMessages,
|
||||
system: extractionPrompt,
|
||||
maxTurns: 5, // 硬上限:5轮(读1轮 + 写1-4轮)
|
||||
canUseTool: createAutoMemCanUseTool(autoMemPath),
|
||||
// ... 共享主会话的 prompt cache
|
||||
})
|
||||
```
|
||||
|
||||
**工具权限白名单:**
|
||||
|
||||
| 工具 | 权限 |
|
||||
|------|------|
|
||||
| FileRead | 允许(无限制) |
|
||||
| Grep | 允许(无限制) |
|
||||
| Glob | 允许(无限制) |
|
||||
| Bash | 仅只读命令(ls, find, grep, cat, stat, wc, head, tail) |
|
||||
| FileEdit | 仅限 `autoMemPath` 内 |
|
||||
| FileWrite | 仅限 `autoMemPath` 内 |
|
||||
| MCP / Agent / 写入型 Bash | **全部禁止** |
|
||||
|
||||
### 3.4 提取节流
|
||||
|
||||
```typescript
|
||||
let turnsSinceLastExtraction = 0
|
||||
const turnsBeforeExtraction = getFeatureValue('tengu_bramble_lintel', 1)
|
||||
|
||||
// 每 N 轮查询才执行一次提取(默认每轮都执行)
|
||||
if (turnsSinceLastExtraction < turnsBeforeExtraction) {
|
||||
turnsSinceLastExtraction++
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 提取后通知
|
||||
|
||||
成功提取后,将写入的文件路径列表作为 `SystemMemorySavedMessage` 追加到主对话中,这样主代理知道记忆已更新。
|
||||
|
||||
---
|
||||
|
||||
## 4. AI 驱动的记忆召回(findRelevantMemories)
|
||||
|
||||
### 4.1 召回流程
|
||||
|
||||
```
|
||||
用户发送消息
|
||||
↓
|
||||
scanMemoryFiles(memoryDir)
|
||||
↓ 递归读取所有 .md 文件(排除 MEMORY.md)
|
||||
↓ 解析前 30 行 frontmatter(description + type)
|
||||
↓ 按 mtime 降序排序,上限 200 个文件
|
||||
↓
|
||||
过滤掉 alreadySurfaced(之前轮次已展示的路径)
|
||||
↓
|
||||
selectRelevantMemories(query, memories, signal, recentTools)
|
||||
↓ 构建文本清单:每行 "- [type] filename (timestamp): description"
|
||||
↓ 附加 "Recently used tools: ..." 部分
|
||||
↓
|
||||
sideQuery → Sonnet 模型
|
||||
↓ 系统提示:SELECT_MEMORIES_SYSTEM_PROMPT
|
||||
↓ 用户消息:Query + Available memories
|
||||
↓ 结构化输出:{ selected_memories: string[] }
|
||||
↓ max_tokens: 256
|
||||
↓
|
||||
返回最多 5 条 RelevantMemory(路径 + mtime)
|
||||
```
|
||||
|
||||
### 4.2 选择提示词的关键规则
|
||||
|
||||
```
|
||||
- 返回最多 5 个文件名
|
||||
- 要有选择性和辨别力;如果不确定,不要包含
|
||||
- 如果提供了最近使用的工具列表:
|
||||
✗ 不要选择这些工具的使用参考/API 文档(已经在用了)
|
||||
✓ 仍然选择这些工具的警告/陷阱/已知问题
|
||||
```
|
||||
|
||||
### 4.3 记忆新鲜度标注
|
||||
|
||||
```typescript
|
||||
function memoryAge(mtimeMs: number): string {
|
||||
const days = memoryAgeDays(mtimeMs)
|
||||
if (days === 0) return 'today'
|
||||
if (days === 1) return 'yesterday'
|
||||
return `${days} days ago`
|
||||
}
|
||||
|
||||
function memoryFreshnessText(mtimeMs: number): string {
|
||||
if (memoryAgeDays(mtimeMs) <= 1) return '' // 新鲜记忆不加警告
|
||||
return `This memory is ${days} days old.
|
||||
Memories are point-in-time observations, not live state —
|
||||
claims about code behavior or file:line citations may be outdated.
|
||||
Verify against current code before asserting as fact.`
|
||||
}
|
||||
```
|
||||
|
||||
**设计动机(源码注释):** 用户报告过过期的记忆被模型当作事实断言。"47 days ago" 比 ISO 时间戳更能触发模型的过期推理。
|
||||
|
||||
### 4.4 召回前的验证要求
|
||||
|
||||
源码中注入的系统提示明确要求模型在使用记忆前验证:
|
||||
|
||||
```
|
||||
- 如果记忆提到一个文件路径 → 检查文件是否存在
|
||||
- 如果记忆提到一个函数或标志 → grep 搜索它
|
||||
- 如果用户即将根据你的建议行动 → 先验证
|
||||
- "记忆说 X 存在"不等于"X 现在存在"
|
||||
- 活动日志、架构快照是冻结在某个时间点的,优先使用 git log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 会话记忆(Session Memory)
|
||||
|
||||
### 5.1 与持久记忆的关键区别
|
||||
|
||||
| 维度 | 会话记忆 | 持久记忆 |
|
||||
|------|----------|----------|
|
||||
| 生命周期 | 当前会话 | 跨会话永久 |
|
||||
| 文件数量 | 1 个 | 多个 |
|
||||
| 触发频率 | 每次后采样(需满足阈值) | 每轮查询结束 |
|
||||
| 主要用途 | **服务于上下文压缩** | 跨会话知识保留 |
|
||||
| 记忆提取者 | Fork Agent(仅 FileEdit) | Fork Agent(Read/Write/Edit/Grep) |
|
||||
| 格式 | 10个固定章节 | 自由格式 + frontmatter |
|
||||
|
||||
### 5.2 触发阈值
|
||||
|
||||
```typescript
|
||||
// 默认配置(可通过 GrowthBook tengu_sm_config 远程调整)
|
||||
const defaults = {
|
||||
minimumMessageTokensToInit: 10_000, // 上下文达到 10K tokens 才激活
|
||||
minimumTokensBetweenUpdate: 5_000, // 每增长 5K tokens 更新一次
|
||||
toolCallsBetweenUpdates: 3, // 且至少 3 次工具调用
|
||||
}
|
||||
|
||||
// 触发条件:
|
||||
// (token 阈值满足 AND 工具调用阈值满足)
|
||||
// OR (token 阈值满足 AND 最后一轮助手消息无工具调用)
|
||||
```
|
||||
|
||||
### 5.3 会话记忆模板(10 个固定章节)
|
||||
|
||||
```markdown
|
||||
# Session Memory
|
||||
|
||||
## Session Title
|
||||
_Brief description of what this session is about_
|
||||
|
||||
## Current State
|
||||
_What is the current status of the work_
|
||||
|
||||
## Task specification
|
||||
_Detailed description of the current task requirements_
|
||||
|
||||
## Files and Functions
|
||||
_Key files and functions being worked on_
|
||||
|
||||
## Workflow
|
||||
_Steps being followed or processes in use_
|
||||
|
||||
## Errors & Corrections
|
||||
_Errors encountered and how they were resolved_
|
||||
|
||||
## Codebase and System Documentation
|
||||
_Important codebase patterns, conventions, and system behavior_
|
||||
|
||||
## Learnings
|
||||
_Insights gained during this session_
|
||||
|
||||
## Key results
|
||||
_Important outputs, measurements, or achievements_
|
||||
|
||||
## Worklog
|
||||
_Chronological log of actions taken_
|
||||
```
|
||||
|
||||
### 5.4 大小限制
|
||||
|
||||
```typescript
|
||||
const MAX_SECTION_LENGTH = 2_000 // 每个章节最大 2K tokens
|
||||
const MAX_TOTAL_SESSION_MEMORY_TOKENS = 12_000 // 整个文件最大 12K tokens
|
||||
```
|
||||
|
||||
### 5.5 与压缩的集成
|
||||
|
||||
Session Memory 是上下文压缩的优先路径(参见 `sessionMemoryCompact.ts`):
|
||||
- 自动压缩触发时,优先尝试用 Session Memory 作为摘要
|
||||
- 保留最近 10K-40K tokens 的消息
|
||||
- 比传统压缩快得多(不调用 LLM),且摘要质量更可预测
|
||||
|
||||
### 5.6 自定义
|
||||
|
||||
用户可以自定义模板和提示词:
|
||||
- 模板:`~/.claude/session-memory/config/template.md`
|
||||
- 提示词:`~/.claude/session-memory/config/prompt.md`(支持 `{{variableName}}` 替换)
|
||||
|
||||
---
|
||||
|
||||
## 6. 团队记忆同步
|
||||
|
||||
### 6.1 前置条件
|
||||
|
||||
同时满足才启用:
|
||||
1. `TEAMMEM` 编译时特性开关开启
|
||||
2. `isTeamMemoryEnabled()` = true(`tengu_herring_clock` GrowthBook 开关)
|
||||
3. 第一方 OAuth + 同时拥有 `INFERENCE_SCOPE` 和 `PROFILE_SCOPE`
|
||||
4. Git remote 是 github.com(非 GitHub 仓库不同步)
|
||||
|
||||
### 6.2 API 端点
|
||||
|
||||
```
|
||||
基础 URL: {baseUrl}/api/claude_code/team_memory?repo={owner/repo}
|
||||
```
|
||||
|
||||
| 方法 | 参数 | 用途 | 关键状态码 |
|
||||
|------|------|------|:----------:|
|
||||
| GET | `repo={slug}` | 拉取全部团队记忆 | 200 / 304 / 404 |
|
||||
| GET | `repo={slug}&view=hashes` | 仅拉取哈希(轻量探测) | 200 / 404 |
|
||||
| PUT | `repo={slug}` | 上传记忆条目(upsert) | 200 / 412 / 413 |
|
||||
|
||||
**认证头:** `Authorization: Bearer {oauthToken}`
|
||||
|
||||
### 6.3 同步协议
|
||||
|
||||
#### 拉取(Pull)
|
||||
|
||||
```
|
||||
1. GET 请求,携带 ETag(条件请求)
|
||||
2. 304 = 未变化,跳过
|
||||
3. 200 = 有更新:
|
||||
a. 对每个条目验证路径(防遍历攻击)
|
||||
b. 跳过 >250KB 的文件
|
||||
c. 跳过本地内容已匹配的文件
|
||||
d. 并行写入本地文件系统
|
||||
e. 刷新 serverChecksums
|
||||
```
|
||||
|
||||
#### 推送(Push — Delta 上传 + 乐观锁)
|
||||
|
||||
```
|
||||
1. readLocalTeamMemory(): 遍历 team/ 目录
|
||||
→ 每个文件扫描秘密(30种规则)
|
||||
→ 跳过 >250KB 的文件
|
||||
→ 计算 SHA-256 哈希
|
||||
|
||||
2. 计算 delta:仅本地哈希与 serverChecksums 不同的 key
|
||||
|
||||
3. 分批上传:贪心装箱,每批 ≤ 200KB (MAX_PUT_BODY_BYTES)
|
||||
|
||||
4. 每批携带 If-Match 头(乐观锁)
|
||||
|
||||
5. 冲突处理:
|
||||
412 Conflict → 探测 ?view=hashes → 刷新 serverChecksums → 重算 delta → 重试(最多 2 次)
|
||||
413 Too Many → 学习 serverMaxEntries 上限,截断后重试
|
||||
|
||||
6. 冲突策略:本地优先(local-wins)
|
||||
→ 当前用户正在编辑的内容覆盖服务端
|
||||
```
|
||||
|
||||
### 6.4 文件监听器
|
||||
|
||||
```typescript
|
||||
// watcher.ts
|
||||
fs.watch(teamMemDir, { recursive: true })
|
||||
// macOS: FSEvents(O(1) 文件描述符)
|
||||
// Linux: inotify(O(子目录数))
|
||||
|
||||
// 2 秒去抖:最后一次变更后 2 秒才触发推送
|
||||
```
|
||||
|
||||
**推送抑制:** 永久失败(no_oauth、no_repo、4xx 非 409/429)后,抑制后续推送,直到发生文件删除(恢复动作)或会话重启。
|
||||
|
||||
### 6.5 团队 vs 私有的作用域划分
|
||||
|
||||
源码注入的系统提示中,每种记忆类型都带有 XML 作用域标签:
|
||||
|
||||
```xml
|
||||
<type>
|
||||
<name>user</name>
|
||||
<scope>always private</scope>
|
||||
...
|
||||
</type>
|
||||
|
||||
<type>
|
||||
<name>project</name>
|
||||
<scope>strongly bias toward team</scope>
|
||||
...
|
||||
</type>
|
||||
```
|
||||
|
||||
额外安全要求:
|
||||
```
|
||||
MUST avoid saving sensitive data within shared team memories
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 秘密扫描保护
|
||||
|
||||
### 7.1 双层防护
|
||||
|
||||
| 层 | 时机 | 文件 | 行为 |
|
||||
|----|------|------|------|
|
||||
| 写入时拦截 | FileWrite/FileEdit 调用 | `teamMemSecretGuard.ts` | 阻止写入,返回错误 |
|
||||
| 上传前扫描 | pushTeamMemory 读取本地文件 | `secretScanner.ts` | 跳过该文件,不上传 |
|
||||
|
||||
### 7.2 检测的 30 种秘密模式
|
||||
|
||||
**云服务商:**
|
||||
- AWS Access Token(A3T/AKIA/ASIA/ABIA/ACCA 前缀)
|
||||
- GCP API Key(AIza 前缀)
|
||||
- Azure AD Client Secret
|
||||
- DigitalOcean PAT / Access Token
|
||||
|
||||
**AI API:**
|
||||
- Anthropic API Key(`sk-ant-api03-` 前缀,运行时拼接避免自身匹配)
|
||||
- Anthropic Admin API Key(`sk-ant-admin01-`)
|
||||
- OpenAI API Key(`sk-proj`/`svcacct`/`admin` + `T3BlbkFJ` 标记)
|
||||
- HuggingFace Access Token(`hf_`)
|
||||
|
||||
**版本控制:**
|
||||
- GitHub PAT / Fine-grained PAT / App Token / OAuth / Refresh Token
|
||||
- GitLab PAT / Deploy Token
|
||||
|
||||
**通信平台:**
|
||||
- Slack Bot/User/App Token(`xoxb-`/`xoxp-`/`xapp-`)
|
||||
- Twilio API Key
|
||||
- SendGrid API Token
|
||||
|
||||
**开发工具:**
|
||||
- npm Access Token
|
||||
- PyPI Upload Token
|
||||
- Databricks API Token
|
||||
- HashiCorp Terraform API Token
|
||||
- Pulumi API Token
|
||||
- Postman API Token
|
||||
|
||||
**可观测性:**
|
||||
- Grafana API Key / Cloud API Token / Service Account Token
|
||||
- Sentry User/Org Token
|
||||
|
||||
**支付:**
|
||||
- Stripe Access Token(`sk_test`/`live`/`prod` 或 `rk_`)
|
||||
- Shopify Access Token / Shared Secret
|
||||
|
||||
**密码学:**
|
||||
- PEM 格式私钥(`BEGIN/END PRIVATE KEY` 块)
|
||||
|
||||
### 7.3 设计原则
|
||||
|
||||
```typescript
|
||||
// 匹配到的秘密值永远不被记录或返回——只返回规则 ID 和人类可读标签
|
||||
// Anthropic API key 前缀在运行时拼接,避免匹配自身的 excluded-strings 检查:
|
||||
const prefix = ['sk', 'ant', 'api'].join('-')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. KAIROS 模式的记忆(每日日志)
|
||||
|
||||
当 KAIROS(AI 助手模式)激活时,记忆范式完全改变:
|
||||
|
||||
```
|
||||
普通模式:写独立文件 + 更新 MEMORY.md 索引
|
||||
KAIROS 模式:追加到 logs/YYYY/MM/YYYY-MM-DD.md 日志文件
|
||||
```
|
||||
|
||||
- 只追加,不编辑已有内容
|
||||
- 每日一个文件,按日期路径组织
|
||||
- 由单独的 `/dream` 技能在夜间蒸馏日志为主题文件和 MEMORY.md
|
||||
- 提示词中使用 `YYYY-MM-DD` 占位符而非实际日期(因为提示词被缓存,日期变化不会触发失效)
|
||||
|
||||
---
|
||||
|
||||
## 9. 记忆系统注入的完整系统提示词
|
||||
|
||||
### 9.1 个人模式(auto memory)
|
||||
|
||||
```
|
||||
# auto memory
|
||||
|
||||
You have a persistent, file-based memory system at `<memoryDir>`.
|
||||
This directory already exists — write to it directly with the Write tool.
|
||||
|
||||
You should build up this memory system over time so that future conversations
|
||||
can have a complete picture of who the user is, how they'd like to collaborate
|
||||
with you, what behaviors to avoid or repeat, and the context behind the work.
|
||||
|
||||
[如果用户要求记住 → 立即保存]
|
||||
[如果用户要求忘记 → 找到并删除]
|
||||
|
||||
## Types of memory
|
||||
[4种类型,每种含 description/when_to_save/how_to_use/examples]
|
||||
|
||||
## What NOT to save in memory
|
||||
[6类排除项]
|
||||
|
||||
## How to save memories
|
||||
Step 1: 写入文件(带 frontmatter)
|
||||
Step 2: 在 MEMORY.md 中添加索引行
|
||||
|
||||
## When to access memories
|
||||
- 看起来相关时
|
||||
- 用户明确要求时(必须访问)
|
||||
- 用户说"忽略记忆"时,假装 MEMORY.md 为空
|
||||
|
||||
## Before recommending from memory
|
||||
- 提到文件路径 → 检查文件是否存在
|
||||
- 提到函数/标志 → grep 搜索
|
||||
- 用户要行动 → 先验证
|
||||
|
||||
## Memory and other forms of persistence
|
||||
- 实施方案 → 用 Plan,不用记忆
|
||||
- 当前对话步骤 → 用 Tasks,不用记忆
|
||||
|
||||
## Searching past context
|
||||
[grep 记忆文件,或搜索会话 transcript]
|
||||
|
||||
## MEMORY.md
|
||||
[实际的 MEMORY.md 内容,或"当前为空"]
|
||||
```
|
||||
|
||||
### 9.2 团队模式额外内容
|
||||
|
||||
在个人模式基础上增加:
|
||||
|
||||
```
|
||||
## Memory scope
|
||||
- 私有记忆:<privateDir> — 你的个人偏好、反馈
|
||||
- 团队记忆:<teamDir> — 项目上下文、外部引用
|
||||
|
||||
[类型定义中增加 <scope> XML 标签]
|
||||
|
||||
额外安全规则:不得在团队记忆中保存敏感数据
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 团队记忆的路径安全(深度防御)
|
||||
|
||||
`teamMemPaths.ts` 是整个记忆系统中安全措施最密集的文件:
|
||||
|
||||
### 10.1 自定义错误类
|
||||
|
||||
```typescript
|
||||
class PathTraversalError extends Error {
|
||||
name = 'PathTraversalError'
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 sanitizePathKey — 服务端提供的相对路径清理
|
||||
|
||||
```typescript
|
||||
function sanitizePathKey(key: string): string {
|
||||
// 1. 拒绝 null 字节
|
||||
// 2. URL 编码遍历检测:解码 %2e%2e%2f 等
|
||||
// 3. Unicode 规范化攻击:全角字符 \uFF0E\uFF0E\uFF0F → ../
|
||||
// 4. 拒绝反斜杠(Windows 路径遍历)
|
||||
// 5. 拒绝绝对路径
|
||||
}
|
||||
```
|
||||
|
||||
### 10.3 realpathDeepestExisting — 符号链接解析
|
||||
|
||||
```typescript
|
||||
async function realpathDeepestExisting(path: string): Promise<string> {
|
||||
// 逐级向上 realpath() 直到成功
|
||||
// 检测悬挂符号链接(link 存在但 target 不存在)
|
||||
// → 安全威胁:writeFile 会跟随链接在 team 目录外创建文件
|
||||
// 检测符号链接循环(ELOOP)→ PathTraversalError
|
||||
// 不可恢复的错误(EACCES, EIO)→ fail-closed
|
||||
}
|
||||
```
|
||||
|
||||
### 10.4 validateTeamMemWritePath — 双重验证
|
||||
|
||||
```
|
||||
写入团队记忆文件前的完整验证链:
|
||||
|
||||
第1遍:字符串级
|
||||
1. Null 字节检查
|
||||
2. path.resolve() 解析
|
||||
3. startsWith(teamDir) 验证
|
||||
|
||||
第2遍:符号链接级
|
||||
4. realpathDeepestExisting() 解析实际路径
|
||||
5. isRealPathWithinTeamDir() 验证实际路径在团队目录内
|
||||
|
||||
通过 → 返回解析后的路径
|
||||
失败 → 抛出 PathTraversalError
|
||||
```
|
||||
|
||||
### 10.5 前缀攻击防护
|
||||
|
||||
```typescript
|
||||
// 团队目录路径以分隔符结尾:/foo/team/
|
||||
// 这样 /foo/team-evil/ 不会通过 startsWith 检查
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 记忆的生命周期全景
|
||||
|
||||
```
|
||||
会话开始
|
||||
│
|
||||
├─ loadMemoryPrompt()
|
||||
│ → 读取 MEMORY.md → 注入系统提示
|
||||
│
|
||||
├─ initSessionMemory()
|
||||
│ → 注册后采样钩子
|
||||
│
|
||||
├─ startTeamMemoryWatcher()
|
||||
│ → 拉取服务端 → 启动文件监听
|
||||
│
|
||||
▼
|
||||
每轮对话
|
||||
│
|
||||
├─ findRelevantMemories(query)
|
||||
│ → 扫描文件 → Sonnet 选择 → 返回最多 5 条
|
||||
│ → 新鲜度标注(>1天 → 加过期警告)
|
||||
│
|
||||
├─ [模型可能直接读/写记忆文件]
|
||||
│ → 写入团队记忆 → checkTeamMemSecrets() 拦截秘密
|
||||
│ → 文件监听触发 → 2秒后推送到服务端
|
||||
│
|
||||
├─ Session Memory 后采样钩子
|
||||
│ → 检查阈值 → Fork Agent 更新单文件
|
||||
│
|
||||
├─ 模型最终响应(无工具调用)
|
||||
│ → Stop Hooks → executeExtractMemories()
|
||||
│ → 互斥检查 → Fork Agent 提取 → 更新持久记忆文件
|
||||
│
|
||||
▼
|
||||
自动压缩触发时
|
||||
│
|
||||
├─ trySessionMemoryCompaction()(优先路径)
|
||||
│ → 用 Session Memory 内容作为摘要
|
||||
│ → 保留最近 10K-40K tokens 的消息
|
||||
│
|
||||
├─ compactConversation()(回退路径)
|
||||
│ → Fork Agent 生成 9 章节结构化摘要
|
||||
│
|
||||
▼
|
||||
会话结束
|
||||
│
|
||||
├─ drainPendingExtraction()
|
||||
│ → 等待进行中的记忆提取完成
|
||||
│
|
||||
└─ 团队记忆推送最后一批变更
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 关键设计决策总结
|
||||
|
||||
| 决策 | 原因 |
|
||||
|------|------|
|
||||
| 记忆文件是独立的 .md 文件,不是数据库 | 用户可以直接编辑、Git 追踪、跨工具使用 |
|
||||
| MEMORY.md 是索引不是记忆 | 防止单文件膨胀,支持截断不丢核心内容 |
|
||||
| 自动提取在 Stop Hook 中即发即忘 | 不阻塞主对话循环 |
|
||||
| 互斥检查(主代理 vs 自动提取) | 防止覆盖用户显式保存的内容 |
|
||||
| 召回用 Sonnet 不用 Haiku | 需要理解语义相关性,Haiku 不够准确 |
|
||||
| 新鲜度用"X days ago"不用 ISO 时间 | 模型对相对时间的推理比绝对时间好 |
|
||||
| 项目设置排除 autoMemoryDirectory | 防止恶意仓库劫持写入路径 |
|
||||
| 团队记忆双重路径验证 | 符号链接攻击需要在文件系统层面检查 |
|
||||
| 秘密模式在运行时拼接 | 避免自身的 excluded-strings 检查 |
|
||||
| 本地优先的冲突策略 | 用户正在编辑 = 最新意图 |
|
||||
| 断路器(MAX_CONFLICT_RETRIES = 2) | 防止推送冲突的无限重试 |
|
||||
|
||||
---
|
||||
|
||||
*来自:AI超元域 | B站频道:https://space.bilibili.com/3493277319825652*
|
||||
|
||||
*基于 Claude Code 源码逆向分析,2026-03-31*
|
||||
Reference in New Issue
Block a user