# 7. 长期记忆迭代

# 7.1 原理

长期记忆跨 Session 存在,目标是保存稳定、可复用且值得检索的事实。不能把完整聊天记录直接称为长期记忆,因为原始历史噪声大、成本高、会过期且可能包含敏感信息。

典型写入流水线:

  1. 从完成的 Session/Rollout 选择候选;
  2. 过滤模型可见且与任务有关的事件;
  3. 抽取事实、偏好、工作流、失败教训、项目约束;
  4. 去重、冲突检测、敏感信息清理;
  5. 合并到主题化记忆;
  6. 建立关键词/向量/结构化索引;
  7. 记录来源、时间、置信度、使用次数和失效策略。

读路径必须控制召回:query → candidate retrieval → rerank → policy filter → bounded injection。长期记忆不能自动拥有比当前用户/仓库策略更高的权威级别。

# 7.2 Claude Code 源码

extractMemories 从 Session Transcript 抽取持久记忆并写入项目 memory 目录。它只统计模型可见消息;如果主 Agent 已直接写过 memory,则跳过重复抽取;抽取子 Agent 只允许只读工具以及对 memory 根目录内的 Edit/Write,防止权限扩张:extractMemories.ts:75extractMemories.ts:121extractMemories.ts:171

系统用消息 UUID 作为增量游标;抽取正在执行时,新请求只保留最新上下文,当前任务结束后做 trailing extraction,避免并发写和重复全量扫描:extractMemories.ts:296extractMemories.ts:506

# 7.3 Codex 源码

Codex 把长期记忆拆为两阶段,源码文档给出了完整协议:memories/README.md

  • Phase 1:按 Thread/Rollout 并行抽取 raw_memory + rollout_summary + slug,使用数据库 claim/lease 防重复,并对失败任务做退避。
  • Phase 2:获得全局锁后选择高价值 Phase 1 输出,更新 raw_memories.mdrollout_summaries/ 和 workspace diff,再启动受限 consolidation Agent 生成更高层记忆。

Phase 2 的执行顺序在 phase2.rs:46 中明确体现。读取侧通过 Extension 注入开发者级记忆使用说明,并按配置注册 list/read/search/note 工具:extension.rs:50extension.rs:98。本地 Backend 拒绝绝对路径、..、隐藏路径和符号链接穿透:local.rs:38

# 7.4 伪代码

async function longTermMemoryPipeline():                              // 定义跨 Session 长期记忆的异步写入流水线
    jobs = db.claimEligibleRollouts(limit, lease)                     // 用租约原子认领待处理 Rollout,避免多 Worker 重复抽取
    raw = parallelMap(jobs, concurrencyLimit, rollout => {            // 在并发上限内并行执行第一阶段抽取
        events = filterMemoryRelevant(rollout.events)                 // 过滤 UI 噪声、无关进度和不应持久化的敏感事件
        memory = model.extractStructured(events)                      // 将有效轨迹抽取成带类型和来源的结构化候选记忆
        memory = redactSecrets(memory)                                // 写入长期存储前清除密钥和高敏个人信息
        db.completePhase1(rollout.id, memory)                         // 原子保存结果并完成该 Rollout 的第一阶段状态
    })                                                                // 结束当前批次的并行抽取函数
    lock = db.tryAcquireGlobalConsolidationLease()                    // 尝试获取全局合并锁,避免多个 Agent 同时改记忆文件
    if not lock: return                                               // 未获取锁时让其他实例负责合并,本实例安全退出
    inputs = db.selectByUsageRecencyAndFreshness(maxN)                // 按使用价值、新鲜度和数量预算选择合并输入
    diff = memoryWorkspace.sync(inputs)                               // 同步原始记忆与摘要,并计算待合并变更
    if diff.isEmpty: return markSuccess()                             // 没有新信息时直接完成,避免无意义模型调用
    runConsolidationAgent(                                            // 启动权限受限的第二阶段 Consolidation Agent
        cwd = memoryRoot,                                             // 将工作目录固定为专用记忆根目录
        network = DENY,                                               // 禁止联网,降低记忆内容外传与供应链风险
        writeRoots = [memoryRoot],                                    // 只允许写入记忆目录,不能修改业务仓库
        approvals = NEVER)                                            // 禁止通过交互审批临时扩大合并 Agent 权限

# 7.5 面试题

问:如何处理长期记忆冲突?

保留来源记录(provenance)和时间,区分追加事实与覆盖事实;对“当前配置或偏好”采用“最后一次有证据的写入”,对知识性结论保留多个来源和置信度;高风险事实需要重新验证。不能简单让最新的模型摘要覆盖旧内容。

# 7.6 原理深化:长期记忆是从原始会话异步生成、可重新构建的结果

长期记忆不在主 Agent Loop 中同步“顺手写入”,而是根据 Rollout 在后台生成、并且可以重新构建的结果。原始 Rollout 是权威会话记录,Phase 1 把单个 Thread 变成候选记忆,Phase 2 再跨任务去重和合并;只有验证通过的产物才进入读取流程。这样即使抽取模型失败、Worker 重启或合并产生坏文件,也不会损坏原始会话,更不会阻塞用户当前 Turn。

Codex Phase 1 的 claim、ownership token 和 lease 表明抽取任务会先被某个 Worker 认领,并通过作业归属令牌和限时租约避免多个 Worker 重复提交结果:phase1.rs:149phase1.rs:230。Phase 2 则依次执行全局认领、工作区同步、Diff 持久化、受限 Agent 合并、Artifact 校验、所有权复核和最终提交;源码在 Agent 无法安全关闭时保留 lease,正是为了防止新 Worker 与旧 Agent 并发写同一记忆空间:phase2.rs:46phase2.rs:411phase2.rs:421

因此一次“记忆迭代”至少有三类反馈。写入反馈判断候选是否新增了非重复事实;使用反馈记录某条记忆是否被召回、引用并帮助任务完成;纠错反馈在工具证据或用户反馈推翻记忆时降权、隔离或重写。排序不能只看语义相似度,应综合来源可信度、新鲜度、历史使用效果和冲突状态。没有这条闭环,长期记忆只会累积模型生成文本,时间越久污染越严重。

最后更新: 2026/8/5 22:05:21