Skip to content

Latest commit

 

History

History
138 lines (94 loc) · 5.57 KB

File metadata and controls

138 lines (94 loc) · 5.57 KB

33. Session Memory 调度与并发控制

所属专题簇:记忆与上下文算法深挖

建议前读:21. 记忆系统:CLAUDE.md、Session Memory 与 Agent Memory

建议后读:19. 上下文压缩与历史治理16. 会话持久化与恢复机制

研究问题

Claude Code 什么时候决定“该写 session memory 了”,以及它如何避免后台抽取把主会话搞乱?

一句话结论

Session Memory 不是随手记录,而是一套带阈值、停顿点判定和防重入控制的后台调度机制;它把“什么时候值得记”和“什么时候安全地记”分开处理。

这篇讲什么

这一章聚焦 SessionMemory 的调度算法、并发控制和运行时不变量,而不是只解释“它会生成一个 markdown 文件”。

如果你不看源码,只看这一章,应该记住什么

  • Session Memory 的触发有硬阈值,不是每轮都写。
  • token 增长是硬前提,tool call 数和自然停顿点是协同条件。
  • Claude Code 明确防止重复 extraction、超时卡死和会话推进期间的状态踩踏。

源码依据

Mermaid 图:Session Memory 触发状态图

flowchart TD
    A["新一轮 messages"] --> B["tokenCountWithEstimation"]
    B --> C{"已初始化?"}
    C -- 否 --> D{"达到 init token threshold?"}
    D -- 否 --> Z["不抽取"]
    D -- 是 --> E["markSessionMemoryInitialized"]
    C -- 是 --> F{"达到 update token threshold?"}
    E --> F
    F -- 否 --> Z
    F -- 是 --> G["count tool calls since last memory"]
    G --> H{"tool call threshold 达标?"}
    H -- 是 --> I["触发 extraction"]
    H -- 否 --> J{"最后一轮无 tool calls?"}
    J -- 否 --> Z
    J -- 是 --> I
    I --> K["markExtractionStarted"]
    K --> L["forked subagent 更新 notes"]
    L --> M["recordExtractionTokenCount / setLastSummarizedMessageId / markExtractionCompleted"]
Loading

触发算法的核心不是“工具调用多了”

src/services/SessionMemory/sessionMemory.ts 里的 shouldExtractMemory() 很清楚:

  • 首先检查初始化阈值 minimumMessageTokensToInit
  • 初始化后,再检查更新阈值 minimumTokensBetweenUpdate
  • 然后再看从 lastMemoryMessageUuid 以来的 tool call 数
  • 最后结合“最后一轮 assistant turn 是否还有 tool calls”

也就是说,tool call 数不是独立触发器,token 增长阈值才是硬前提

自然停顿点是单独条件

同一函数里还有一个非常重要的分支:

  • 如果 token threshold 已满足
  • 即使 tool call threshold 没达到
  • 只要最后一轮没有 tool calls,也可以触发 extraction

这说明 Anthropic 希望在“自然停顿点”更新 session memory,而不是死等工具调用计数凑够。

并发控制是显式建模的

src/services/SessionMemory/sessionMemoryUtils.ts 中有几组关键状态:

  • lastSummarizedMessageId
  • extractionStartedAt
  • tokensAtLastExtraction
  • sessionMemoryInitialized

这已经不是单纯“跑一个后台任务”,而是明确的 extraction 状态机。

防卡死机制写得很具体

同一文件里 waitForSessionMemoryExtraction() 有两层保护:

  • 最长等待 EXTRACTION_WAIT_TIMEOUT_MS = 15000
  • 如果 extraction age 超过 EXTRACTION_STALE_THRESHOLD_MS = 60000,直接视为 stale,不再等待

这说明 Claude Code 已经预设了“后台 extraction 可能挂住或过久”,因此主会话不能无限阻塞。

Session Memory 的 prompt 也体现了调度哲学

src/services/SessionMemory/prompts.ts 强调:

  • 只允许使用 Edit tool
  • 保持既有 section/header 结构不变
  • 当前状态必须始终更新
  • 过长 section 和总 token 超预算时必须主动压缩

这说明 Session Memory 不是开放式笔记,而是受模板和预算约束的结构化笔记。

这里最关键的不变量

这一层至少有 4 个不变量:

  • 不在 token 增长不足时频繁 extraction
  • 不在最后一轮仍处于 tool call 中途时贸然 extraction
  • 同一时刻最多有一个 extraction 被视为有效进行中
  • extraction 不能无限阻塞主流程

为什么要这样设计

如果没有这些限制,Session Memory 很容易退化成:

  • 高频无意义抽取
  • 在工具调用半途中记录不稳定状态
  • 并发 background 任务互相覆盖 notes
  • 主会话在等待 memory 任务时卡住

Claude Code 这一套门槛,就是在防这些失败模式。

你真正应该记住的点

  • Session Memory 的难点不是“怎么写笔记”,而是“何时安全地写笔记”。
  • Anthropic 把 token 增长、tool call 密度和自然停顿点组合成调度条件。
  • 它还有显式的 stale timeout 和 extraction in-progress 管理,不是朴素后台任务。

延伸阅读