Skip to content
预计 1 分钟右侧目录可快速定位

什么值得长期记住:Long-Term Memory 的写入与检索 ​

前置:运行中的信息放哪里:Conversation、Checkpoint 与 Short-Term Memory
核心问题:哪些信息值得跨任务长期保存,什么时候应该写入、何时应该过期或拒绝写入,避免 Memory 变成永久垃圾场?

用户多次纠正系统“这个仓库使用 uv,不用 pip”。是否应该把这条经验写成长期记忆?写入后多久有效?另一个仓库是否适用?下一次运行何时检索?这些都不是一个 save() 调用能回答的。

Mental Model ​

text
Run / Observation
      ↓
Memory Candidate Extraction
      ↓
Write Policy
  ├─ usefulness
  ├─ stability
  ├─ scope
  ├─ sensitivity
  └─ duplication
      ↓
Durable Memory Record + Provenance
      ↓ future run
Memory Query → scope filter → rank/freshness → selected memories
      ↓
ContextManager → token budget → Model Context

Long-Term Memory 不是“更大的聊天记录”,而是一条受策略控制的长期知识管线。

关键边界是 Write、Store、Retrieve、Inject 四步分离。模型可以帮助提取 candidate 或判断语义相关性,但真正写入哪个 namespace、是否允许持久化、是否过期以及最终注入多少 token,都应由程序策略约束。

Long-Term Memory 的定义与 Scope ​

Long-Term Memory 指跨当前 thread/session 仍有复用价值的信息。典型是关于用户、项目或环境的相对稳定事实,例如:

text
project uses Python 3.12
repository test command is `uv run pytest`
user prefers concise changelogs

Memory Write Policy ​

Tool 返回 1000 条日志,这 1000 条不是 1000 条 Memory。

写入前至少评估:

text
Future usefulness:以后还会用吗?
Stability:这个事实容易变化吗?
Confidence:来源可靠程度?
Scope:属于 user / project / workspace / agent / global?
Sensitivity:是否包含敏感信息?
Duplication:已有相同/更好的记录吗?

🔍 源码对照:Memory 写入是 opportunistic maintenance,不是 Turn commit ​

精确定位

ts
async function runTurnMemory(store, progress, session, ctx, ts, exchange) {
  await runMemoryExtraction(store, session, ctx, exchange)
  if (!progress) return

  try {
    progress.recordEpisodeTurn({ ts, ...exchange })
    if (progress.pending().length < episodeInterval()) return
    await summarizeAndPersistEpisode(progress, store, session, ctx)
  } catch {
    // maintenance must not fail the user-visible turn
  }
}

对照理解

  • Memory extraction / episode summarization 属于 maintenance path,不是 Turn settlement 的强一致步骤。
  • 源码故意吞掉这条维护路径的异常:记忆失败不能把已经完成的用户 Turn 反向判成失败。
  • 因此 Memory、Checkpoint、Conversation State 必须分开:Checkpoint 决定能否恢复,Memory 只增强未来决策。

源码说明

turn-memory.ts 直接展示了两类维护动作:memory extraction 与 episode summary;更重要的是,这条路径显式捕获异常,注释要求 memory maintenance 不能让 user-visible turn 失败。这个边界很适合说明 Long-Term Memory 是辅助知识系统,而不是 Run correctness 的唯一事实源。

工程边界

“best effort”适用于这里的 memory maintenance,不意味着所有 memory 系统都可随意丢失;如果产品把某类用户承诺/偏好当强持久化数据,就需要更强 durability contract。

Shadow Harness 对照

  • Memory write 有独立 policy:candidate → dedup/conflict/provenance → commit。
  • Episode summary 与 raw evidence 分离,摘要可重建/更新,证据保留 provenance。

验证

让 memory extraction 模型调用失败,用户 Turn 仍成功;随后重跑 maintenance,验证不会重复写入相同 memory。

Memory Record 设计 ​

一条可治理的 MemoryRecord 不能只有 text + embedding,至少还需要 scope、provenance、有效期和 supersede 关系:

python
@dataclass
class MemoryRecord:
    memory_id: str
    namespace: tuple[str, ...]
    kind: str
    content: str
    provenance: MemoryProvenance
    confidence: float
    valid_from: datetime
    valid_until: datetime | None
    supersedes: str | None
    tags: set[str]
    embedding_ref: str | None

Memory Retrieval ​

即使检索命中,也不代表必须放入 Context。

它同样需要相关性排序,但长期记忆还必须受 scope 和可用性策略约束;freshness、conflict 与 isolation 的完整规则放到下一篇专门处理。

text
Memory Store
  ↓ retrieve candidate
Candidate Set
  ↓ filter/rank
Relevant Memories
  ↓ context budget / trust policy
Injected Context

最小实现 / 数据结构 ​

python
class MemoryStore(Protocol):
    async def put(self, record: MemoryRecord) -> None: ...
    async def search(self, query: MemoryQuery) -> list[MemoryHit]: ...
    async def supersede(self, old_id: str, new_record: MemoryRecord) -> None: ...
    async def delete(self, memory_id: str) -> None: ...

class MemoryWritePolicy(Protocol):
    async def evaluate(self, candidate: MemoryCandidate, ctx: RunContext) -> WriteDecision: ...

class MemoryRetrievalPolicy(Protocol):
    async def select(self, hits: list[MemoryHit], ctx: RunContext, token_budget: int) -> list[MemoryHit]: ...

在 Shadow Harness 中怎么落地 ​

Shadow Harness 建议把 Memory 拆成 MemoryCandidateExtractor、MemoryWritePolicy、MemoryStore、MemoryRetrievalPolicy 四个接口。不要让 ContextManager 直接拥有 Memory 写权限;它只消费经过 retrieval policy 选出的记录。

Failure Modes ​

自动保存每一句用户话 ​

症状: 所有 message/observation 都直接写入长期 Memory。

为什么危险: 长期存储会被一次性信息、噪声和敏感数据污染,未来 retrieval 更容易返回错误内容。

处理: 引入 MemoryCandidate + WritePolicy,按 usefulness、stability、scope、sensitivity、duplication 决定 commit。

只靠 Vector Similarity ​

症状: 检索只按 embedding similarity top-k,不考虑 namespace、freshness、provenance 或 structured key。

为什么危险: 语义相似不等于属于当前用户/项目,也不等于事实仍有效。

处理: 先强制 namespace/filter,再组合 exact/keyword/semantic/hybrid,并加入 freshness/source trust。

永不更新旧 Memory ​

症状: 同一 canonical fact 的多个版本长期同时 active。

为什么危险: 模型每次要重新解释冲突,行为会随检索排序波动。

处理: 支持 upsert/supersede/merge/delete,读取默认过滤 inactive/superseded。

检索结果全部注入 ​

症状: Memory search 命中多少条就向 Context 注入多少条。

为什么危险: Store 容量与模型 working set 是两个概念;无限注入会制造 context pollution。

处理: retrieve 与 inject 分开,用 RetrievalPolicy 做 filter/rank/token budget/progressive disclosure。

如何验证 ​

Case断言
自动保存每一句用户话用标注 observation set 计算 write precision/recall,敏感/越 scope 候选不得 commit。
只靠 Vector Similarity写入跨 project 的高相似 memory,断言未授权 scope 永远不进入候选。
永不更新旧 Memory写入同 key 新版本后,默认检索只返回 active 记录,同时历史链可审计。
检索结果全部注入让 Store 返回大量候选,断言最终注入受 budget/threshold 控制。

关联源码 ​

自测 ​

  1. 用户一次临时偏好是否应该写入长期 Memory?哪些条件能让一条信息值得跨 Session 保存?
  2. 长期 Memory 检索到了旧事实。为什么“检索命中”不等于“当前仍可直接使用”?

Sources ​

AI Engineering · Agent · Harness · Backend Systems