Memory 写入、检索与纠错机制:让 Agent 记住,也让它忘对

说明:本文源码是教学用的最小实现样例,不代表任何闭源产品内部实现。涉及 OpenAI Agents SDK、Claude Code 的公开能力,以文末官方文档为准。

摘要

Agent 记忆系统最难的不是"存起来",而是"该不该信"。错误记忆、过期记忆、误写入的用户偏好,会让 Agent 在未来稳定犯错。

一个成熟的 Memory System 要解决五个问题:

  • 什么值得写入?
  • 写到哪里?
  • 什么时候检索?
  • 发现错了怎么改?
  • 不同用户、项目、Agent 之间如何隔离?

OpenAI Agents SDK 区分 session memory 和 sandbox agent memory;Claude Code 也有 CLAUDE.md、auto memory 等机制。这说明记忆不是单一数据库,而是一套有作用域、有来源、有生命周期的系统。

一、记忆分层

建议至少分四类:

类型 内容 生命周期 示例
会话记忆 当前对话上下文 短期 用户刚才要求"不要改接口"
项目记忆 项目规则和命令 中长期 测试命令是 pnpm test
用户记忆 个人偏好 长期 喜欢简洁回答
经验记忆 可复用解决路径 长期但需衰减 某类构建错误的处理方式

普通人可以这样理解:会话记忆像临时便签,项目记忆像团队文档,用户记忆像个人偏好设置,经验记忆像做题笔记。

二、源码示例:记忆不能只是字符串

先定义记忆对象:

python 复制代码
from dataclasses import dataclass, field
from enum import Enum
from time import time
from typing import Any
import uuid


class MemoryScope(str, Enum):
    SESSION = "session"
    PROJECT = "project"
    USER = "user"
    GLOBAL = "global"


class MemoryStatus(str, Enum):
    ACTIVE = "active"
    DEPRECATED = "deprecated"
    DELETED = "deleted"


@dataclass
class MemoryItem:
    content: str
    scope: MemoryScope
    source: str
    confidence: float
    applies_to: list[str] = field(default_factory=list)
    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    created_at: float = field(default_factory=time)
    updated_at: float = field(default_factory=time)
    expires_at: float | None = None
    status: MemoryStatus = MemoryStatus.ACTIVE
    evidence: list[dict[str, Any]] = field(default_factory=list)

关键字段:

  • scope:作用范围。
  • source:来源,是用户明确要求,还是系统自动提取。
  • confidence:可信度。
  • expires_at:是否过期。
  • evidence:证据来源,便于审计。

三、源码示例:记忆写入要分阶段

不要让模型随便把任何东西写成长久记忆。建议流程:

text 复制代码
候选提取 -> 安全过滤 -> 冲突检测 -> 作用域判断 -> 写入草稿 -> 确认或延迟生效

示例:

python 复制代码
SENSITIVE_WORDS = {"password", "api_key", "secret", "token", "private key"}


def is_sensitive(text: str) -> bool:
    lower = text.lower()
    return any(word in lower for word in SENSITIVE_WORDS)


def should_write_memory(candidate: str, source: str) -> tuple[bool, str]:
    if is_sensitive(candidate):
        return False, "contains_sensitive_information"

    if source == "untrusted_webpage":
        return False, "untrusted_source"

    if len(candidate.strip()) < 10:
        return False, "too_short"

    return True, "ok"

用户明确说"记住"时,可以提高置信度:

python 复制代码
def create_memory(candidate: str, scope: MemoryScope, source: str, trace_id: str) -> MemoryItem | None:
    ok, reason = should_write_memory(candidate, source)
    if not ok:
        return None

    confidence = 0.95 if source == "user_explicit" else 0.65

    return MemoryItem(
        content=candidate,
        scope=scope,
        source=source,
        confidence=confidence,
        evidence=[{"trace_id": trace_id}],
    )

四、源码示例:简单 Memory Store

教学版可以用内存实现,生产环境可换成 SQLite、Postgres、文件、向量库或组合存储。

python 复制代码
class MemoryStore:
    def __init__(self):
        self.items: dict[str, MemoryItem] = {}

    def add(self, item: MemoryItem) -> None:
        self.items[item.id] = item

    def update(self, item: MemoryItem) -> None:
        item.updated_at = time()
        self.items[item.id] = item

    def delete(self, memory_id: str) -> None:
        item = self.items[memory_id]
        item.status = MemoryStatus.DELETED
        item.updated_at = time()

    def list_active(self, scope: MemoryScope) -> list[MemoryItem]:
        now = time()
        return [
            item for item in self.items.values()
            if item.scope == scope
            and item.status == MemoryStatus.ACTIVE
            and (item.expires_at is None or item.expires_at > now)
        ]

这里用软删除,而不是直接物理删除,便于审计和恢复。

五、源码示例:检索要考虑相关性、置信度、新鲜度

一个简单检索排序:

python 复制代码
def lexical_score(query: str, text: str) -> float:
    q = set(query.lower().split())
    t = set(text.lower().split())
    if not q:
        return 0.0
    return len(q & t) / len(q)


def freshness_score(item: MemoryItem) -> float:
    age_days = (time() - item.updated_at) / 86400
    return max(0.0, 1.0 - age_days / 180)


def retrieve_memories(store: MemoryStore, query: str, scope: MemoryScope, limit: int = 5) -> list[MemoryItem]:
    candidates = store.list_active(scope)

    ranked = sorted(
        candidates,
        key=lambda item: (
            0.5 * lexical_score(query, item.content)
            + 0.3 * item.confidence
            + 0.2 * freshness_score(item)
        ),
        reverse=True,
    )

    return ranked[:limit]

生产系统可以用向量检索、全文索引、规则过滤、reranker 组合。但无论用什么技术,都不能只看相似度。相似但过期、低置信、作用域错误的记忆,可能更危险。

六、源码示例:冲突检测

冲突检测可以从简单规则开始:

python 复制代码
def detect_conflict(new_item: MemoryItem, existing_items: list[MemoryItem]) -> list[MemoryItem]:
    conflicts = []
    for item in existing_items:
        if item.scope != new_item.scope:
            continue

        # 简化示例:真实系统可用 LLM 或规则引擎判断语义冲突。
        if "use npm" in item.content.lower() and "use pnpm" in new_item.content.lower():
            conflicts.append(item)
        if "use pnpm" in item.content.lower() and "use npm" in new_item.content.lower():
            conflicts.append(item)

    return conflicts

发现冲突后,不要直接覆盖。可以:

  • 请求用户确认。
  • 降权旧记忆。
  • 标记旧记忆为 deprecated。
  • 同时保留并注明适用范围。
python 复制代码
def deprecate_memory(store: MemoryStore, memory_id: str, reason: str) -> None:
    item = store.items[memory_id]
    item.status = MemoryStatus.DEPRECATED
    item.evidence.append({"deprecated_reason": reason})
    store.update(item)

七、源码示例:记忆注入上下文时要标注来源

不要把记忆伪装成系统真理。

python 复制代码
def format_memory_for_context(items: list[MemoryItem]) -> str:
    lines = []
    for item in items:
        lines.append(
            f"- [{item.scope.value}, confidence={item.confidence:.2f}, source={item.source}] "
            f"{item.content}"
        )
    return "\n".join(lines)

模型看到来源和置信度后,更容易在冲突时做合理判断。

八、记忆隔离

多用户、多项目场景必须隔离记忆。

python 复制代码
@dataclass
class MemoryNamespace:
    tenant_id: str
    user_id: str
    project_id: str | None
    agent_id: str


def namespace_key(ns: MemoryNamespace, scope: MemoryScope) -> str:
    if scope == MemoryScope.USER:
        return f"tenant:{ns.tenant_id}:user:{ns.user_id}"
    if scope == MemoryScope.PROJECT:
        return f"tenant:{ns.tenant_id}:project:{ns.project_id}"
    if scope == MemoryScope.SESSION:
        return f"tenant:{ns.tenant_id}:agent:{ns.agent_id}:session"
    return f"tenant:{ns.tenant_id}:global"

隔离不是洁癖,而是防止 A 项目的经验污染 B 项目,或 A 用户的信息泄露给 B 用户。

九、纠错机制

记忆系统必须支持人类纠错。

python 复制代码
def correct_memory(store: MemoryStore, memory_id: str, new_content: str, correction_trace_id: str) -> None:
    old = store.items[memory_id]
    old.status = MemoryStatus.DEPRECATED
    old.evidence.append({"corrected_by": correction_trace_id})
    store.update(old)

    corrected = MemoryItem(
        content=new_content,
        scope=old.scope,
        source="user_correction",
        confidence=0.95,
        applies_to=old.applies_to,
        evidence=[{"replaces": old.id, "trace_id": correction_trace_id}],
    )
    store.add(corrected)

纠错不是简单改文本,而是保留历史关系。否则以后审计时不知道为什么记忆变了。

十、记忆写入的红线

不要自动写入:

  • 密钥、密码、Token。
  • 未验证的猜测。
  • 来自网页或邮件的指令。
  • 一次性任务细节。
  • 与安全策略冲突的偏好。
  • 可能伤害其他用户或项目隔离的信息。

可以写入:

  • 用户明确要求记住的偏好。
  • 多次验证成功的项目命令。
  • 用户纠正过的固定规则。
  • 有证据支持的项目结构信息。
  • 可复用且作用域明确的经验。

十一、评估指标

记忆系统是否有效,可以看:

  • 用户重复纠正次数是否下降。
  • 重复探索文件次数是否下降。
  • 任务完成时间是否下降。
  • 错误记忆导致的失败率。
  • 记忆命中率和有效命中率。
  • 敏感信息写入拦截率。
  • 过期记忆命中率。

十二、落地检查表

  • 记忆是否有 scope、source、confidence、evidence?
  • 是否阻止敏感信息写入?
  • 是否区分用户、项目、会话记忆?
  • 是否支持过期、降权、删除、纠错?
  • 检索是否考虑置信度和新鲜度?
  • 冲突是否可检测?
  • 记忆注入上下文时是否标注来源?
  • 是否支持多租户隔离?
  • 是否能审计一条记忆从哪里来?

结论

好的记忆系统不是"什么都记",而是"只记该记的、知道为什么记、错了能改、过期能忘"。Agent Memory 的工程核心是治理,不是存储。

参考资料

Hermes 源码核实

Hermes 的记忆系统和很多人理解的"一个向量库"不一样,它其实是本地记忆、插件记忆和会话注入三层结构。

1. 本地记忆是 MEMORY.md / USER.md

tools/memory_tool.py 明确把本地记忆分成两类:

  • MEMORY.md:更偏向 agent 自己的观察、环境事实、项目细节
  • USER.md:更偏向用户偏好、表达风格、长期习惯

run_agent.py 里也有 memory_enableduser_profile_enabled 两个开关。也就是说,Hermes 不是默认把所有记忆都打开,而是分项控制。

2. 记忆不是直接读写,而是先加载成快照

MemoryStore.load_from_disk() 会在启动时把 MEMORY.md / USER.md 读进来,形成系统提示快照。后续回答时用的是这个快照,而不是每轮重新读完整文件。这样做的好处是更稳定,也更利于缓存。

3. 外部记忆是插件化,但只能选一个

agent/memory_manager.py 明确限制:内置 memory provider 必须存在,外部 provider 一次只允许一个。源码里还会拒绝第二个外部 provider 的注册。这说明 Hermes 已经意识到:

多个记忆后端并存,不一定更强,反而更容易冲突和膨胀。

4. 记忆上下文注入会加 fence

MemoryManager.build_memory_context_block() 会把预取回来的内容包在:

text 复制代码
<memory-context>
[System note: ...]
...
</memory-context>

这样模型更不容易把回忆内容误当成用户最新输入。这一层 fence 非常适合新手理解:记忆可以进上下文,但必须标明"这是回忆,不是指令"。

5. 外部 provider 真的是主动工作的

MemoryProvider 的 ABC 里有这些关键接口:

  • initialize()
  • system_prompt_block()
  • prefetch()
  • queue_prefetch()
  • sync_turn()
  • get_tool_schemas()
  • handle_tool_call()
  • on_pre_compress()
  • on_memory_write()

这说明 Hermes 的外部 memory 不只是被动存储,而是会参与 turn 前回忆、turn 后同步、压缩前提取、记忆写入镜像。

6. 现有插件已经证明这套设计可用

源码里能看到至少三类 provider:

  • holographic
  • retaindb
  • openviking

它们都实现了不同风格的预取、同步、工具暴露和记忆回写。这说明 Hermes 的记忆系统不是概念图,而是已经落到可运行插件层。

7. 给新手的理解

可以这样理解 Hermes 的记忆:

  • MEMORY.md / USER.md 像便携笔记本
  • MemoryProvider 像外接记忆盒
  • MemoryManager 像记忆总控台
  • memory-context fence 像提醒模型别把回忆当命令
相关推荐
小赵不会秃头1 小时前
数据结构Day 06:线性结构、库操作及 Makefile 完整学习笔记
java·linux·数据结构·算法·面试
雨田大大1 小时前
Windows11下IDEA运行后端时,端口被占用的解决方法
linux·运维·服务器
xqqxqxxq1 小时前
Maven 完整配置与使用技术笔记
java·笔记·maven
砍材农夫1 小时前
物联网 基于netty理解粘包/拆包
java·物联网·struts
Counter-Strike大牛1 小时前
Nacos源码修改tomcat版本方法
java·tomcat
上海云盾-小余1 小时前
服务器入侵应急处置:痕迹清理、漏洞封堵与事后加固全流程
运维·服务器
念越1 小时前
HTTPS 安全内核:对称与非对称加密的博弈,数字证书一战定局
java·网络·网络协议·安全·https
Anastasiozzzz1 小时前
深入研究Java Agent生态:SpringAI 与 SpringAIAlibaba核心能力、架构演进与全场景对比研究
java·开发语言·架构
彭于晏Yan1 小时前
JSONObject 使用文档(Java/Android原生)
java·spring boot·后端