说明:本文源码是教学用的最小实现样例,不代表任何闭源产品内部实现。涉及 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 的工程核心是治理,不是存储。
参考资料
- OpenAI Agents SDK Sessions: https://openai.github.io/openai-agents-python/sessions/
- OpenAI Agents SDK Sandbox Agent Memory: https://openai.github.io/openai-agents-python/sandbox/memory/
- Claude Code Memory: https://code.claude.com/docs/en/memory
- Anthropic Claude Code memory management: https://docs.anthropic.com/en/docs/claude-code/memory
Hermes 源码核实
Hermes 的记忆系统和很多人理解的"一个向量库"不一样,它其实是本地记忆、插件记忆和会话注入三层结构。
1. 本地记忆是 MEMORY.md / USER.md
tools/memory_tool.py 明确把本地记忆分成两类:
MEMORY.md:更偏向 agent 自己的观察、环境事实、项目细节USER.md:更偏向用户偏好、表达风格、长期习惯
run_agent.py 里也有 memory_enabled 和 user_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:
holographicretaindbopenviking
它们都实现了不同风格的预取、同步、工具暴露和记忆回写。这说明 Hermes 的记忆系统不是概念图,而是已经落到可运行插件层。
7. 给新手的理解
可以这样理解 Hermes 的记忆: