7. 会话、记忆与上下文管理——开发者参考

April 7, 2026 · View on GitHub

Claude Code 最核心的工程优势之一:5 层递增压缩、CLAUDE.md 分层记忆、Auto Dream 自动整理、Team Memory 团队同步、崩溃恢复。这是 Qwen Code 与 Claude Code 差距最大的领域(~73,000 行代码)。

Qwen Code 对标:上下文压缩(仅单一 70% 手动压缩 vs 5 层自动)、记忆系统(简单笔记 vs CLAUDE.md + Auto Dream)、崩溃恢复(无 vs 3 种检测 + 合成续行)

为什么上下文管理是 Code Agent 的核心竞争力

问题定义

Code Agent 的会话比聊天机器人长 10-100 倍——一个重构任务可能持续 200+ 轮、消耗 500K+ token。上下文窗口(即使是 100 万 token)最终会被填满。此时发生什么,决定了 Agent 的可用性:

策略做法后果
无压缩上下文满后报错终止长任务无法完成
单一压缩(Qwen Code 当前)超过 70% 时全量压缩一次性丢失大量细节,可能遗忘关键决策
5 层递增压缩(Claude Code)从轻量到重量逐级升级最大限度保留信息,平滑过渡

为什么"简单压缩"不够

  • 工具输出占上下文的 60-80%(一个 npm test 可能输出 500 行)
  • 旧工具输出的信息价值随时间递减(3 轮前的 git diff 结果已过时)
  • 但 Agent 的决策推理价值不递减("我决定用策略 B 因为 A 有性能问题")
  • 简单压缩无法区分"可丢弃的工具输出"和"不可丢弃的决策上下文"

Claude Code 的 5 层压缩精确解决了这个问题——每一层针对不同类型的内容,从最低价值开始裁剪。

竞品上下文管理对比

Agent压缩策略记忆系统崩溃恢复
Claude Code5 层递增 + Tool Output MaskingCLAUDE.md + Auto Dream + Team Memory3 种检测 + 合成续行
Gemini CLI单阈值压缩(默认 50%) + Tool Output MaskingGEMINI.md
Qwen Code单阈值压缩(70%)QWEN.md + 简单笔记
Copilot CLI上下文管理 + 检查点copilot-instructions.md有限
CursorVS Code 会话管理.cursorrulesIDE 级恢复

计数规则:源码行数基于 TypeScript 文件的 wc -l 统计。7.1.1 表格「目录总规模」列为子系统所有文件合计;括号内为核心文件精确 LOC。全文 ~73,000 行为所有 session/memory 相关目录(含 Token 管理、对话恢复、并发管理等辅助模块)的总计。

7.1 架构总览

7.1.1 子系统全景

子系统目录总规模核心文件(单文件 LOC)职责
会话存储~9,938 LOCutils/sessionStorage.ts (5,105)JSONL 持久化、会话恢复、并发管理
上下文压缩~3,960 LOCservices/compact/compact.ts (1,706)5 层递增压缩策略
Token 管理~3,327 LOCutils/analyzeContext.ts (1,382)预算分配、用量估算、阈值告警
CLAUDE.md 记忆~1,876 LOCutils/claudemd.ts (1,480)6 层发现、@include 指令、条件加载
Memdir 系统~1,736 LOCmemdir/memdir.ts (507)记忆目录管理、路径解析
自动记忆提取~769 LOCservices/extractMemories/extractMemories.ts (616)对话结束后后台提取记忆
SessionMemory~1,026 LOCservices/SessionMemory/sessionMemory.ts (495) + prompts.ts (324) + utils.ts (207)轻量级会话内记忆缓存
autoDream~550 LOCservices/autoDream/autoDream.ts (325)后台记忆整合(24h + 5 会话门控)
团队记忆同步~2,167 LOCservices/teamMemorySync/index.ts (1,257)本地↔云端双向同步
Worktree~2,888 LOCutils/worktree.ts (1,519)Git worktree 隔离会话
文件检查点~1,116 LOCutils/fileHistory.ts (1,116)文件级快照/回退

计数说明:「目录总规模」是该子系统所有文件的 LOC 合计(含辅助文件、类型定义、UI 组件等)。「核心文件」是其中最大的单一文件,括号内为其精确 LOC。

7.1.2 会话生命周期

┌─────────────┐     ┌─────────────────────────────┐     ┌─────────────┐
│  创建         │     │  活跃                         │     │  恢复/归档   │
│             │     │                             │     │             │
│ 懒创建 JSONL  │────▶│ 追加消息                      │────▶│ --resume    │
│ 注册 PID      │     │ PID 状态更新                  │     │ --continue  │
│ 初始化记忆    │     │ 工具结果持久化                 │     │ --fork      │
│             │     │  ┌──────────────────┐        │     │ 对话链回放   │
│             │     │  │ 上下文压缩(异步) │        │     │             │
│             │     │  │ 5层递增 / Token预算│        │     │             │
│             │     │  └──────────────────┘        │     │             │
└─────────────┘     └─────────────────────────────┘     └─────────────┘

关键设计

  • 会话文件懒创建:第一条 user/assistant 消息时才创建 JSONL,之前元数据缓存在内存 pendingEntries
  • Append-only JSONL:消息只追加、不原地更新。删除使用尾部拼接(tail-splice)提高效率
  • PID 文件并发管理~/.claude/sessions/{pid}.json 跟踪运行中会话,claude ps 可查看
  • 上下文压缩是活跃阶段的异步子任务,不是独立的线性阶段——在 token 用量超过阈值时自动触发,也可通过 /compact 手动触发

7.2 会话存储

7.2.1 存储格式

源码:utils/sessionStorage.ts

会话文件路径~/.claude/projects/{sanitized_project_path}/{sessionId}.jsonl

每行一个 JSON 条目,核心字段:

{
  "type": "user|assistant|attachment|system|last-prompt|custom-title|tag|...",
  "uuid": "...",
  "parentUuid": "...",
  "timestamp": "...",
  "sessionId": "..."
}

元数据条目类型(通过 reAppendSessionMetadata() 持久化):

条目类型用途
last-prompt最后用户提示文本
custom-title用户/AI 设置的会话标题
tag会话标签
agent-name / agent-color / agent-settingAgent 上下文
modecoordinator vs normal 模式
worktree-stateWorktree 会话状态(三态:undefined/null/object)
pr-link关联的 PR 元数据
file-history-snapshot文件检查点快照
content-replacement缓存优化替换记录

子代理转录路径{projectDir}/{sessionId}/subagents/agent-{agentId}.jsonl

文件权限0o600(仅所有者可读写),目录 0o700

7.2.2 关键常量

源码:utils/sessionStorage.ts

常量用途
MAX_TRANSCRIPT_READ_BYTES50 MB防止 OOM
MAX_TOMBSTONE_REWRITE_BYTES50 MB尾部拼接安全限制
FLUSH_INTERVAL_MS100 ms写队列排空间隔
MAX_CHUNK_BYTES100 MB单次写入最大块

7.2.3 Project 单例与写队列

源码:utils/sessionStorage.tsProject

Project 是管理所有写入的单例,采用写队列模式

  • 条目缓存在 writeQueues(按文件的 Map)
  • 每 100ms 通过 scheduleDrain() / drainWriteQueue() 排空
  • 支持等待排空的 flushSessionStorage()

缓存的元数据字段:

currentSessionTag, currentSessionTitle, currentSessionAgentName,
currentSessionAgentColor, currentSessionLastPrompt,
currentSessionAgentSetting, currentSessionMode ('coordinator' | 'normal'),
currentSessionWorktree (三态), currentSessionPrNumber, ...

7.2.4 消息链构建

源码:utils/sessionStorage.tsbuildConversationChain(byUuid, tip)

从叶子节点沿 parentUuid 链回溯到根节点,构建完整的对话树。progress 条目被排除在链外——它们是 UI 临时状态,包含会导致恢复时产生链分叉(bugs #14373, #23537)。

7.2.5 轻量元数据读取

源码:utils/sessionStoragePortable.ts

readLiteMetadata() 只读取 JSONL 文件最后 ~64KB 的尾部窗口提取元数据,无需扫描全文件。会话退出时,reAppendSessionMetadata() 将元数据重写到 EOF,确保尾部窗口可找到最新元数据。

设计理由:对于长会话(数万条消息),全文件扫描可能需要数秒。尾部窗口方案将元数据读取降至毫秒级。

7.3 会话恢复

7.3.1 恢复入口

# 恢复最近会话
claude --resume

# 恢复指定会话
claude --resume <session-id>

# 继续上次对话
claude -c

# 恢复并创建新会话 ID(不覆盖原会话)
claude --resume <session-id> --fork-session

# 关联 PR 恢复
claude --from-pr 123

7.3.2 恢复流程

源码:utils/conversationRecovery.tsutils/sessionRestore.ts

loadConversationForResume(source)
  ├── 1. 定位 JSONL 文件(最新 / session-id / 预加载)
  ├── 2. 反序列化 + 过滤
  │     ├── migrateLegacyAttachmentTypes()    # new_file→file, new_directory→directory
  │     ├── filterUnresolvedToolUses()        # 移除未配对的 tool_use/tool_result
  │     ├── filterOrphanedThinkingOnlyMessages()
  │     └── filterWhitespaceOnlyAssistantMessages()
  ├── 3. 回合中断检测 (detectTurnInterruption)
  │     ├── 'none': 最后一轮已完成
  │     ├── 'interrupted_prompt': 用户已发送但模型未响应
  │     └── 'interrupted_turn': 模型执行中中断 → 注入 "Continue from where you left off."
  ├── 4. 恢复 Skill 状态
  └── 5. 触发 SessionStart hooks (resume type)

processResumedConversation(result)
  ├── 1. 匹配 coordinator/normal 模式
  ├── 2. 设置 session ID(或 fork 新 ID)
  ├── 3. 恢复会话元数据(标题、标签、Agent、Worktree、PR)
  ├── 4. 恢复 Worktree 目录(chdir)
  ├── 5. 恢复 Agent 定义和模型覆盖
  └── 6. 更新 PID 文件

7.3.3 回合中断检测

源码:utils/conversationRecovery.tsdetectTurnInterruption()

中断类型判定条件恢复行为
none最后一条是 assistant/system/meta直接恢复
interrupted_prompt最后一条是普通用户消息注入 "Continue from where you left off."
interrupted_turn最后一条是 tool_resultattachment转为 interrupted_prompt 并注入续行消息

特殊处理

  • Brief 模式SendUserMessage 的 tool_result 是终端状态(回合已完成),不会误判为中断
  • API 错误消息:在查找最后一条回合相关消息时被跳过

7.3.4 跨项目恢复

源码:utils/crossProjectResume.ts

--resume 指定的会话属于不同项目目录时,系统检测跨项目情况并区分处理:

  • 同 repo worktree:直接恢复,无需切换目录
  • 不同项目:返回 cd '/path' && claude --resume <sessionId> 命令,由调用方决定是否执行

:Worktree 检测当前仅对 USER_TYPE === 'ant' 用户启用(分阶段推送)。非 ant 用户始终走命令提示路径。

7.4 上下文压缩(5 层递增策略)

上下文压缩是 Claude Code 最复杂的子系统之一,按成本递增分为 5 层,每一层在上一层无法释放足够空间时触发。

7.4.1 压缩层次总览

层级机制触发时机信息损失源码
L1: 缓存编辑微压缩cache_edits API 删除旧工具结果每次 API 调用前(仅 Ant)工具输出文本services/compact/microCompact.ts
L2: 时间微压缩就地清除过期工具结果服务器缓存已过期时工具输出文本services/compact/microCompact.ts
L3: SessionMemory 压缩基于 SessionMemory 裁剪旧消息自动压缩触发后优先尝试(与 L4 共享阈值 ~83.5%)旧对话轮次services/compact/sessionMemoryCompact.ts
L4: 完整压缩(摘要)Fork 子代理生成摘要替代历史L3 失败后、手动 /compact大部分对话细节services/compact/compact.ts
L5: 反应式压缩API 413 应急压缩API 返回 prompt_too_long可能严重query.ts

7.4.2 Token 预算计算

源码:services/compact/autoCompact.ts, utils/tokens.ts

原始上下文窗口:     200,000 tokens(默认模型)
                    1,000,000 tokens(Sonnet 4 / Opus 4-6)

摘要预留:           min(modelMaxOutput, 20,000) tokens
                    (基于 p99.99 = 17,387 tokens)

有效上下文窗口:     rawWindow - summaryReserved
                    = 200,000 - 20,000 = 180,000

自动压缩阈值:       effectiveWindow - 13,000
                    = 180,000 - 13,000 = 167,000 (~83.5%)

警告阈值:           autoCompactThreshold - 20,000
错误阈值:           autoCompactThreshold - 20,000
阻塞限制:           effectiveWindow - 3,000

Token 估算函数 tokenCountWithEstimation()utils/tokens.ts):

  • 取最后一次 API 响应的 usage(input + output + cache tokens)
  • 加上自此之后追加消息的粗略估算
  • 按 4/3 比例填充(保守估计)
  • 处理并行工具调用:回溯到相同 message.id 的第一个兄弟消息

7.4.3 L1/L2: 微压缩

源码:services/compact/microCompact.ts (531 LOC)

可压缩工具集合 (COMPACTABLE_TOOLS):FileRead, Shell, Grep, Glob, WebSearch, WebFetch, FileEdit, FileWrite

L1: 缓存编辑微压缩(仅 Ant,Feature gate: CACHED_MICROCOMPACT):

  • 使用 Anthropic cache_edits API,直接在缓存前缀中删除工具结果
  • 不使缓存失效——无需重发内容
  • 仅修改缓存删除指令,本地内容不变

L2: 时间微压缩

  • 检测距最后一次 assistant 消息的时间差是否超过阈值(服务器缓存已过期)
  • 将旧工具结果内容替换为 [Old tool result content cleared]
  • 由于缓存已过期,清除内容"免费"(不会增加重写成本)

设计理由:微压缩只在主线程(isMainThreadSource)执行。源码注释说明:"Forked agents (session_memory, prompt_suggestion, etc.) would cause the main thread to try deleting tools that don't exist in its own conversation."

7.4.4 L3: SessionMemory 压缩

源码:services/compact/sessionMemoryCompact.ts (631 LOC)

实验性结构感知压缩,在完整压缩前优先尝试。

配置 (DEFAULT_SM_COMPACT_CONFIG):

参数默认值用途
minTokens10,000最少保留 token 数
minTextBlockMessages5最少保留含文本块消息数
maxTokens40,000保留 token 硬上限

流程

  1. 检查 SessionMemory 是否可用且非空
  2. 找到上一次压缩的 lastSummarizedMessageId
  3. 计算裁剪点:向后扩展以满足最低 token/消息约束
  4. adjustIndexToPreserveAPIInvariants() 确保不拆分 tool_use/tool_result 对或 thinking 块
  5. 创建压缩边界消息并构建后压缩消息

设计理由adjustIndexToPreserveAPIInvariants() 处理一个微妙的流式工件——"streaming yields separate messages per content block (thinking, tool_use, etc.) with the same message.id but different uuids"。如果拆分这些组,会导致 "API error: orphan tool_result references non-existent tool_use"。

7.4.5 L4: 完整压缩

源码:services/compact/compact.ts (1,706 LOC)

核心函数compactConversation()

完整压缩流程

1. 执行 PreCompact hooks(合并 hook 指令与用户指令)
2. 构建压缩提示词(prompt.ts)
3. 尝试 forked-agent 路径(复用主线程的 prompt cache)
   ├── 成功 → 使用结果
   └── 失败 → 回退到流式路径(常规 API 调用)
4. 如果响应为 prompt_too_long:
   └── truncateHeadForPTLRetry() 丢弃最旧 API-round 组,重试(最多 3 次)
5. 后压缩清理:
   ├── 清除文件读取缓存
   ├── 重新生成附件(文件、计划、Skill、延迟工具、MCP 指令、Agent 列表)
   └── 执行 SessionStart hooks + PostCompact hooks
6. 重置 cache-break 检测基线(notifyCompaction)
7. 记录遥测(tengu_compact 事件)

关键常量

常量用途
POST_COMPACT_MAX_FILES_TO_RESTORE5压缩后重新注入的最大文件数
POST_COMPACT_TOKEN_BUDGET50,000重新注入文件的总 token 预算
POST_COMPACT_MAX_TOKENS_PER_FILE5,000每个文件最大 token
POST_COMPACT_SKILLS_TOKEN_BUDGET25,000Skill 总预算(~5 个 Skill)
POST_COMPACT_MAX_TOKENS_PER_SKILL5,000每个 Skill 最大 token
MAX_PTL_RETRIES3prompt-too-long 重试次数

部分压缩 (partialCompactConversation):

  • 'from' 方向:总结 pivotIndex 之后的消息(保留 prompt cache 前缀)
  • 'up_to' 方向:总结 pivotIndex 之前的消息(会失效 cache)

CompactionResult 接口

interface CompactionResult {
  boundaryMarker: string           // 压缩边界标记
  summaryMessages: Message[]       // 生成的摘要消息(替代被压缩的对话)
  attachments: Attachment[]        // 附件(保留)
  hookResults: HookResult[]        // Hook 执行结果
  messagesToKeep?: Message[]       // 未被压缩的保留消息
  preCompactTokenCount?: number    // 压缩前 token 数
  postCompactTokenCount?: number   // 压缩后 token 数
}

7.4.6 压缩提示词

源码:services/compact/prompt.ts (374 LOC)

提示词结构(3 段式):

┌─────────────────────────────────┐
│ NO_TOOLS_PREAMBLE               │ ← "Respond with TEXT ONLY"(置于最前)
│   防止 Sonnet 4.6+ 自适应思考    │   Sonnet 4.6 失败率 2.79% vs 4.5 的 0.01%
├─────────────────────────────────┤
│ <analysis> 暂存块               │ ← 模型组织思路的草稿区
├─────────────────────────────────┤
│ 9 段摘要模板                    │
│  1. Primary Request and Intent  │
│  2. Key Technical Concepts      │
│  3. Files and Code Sections     │
│  4. Errors and Fixes            │
│  5. Problem Solving             │
│  6. All User Messages           │
│  7. Pending Tasks               │
│  8. Current Work                │
│  9. Optional Next Step          │
├─────────────────────────────────┤
│ NO_TOOLS_TRAILER                │ ← 强化"不要调用工具"
└─────────────────────────────────┘

formatCompactSummary() 处理输出:去除 <analysis> 草稿块,将 <summary> XML 标签替换为纯文本标题。

7.4.7 自动压缩触发

源码:services/compact/autoCompact.ts (351 LOC)

触发门控链

1. 查询源检查 → 跳过 session_memory/compact 查询(避免死锁)
2. Feature flag → REACTIVE_COMPACT 抑制主动压缩
                  CONTEXT_COLLAPSE 抑制(会与 collapse 竞争)
3. 阈值检查 → tokenCount >= effectiveWindow - 13,000
4. 优先尝试 SessionMemory 压缩
   └── 失败 → 回退到完整压缩

熔断器MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3,连续 3 次失败后停止重试。

设计理由:源码注释说明熔断器的起因——"was causing ~250K wasted API calls/day"。在不可恢复的超大会话中,1,279 个会话产生 50+ 次失败。

环境变量覆盖

变量用途
CLAUDE_CODE_AUTO_COMPACT_WINDOW覆盖有效上下文窗口
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE按百分比触发
DISABLE_COMPACT禁用所有压缩
DISABLE_AUTO_COMPACT禁用自动但保留手动 /compact

7.4.8 工具结果持久化

源码:utils/toolResultStorage.ts

在压缩之外,大型工具结果通过独立机制管理:

三层大小管理

层级机制阈值
单结果持久化超过阈值的结果保存到磁盘,模型收到路径 + 2KB 预览min(tool.maxResultSizeChars, 50,000) 字符
聚合消息预算单用户消息内的并行工具结果总预算200,000 字符(MAX_TOOL_RESULTS_PER_MESSAGE_CHARS
微压缩清除就地替换旧工具结果为 [Old tool result content cleared]时间/缓存过期触发

缓存稳定性:一旦工具结果被"看过",它就被"冻结"(要么已替换,要么保持原样)。每次回合重新应用相同决策,以保持 prompt cache 前缀。

7.4.9 消息分组

源码:services/compact/grouping.ts (63 LOC)

消息按 API-round 边界(新的 assistant.message.id)分组,确保压缩时不会拆分 tool_use/tool_result 对。

7.5 CLAUDE.md 记忆系统

7.5.1 六层发现层级

源码:utils/claudemd.ts (1,479 LOC)

层级名称路径作用域可排除
1Managed/etc/claude-code/CLAUDE.md系统管理员策略(所有用户)
2User~/.claude/CLAUDE.md + ~/.claude/rules/*.md私人全局指令
3ProjectCLAUDE.md, .claude/CLAUDE.md, .claude/rules/*.md团队共享(提交到 Git)
4LocalCLAUDE.local.md(项目根目录)私人项目级(gitignored)
5AutoMem~/.claude/projects/<slug>/memory/MEMORY.md自动生成
6TeamMem<autoMemPath>/team/MEMORY.md团队共享(Feature flag)

加载优先级后加载的优先级更高——模型对后面出现的内容关注度更高。在 Project/Local 层内,越靠近 CWD 的文件越后加载(优先级越高)。目录遍历从根向 CWD 进行。

设计理由:源码注释说明:"Files loaded LATER have HIGHER priority because the model pays more attention to later content." 这与直觉相反(通常先加载的更重要),但符合 LLM 注意力机制的特性。

层 3 vs 层 4 说明:Project 和 Local 虽在同一目录遍历中加载,但它们是独立的层级——使用不同的 MemoryType 标记('Project' vs 'Local'),受不同的 isSettingSourceEnabled 门控(projectSettings vs localSettings),且 Local 不受 worktree 去重影响。

7.5.2 @include 指令系统

源码:utils/claudemd.ts

语法@path, @./relative/path, ~/home/path, @/absolute/path

约束

  • 只在叶子文本节点中工作(不在代码块内)
  • 支持递归,最大深度 MAX_INCLUDE_DEPTH = 5
  • 通过 processedPaths 集合防止循环引用
  • 支持 70+ 文本文件扩展名(白名单 TEXT_FILE_EXTENSIONS
  • HTML 注释被剥离;frontmatter 解析用于基于 glob 的条件规则

设置源门控:每个层级受设置源控制——userSettingsprojectSettingslocalSettings 分别控制各层启用。claudeMdExcludes 模式可跳过特定 User/Project/Local 文件(Managed/AutoMem/TeamMem 永不排除)。

7.5.3 getMemoryFiles() 函数

源码:utils/claudemd.ts:790(memoized)

处理顺序:

  1. Managed → User → Project/Local(root→CWD 遍历)→ --add-dir 目录 → AutoMem → TeamMem
  2. 每步调用 processMemoryFile() 处理 @include 递归
  3. getClaudeMds() 格式化为统一提示词字符串

头部声明"Codebase and user instructions are shown below. These instructions OVERRIDE any default behavior."

每个文件附带描述标签(如 "project instructions, checked into the codebase")。

7.5.4 AutoMem 路径解析

源码:memdir/paths.ts (278 LOC)

getAutoMemPath() 优先级链:

  1. CLAUDE_COWORK_MEMORY_PATH_OVERRIDE 环境变量(SDK/Cowork 全路径覆盖)
  2. settings.json 中的 autoMemoryDirectory(仅限信任源:policy/flag/local/user,排除 projectSettings 防止注入)
  3. 默认:<memoryBase>/projects/<sanitized-git-root>/memory/

路径安全validateMemoryPath()):拒绝相对路径、根路径(长度 < 3)、Windows 驱动器根、UNC 路径、空字节、裸 ~ 展开。

7.5.5 记忆内容类型

源码:memdir/memoryTypes.ts (271 LOC)

类型范围内容
user始终私人用户角色、目标、偏好、知识水平
feedback默认私人;团队级可共享纠正和确认的方法
project偏向团队进行中的工作、目标、截止日期
reference通常团队外部系统指针(Linear 项目、Grafana 仪表板)

内容排除规则 (WHAT_NOT_TO_SAVE_SECTION):

  • 不保存代码模式/架构/文件路径(可从项目状态推导)
  • 不保存 git 历史(使用 git log/git blame
  • 不保存调试解决方案(修复已在代码中)
  • 不保存已在 CLAUDE.md 中的内容
  • 不保存临时任务细节
  • 即使显式保存,这些排除规则仍然适用

截断限制:MEMORY.md 上限 200 行 25KB。

7.6 自动记忆提取

7.6.1 ExtractMemories(回合结束后台代理)

源码:services/extractMemories/extractMemories.ts (616 LOC)

在每个完整查询循环结束时运行(模型产生最终响应且无工具调用时),通过 handleStopHooks 触发。

特性

  • Feature gate:tengu_passport_quail(提取模式)+ tengu_slate_thimble(非交互式会话)
  • 使用 runForkedAgent()——完美复制主对话,共享 prompt cache
  • 与主代理互斥:如果主代理在当轮已写入记忆(hasMemoryWritesSince() 检测),后台代理跳过该范围
  • 允许工具:FileEdit, FileWrite, FileRead, Glob, Grep, Bash(只读), REPL
  • createAutoMemCanUseTool() 工厂约束工具只能操作自动记忆目录

7.6.2 SessionMemory(会话内记忆缓存)

源码:services/SessionMemory/ (3 文件共 1,026 LOC:sessionMemory.ts 495 + prompts.ts 324 + sessionMemoryUtils.ts 207)

轻量级会话内记忆,通过 forked subagent 定期提取。

特性

  • Feature gate:tengu_session_memory GrowthBook flag
  • 初始化阈值:token 数超过可配置阈值时触发
  • 更新阈值:需要最低 token 增长 + 最低工具调用次数(自上次提取以来)
  • 使用 runForkedAgent() 共享 prompt cache
  • 写入会话作用域记忆文件(getSessionMemoryPath()
  • 可自定义:~/.claude/session-memory/config/template.mdprompt.md 覆盖默认提示词

7.6.3 autoDream(后台记忆整合)

源码:services/autoDream/autoDream.ts (325 LOC)

当时间门控和会话数门控同时通过时,以 forked subagent 运行 /dream 提示词。

双触发门控 + 执行锁(从最便宜的开始):

阶段类型条件默认值
时间门控触发条件距上次整合 >= minHours24h(GrowthBook tengu_onyx_plover
会话门控触发条件mtime > lastConsolidatedAt 的会话数 >= minSessions5
锁门控执行保护无其他进程正在整合文件锁 tryAcquireConsolidationLock()

额外约束

  • 扫描节流:会话目录扫描间隔最少 10 分钟(SESSION_SCAN_INTERVAL_MS
  • 排除当前会话
  • KAIROS 模式(长期助手会话):追加时间戳条目到日期日志文件,夜间 /dream 整合
  • 成功时记录 tengu_auto_dream_completed 遥测(含 cache hit/creation 指标)
  • 失败时回滚锁 mtime 使时间门控再次通过

7.7 团队记忆同步

7.7.1 架构

源码:services/teamMemorySync/index.ts (1,257 LOC)

团队记忆在本地文件系统与 Anthropic 服务器 API 之间同步,按仓库(通过 git remote hash 标识)划分作用域,组织内所有认证成员共享。

7.7.2 API 契约

操作方法说明
拉取GET /api/claude_code/team_memory?repo={owner/repo}支持 ETag/304
校验和GET ...&view=hashes仅获取 SHA-256,用于冲突检测
推送PUT /api/claude_code/team_memory?repo={owner/repo}Upsert 语义

7.7.3 同步语义

操作行为
Pull服务器优先(本地文件被覆盖)
Push增量上传——仅推送 SHA-256 内容哈希不同的条目
删除不传播删除本地文件不会从服务器删除;下次 pull 会恢复

⚠️ 推断:删除不传播的设计可能是为了防止一个成员的误操作影响整个团队,但会增加协调成本。

7.7.4 安全措施

源码:services/teamMemorySync/secretScanner.ts (324 LOC)

措施实现
密钥扫描Push 前运行 scanForSecrets() 防止凭据泄露
路径验证validateTeamMemKey() 防止路径遍历
设置源信任仅 policy/flag/local/user 可配置团队记忆路径(排除 projectSettings
认证需要第一方 OAuth(CLAUDE_AI_INFERENCE_SCOPE + CLAUDE_AI_PROFILE_SCOPE

关键常量

常量
MAX_FILE_SIZE_BYTES250,000 / 条目
MAX_PUT_BODY_BYTES200,000(网关限制;大批量拆分为顺序 PUT)
TEAM_MEMORY_SYNC_TIMEOUT_MS30,000
MAX_RETRIES3
MAX_CONFLICT_RETRIES2

7.8 Worktree 隔离

7.8.1 概述

源码:utils/worktree.ts (1,519 LOC) + tools/EnterWorktreeTool/ (177 LOC) + tools/ExitWorktreeTool/ (386 LOC)

Git worktree 允许多个 Claude Code 实例在不同分支上并行工作,互不干扰。

7.8.2 Worktree 创建

# CLI 方式
claude --worktree [name]
claude --worktree --tmux    # 在 tmux 会话中运行

# 工具方式(会话内动态切换)
EnterWorktree: { name?: string }
ExitWorktree:  { action: 'keep' | 'remove', discard_changes?: boolean }

创建流程getOrCreateWorktree):

  1. 快速恢复路径:直接读取 .git 指针文件(无需 subprocess,节省 ~15ms)
  2. 新 worktree 路径
    • 获取基础分支(如果 origin/<branch> 已本地存在则跳过 fetch,节省 6-8s)
    • git worktree add -B
    • 支持 --no-checkout + sparse-checkout set --cone 用于大仓库
  3. 后创建设置performPostCreationSetup):
    • 复制 settings.local.json
    • 配置 core.hooksPath(husky 兼容)
    • 软链接配置目录(如 node_modules,避免磁盘膨胀)
    • 复制 .worktreeinclude 文件
    • 安装 commit attribution hook(installPrepareCommitMsgHook,Feature flag COMMIT_ATTRIBUTION

Worktree 存储位置.claude/worktrees/<flattened-slug>

Slug 验证validateWorktreeSlug):

  • 拒绝路径遍历(..、绝对路径)
  • 每段验证:/^[a-zA-Z0-9._-]+$/
  • 最大 64 字符
  • flattenSlug()/ 替换为 +,避免 git ref D/F 冲突

7.8.3 Worktree 清理

操作行为
keep保留 worktree + 分支在磁盘上
removegit worktree remove --force + git branch -D(100ms 等待锁释放)

过期清理cleanupStaleAgentWorktrees):

  • 仅清理匹配 6 种临时模式(EPHEMERAL_WORKTREE_PATTERNS)的 slug
  • Fail-closedgit status 失败或有 tracked changes 时跳过;未从 remote 可达的 commit 也跳过
  • hasWorktreeChanges() 检查脏工作树 + 新 commit

7.8.4 ExitWorktree 安全验证

源码:tools/ExitWorktreeTool/ExitWorktreeTool.ts

validateInput() 中的安全门:

  1. 拒绝无活跃会话的情况
  2. action: 'remove' 且未设 discard_changes: true 时:
    • 如果 countWorktreeChanges 返回 null(无法验证状态)→ 失败
    • 如果有未提交文件或新 commit → 失败(列出精确数量)

这是防止数据丢失的唯一安全门。

7.9 文件检查点与回退

7.9.1 概述

源码:utils/fileHistory.ts (1,116 LOC)

Claude Code 在每次工具调用前自动创建文件快照,支持精确到文件级别的回退。

7.9.2 存储格式

备份路径~/.claude/file-history/<sessionId>/<hash>@v<N>

  • hashsha256(filePath) 前 16 个十六进制字符
  • 版本号:每文件单调递增
  • null backupFileName = 文件在当时的版本不存在
  • 路径存储为相对于 originalCwd(节省空间)

7.9.3 核心数据结构

type FileHistoryBackup = {
  backupFileName: string | null  // null = 文件不存在
  version: number
  backupTime: Date
}

type FileHistorySnapshot = {
  messageId: UUID
  trackedFileBackups: Record<string, FileHistoryBackup>
  timestamp: Date
}

type FileHistoryState = {
  snapshots: FileHistorySnapshot[]
  trackedFiles: Set<string>
  snapshotSequence: number  // 单调计数器,永不重置
}

快照上限MAX_SNAPSHOTS = 100

7.9.4 三阶段模式

所有操作采用同步捕获 → 异步 I/O → 同步提交三阶段:

fileHistoryTrackEdit(文件写入前调用):

  1. 阶段 1(sync):检查文件是否已在最新快照中被跟踪
  2. 阶段 2(async):createBackup() 创建备份
  3. 阶段 3(sync):提交到状态(重新检查竞争条件)

fileHistoryMakeSnapshot(每回合调用一次):

  1. 阶段 1(sync):通过 no-op updater 捕获状态
  2. 阶段 2(async):对每个跟踪文件 stat + 与最新备份比较(mtime 优化跳过未修改文件)
  3. 阶段 3(sync):追加快照,淘汰到 MAX_SNAPSHOTS

7.9.5 回退机制

fileHistoryRewind(state, messageId):恢复所有跟踪文件到指定 messageId 对应快照时的状态。

applySnapshot() 行为:

  • 删除目标版本中不存在的文件
  • 通过 restoreBackup() 恢复文件(复制 + 恢复权限)
  • 仅写入与当前状态不同的文件

辅助函数

  • fileHistoryGetDiffStats():干运行回退,返回 { filesChanged, insertions, deletions }
  • fileHistoryHasAnyChanges():轻量布尔检查,第一个变化文件即返回

7.9.6 恢复支持

源码:copyFileHistoryForResume(log) — 通过硬链接(fs.link,回退到复制)将备份从旧会话迁移到新会话。仅记录所有备份迁移成功的快照。

7.9.7 启用条件

fileHistoryEnabled():
  // 交互模式:默认启用
  //   禁用条件:fileCheckpointingEnabled === false 或 CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING

fileHistoryEnabledSdk():
  // SDK/非交互模式:默认禁用
  //   需显式启用:CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true
  //   且未被 CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING 覆盖

设计理由:SDK 模式默认禁用是因为自动化脚本通常在无用户交互的环境运行,文件快照的磁盘开销和 I/O 延迟可能不可接受。

7.10 并发会话管理

7.10.1 PID 文件系统

源码:utils/concurrentSessions.ts (204 LOC)

PID 目录~/.claude/sessions/ PID 文件{process.pid}.json

{
  "pid": "<process.pid>",
  "sessionId": "<UUID>",
  "cwd": "<original working dir>",
  "startedAt": "<timestamp>",
  "kind": "<interactive|bg|daemon|daemon-worker>",
  "status": "<busy|idle|waiting>",
  "name": "<session name>",
  "bridgeSessionId": "<remote session ID or null>"
}

会话类型 (SessionKind):

类型说明
interactive交互式 CLI 会话
bg后台 tmux 会话(claude --bg
daemon守护进程会话
daemon-worker守护进程工作线程

会话状态 (SessionStatus):

状态说明
busy正在处理
idle等待输入
waiting阻塞等待特定条件

过期清理countConcurrentSessions() 时自动删除进程不存在的 PID 文件(WSL 上跳过,因为 WSL 无法通过 /proc 探测 Windows 宿主机的 PID,防止误删正在运行的宿主进程)。严格的文件名验证 /^\d+\.json$/ 防止误删。

7.11 MCP 集成

7.11.1 传输协议

协议说明适用场景
stdio标准输入/输出本地进程,最常用
sseServer-Sent Events远程服务器
streamable-http可流式 HTTP云端部署

7.11.2 配置

CLI 方式--mcp-config <configs...> 加载 MCP 服务器配置(JSON 文件或字符串)

项目级.claude/settings.json):

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": { "GITHUB_TOKEN": "ghp_xxx" }
    },
    "remote-server": {
      "type": "sse",
      "url": "https://mcp.example.com/sse"
    }
  }
}

7.11.3 工具命名与限制

MCP 工具以 mcp__serverName__toolName 格式注册(双下划线分隔)。

关键常量

常量用途
MAX_MCP_OUTPUT_TOKENS25,000MCP 输出 token 限制(可通过环境变量覆盖)
MCP 截断乘数0.5输出超过限制时截断到 50%

MCP 相关工具(详见 04-tools.md 4.8 节):

  • McpAuthTool:未认证 MCP 服务器的 OAuth 流程
  • ReadMcpResourceTool:读取 MCP 资源
  • ListMcpResourcesTool:列出 MCP 资源

SDK 控制消息(MCP 管理用):

  • mcp_status:查询 MCP 服务器状态
  • mcp_message:发送 MCP 消息
  • mcp_set_servers:动态设置 MCP 服务器
  • mcp_reconnect:重新连接 MCP 服务器
  • mcp_toggle:启用/禁用 MCP 服务器

7.12 实现者 Checklist

对于设计会话和记忆系统的 Code Agent 开发者:

会话持久化

  • 采用 append-only JSONL 格式(避免原地更新的并发问题)
  • 元数据尾部重写(快速元数据读取无需全文件扫描)
  • PID 文件跟踪运行中会话(支持 ps 命令和并发限制)
  • 消息链使用 parentUuid 树结构(支持分叉和恢复)
  • 懒创建会话文件(避免空会话产生垃圾文件)
  • SIGINT/SIGTERM 信号处理:优雅退出时强制排空 pendingEntries 队列,保证 JSONL 完整性

上下文压缩

  • 实现多层递增压缩策略(从低成本到高成本逐层尝试)
  • 微压缩优先清除已过期的缓存内容(零额外成本)
  • SessionMemory 压缩在完整压缩前尝试(保留更多信息)
  • 完整压缩使用 forked-agent 复用 prompt cache(降低成本)
  • 熔断器防止连续压缩失败浪费 API 调用
  • prompt-too-long 时丢弃最旧 API-round 组重试

记忆系统

  • 多层发现(全局/项目/目录级)覆盖不同使用场景
  • 加载顺序与优先级一致(后加载 = 高优先级)
  • @include 支持递归引用,设置循环引用检测和深度限制
  • 记忆内容排除规则防止保存可推导信息
  • 后台提取与主代理互斥(避免同一范围重复写入)
  • 团队记忆密钥扫描防止凭据泄露

Worktree 隔离

  • VCS-agnostic hook 接口(不绑定特定 VCS)
  • Fail-closed 清理策略(有未提交变更时拒绝删除)
  • 软链接大目录(node_modules)避免磁盘膨胀
  • 过期清理仅匹配临时模式(防止误删用户 worktree)

文件检查点

  • 三阶段操作模式防止 React 重渲染风暴
  • 硬链接迁移备份(跨会话恢复时节省磁盘)
  • mtime 快速路径跳过未修改文件
  • 版本号去重(相同内容文件共享 {hash}@v1 备份名)

7.13 设计哲学与架构权衡

决策选择替代方案权衡
会话存储格式Append-only JSONLSQLite / LevelDB写入并发零冲突、崩溃恢复简单;查询需全扫描
5 层递增压缩L1→L2→L3→L4→L5 逐层升级单层智能压缩渐进降级保证可用性;实现复杂度高
CLAUDE.md 优先级后加载 = 高优先级先加载 = 高优先级符合 LLM 注意力机制(模型更关注 prompt 尾部)
团队记忆删除删除不传播双向同步删除防止误操作影响全团队;本地删除仅影响本地
文件检查点模式sync→async→sync纯异步防止 React 重渲染风暴;微小延迟开销
autoDream 触发双门控 + 文件锁定时任务资源高效(仅达标时运行);整合可能延迟
文件备份命名hash@vN时间戳去重(相同内容共享备份)+ 确定性可验证