⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议。
跳转到正文

X1-2:记忆的边界与信任

Easy Data x AI 课程 · 扩展篇 · 中篇

上篇让单 Agent 会记、会忘、会裁决冲突。中篇回答:同一个人的记忆,在不同 Agent 之间该共享还是隔离?

开篇

上篇把单 Agent 的记忆生命周期跑通了:写入分层、算保留率 R、检索精排、冲突裁决。但现实中,用户很少只用一个 Agent。

你可能用 Cursor 写代码,用 ChatGPT 做日常问答,用一个垂直工具管财务。它们都应该认识你,但认识的程度不该一样

  • 财务工具需要知道收入结构和风险偏好,代码助手不需要
  • 代码助手需要知道技术栈偏好,财务工具不需要
  • 代码助手里未发布的产品细节,如果泄漏到联网的 ChatGPT 会话,就是安全事件

多 Agent 记忆的核心问题:同一个用户的数据,边界画在哪、共享怎么控、用户怎么信。

P3 从产品设计角度讲过命名空间和可见性档次。中篇用 code/X1/part2_namespace/ 的示例代码,把设计落到存储层,串成一条链路:隔离键默认互不可见 → 作用域按需共享 → 并发写入有仲裁 → 用户能查、能改、能删

目录

第一部分:命名空间与默认隔离

上篇始终在单个 Agent 里讨论记忆,存储层往往用 user_id 区分用户就够了。一旦第二个 Agent 接入,同一位用户下会并存多套记忆,若表结构里只有 user_id、没有 agent_id,这些条目混在同一命名空间里,隔离几乎无法事后补做。

1.1 三级隔离键:写入时贴标签

第一步是给每条记忆标明"属于谁"。示例代码 x1_7_namespace_isolation.py 在写入时为每条记忆带上三个字段:

user_id  →  agent_id  →  run_id
字段隔离粒度典型用途
user_id用户级用户 A 与用户 B 完全隔离
agent_idAgent 级代码助手 vs 生活助手默认互不可见
run_id会话级单次任务上下文,任务结束可清理

写入时贴好标签,只是准备工作。隔离是否生效,取决于检索时有没有先用这些标签过滤。上篇两阶段检索里,阶段 1 也是先缩小候选集、再算语义;多 Agent 场景同理:必须先按 user_idagent_id 圈定"当前 Agent 能看的子集",再在这个子集里做语义匹配。

但这个顺序不能反过来。如果先对全库做向量搜索、再在应用层做 if mem["agent_id"] == agent_id 过滤,会出现两个问题:

  1. 性能:无关 Agent 的记忆也被拉回内存、参与相似度计算,向量索引的毫秒级优势丧失
  2. 安全:被过滤掉的记忆可能已经经过 embedding 计算、写进日志或临时缓存,存在泄漏面

因此隔离键必须在查询构建阶段注入,而不是检索后补救。下面的 search() 演示的就是这个顺序:先比对 user_idagent_id,通过后才计算 relevance

python
class NamespaceMemoryStore:
    def search(self, query, user_id, agent_id):
        """先按隔离键过滤命名空间,再算语义相关度"""
        results = []
        for mem in self._store:
            if mem["user_id"] != user_id:
                continue          # 一级:跨用户不可见
            if mem["agent_id"] != agent_id:
                continue          # 二级:跨 Agent 默认不可见
            if not mem["is_active"]:
                continue
            relevance = keyword_match(mem["content"], query)
            if relevance > 0:
                results.append(mem)
        return results

上面循环里前两行 continue,就是在模拟生产环境中的两种等价写法:

  • 关系型存储:WHERE user_id = ? AND agent_id = ?,在 SQL 层只扫当前用户的当前 Agent 子集
  • 向量库:检索请求里带 metadata filter(user_idagent_id),索引层直接跳过无关分区

PowerMem 记忆系统也采用同样的"查询阶段注入过滤"思路。两种实现的对比如下:

方式与示例代码的对应延迟安全性
查询阶段过滤(推荐)search() 里先 user_id/agent_idrelevance
检索后过滤(不推荐)先全表算 relevance,最后再 if 过滤

1.2 动手实验 1:默认隔离是否生效

x1_7_namespace_isolation.py 验证的正是上面这套顺序有没有落地:

  • Agent A(代码助手)写入 3 条技术记忆
  • Agent B(生活助手)写入 2 条生活记忆
  • 让 A 搜索"饮食 过敏",预期零结果(B 的饮食记忆不应进入 A 的命名空间)
bash
cd code/X1/part2_namespace
python x1_7_namespace_isolation.py

若 A 能搜到 B 的记忆,说明隔离键没有在检索入口生效,这是 P0 级 bug,与上表"检索后过滤"的风险一致。

x1_9_cross_agent_query.py 在 1000 条模拟数据上,量化"先过滤再检索"和"先检索再过滤"的延迟差。跑完可以直观看到:隔离不是多写两行 if 的事,而是检索管线顺序的设计问题。

bash
python x1_9_cross_agent_query.py

第一部分解决的是"默认互不可见"。但姓名、角色、同组工具间的技术栈,有时确实也应该在多个 Agent 中进行共享。这就需要第二个维度:作用域。

第二部分:作用域与权限提升

user_id + agent_id 只能回答"这条记忆属于哪个 Agent"。它不能回答:"同属 开发组的 PM 助手,能不能看到代码助手写的技术偏好?"

作用域(Scope)就是在同一用户、已有隔离键的前提下,控制不同 Agent 之间能否看到某条记忆,这一部分管经用户确认后,怎么有审计地打开

2.1 四级作用域

作用域谁能看到典型场景
PRIVATE仅写入该记忆的 Agent代码偏好、项目上下文
AGENT_GROUP同一 Agent Group 内所有 Agent开发工具组共享技术栈
USER_GROUP指定用户组可见团队协作
PUBLIC所有 Agent姓名、角色等通用信息

默认值是 PRIVATE。 记忆写入时不自动跨 Agent 流通;要共享,必须走 promote,并在审计日志里留痕。

2.2 Promote:只能向更开放的方向移动

沿用上面的例子:代码助手写入"用户主力语言 Go",scope 为 PRIVATE。同组 PM 助手调用 search() 时,即使 user_id 相同、agent_id 不同,且 scope 仍为 PRIVATE,也看不到这条记忆。

用户确认"开发组内可以共享技术栈"后,执行 promote:

python
SCOPE_LEVEL = {"PRIVATE": 0, "AGENT_GROUP": 1, "PUBLIC": 2}

def promote(self, memory_id, new_scope, target_group=None, actor_id="user"):
    """提升作用域,并写入 audit_log 供事后追溯"""
    for mem in self._store:
        if mem["id"] != memory_id:
            continue
        old_scope = mem["scope"]
        if SCOPE_LEVEL[new_scope] <= SCOPE_LEVEL[old_scope]:
            return False  # 只能向更开放方向 promote,不能悄悄收回

        mem["scope"] = new_scope
        if target_group:
            mem["target_group"] = target_group

        self._audit_log.append({
            "memory_id": memory_id,
            "old_scope": old_scope,
            "new_scope": new_scope,
            "actor": actor_id,
        })
        return True
    return False

x1_8_promote_and_share.py 把 promote 之后检索行为的变化跑一遍:

  1. 代码助手(dev_tools)写入技术偏好 → PRIVATE,PM 助手检索 → 看不到
  2. promote 到 AGENT_GROUP(dev_tools) → PM 助手现在可见(检索逻辑会额外检查 scope 和 target_group)
  3. 生活助手(life_tools 组)检索 → 仍然看不到
bash
python x1_8_promote_and_share.py

用户问"AI 为什么知道我是后端工程师?",audit_log 可以回答:某次 promote 把 PRIVATE 开放给了开发组。信任靠可审计的变更链,不是靠黑盒推断。 LLM 可以建议是否 promote,决定权应在用户或业务规则。

第三部分:共享架构与并发写入

隔离键和作用域解决了单套部署内"谁能看到哪条记忆"。一旦记忆要跨设备、跨平台同步,或两个 Agent 同时写同一字段,还会遇到架构选型和并发仲裁问题。第三部分讨论的是共享落地之后的两类工程挑战。

3.1 记忆跨平台时放哪:三种架构

架构代表优势代价
中心化记忆服务ChatGPT Memory一致性强,跨平台数据主权低
本地优先 + 选择性同步PowerMem 等数据主权高,离线可用多设备一致性弱
点对点传递Agent 间 transfer最灵活无全局视图,难管理

没有绝对最优:一致性、数据主权、跨平台能力,三者最多取二。 这一节不跑示例脚本,而是帮你在 promote 之外补全"数据物理上怎么流动"的选型视角。合规要求高的内网倾向本地优先;快速接入倾向托管 API。

3.2 边界设计仍可能失效的两类故障

即使 isolation + scope 设计正确,上线后仍常见两类偏差,它们分别对应第二部分的两个维度:

工作记忆泄漏(scope 维度): 代码助手在调试中记住"用户最近失眠"。若 scope 误设为 PUBLIC,生活助手会在无关场景读到。缓解:敏感类别(健康、财务)写入时默认 PRIVATE,promote 前触发用户确认。

并发写入冲突(共享画像维度): 多个 Agent 写同一份共享用户画像时,Agent A 写"偏好 Python",Agent B 同时写"主力 Go"。无版本控制时,后写覆盖先写,先写的更新静默丢失。这与上篇 Java→Go 的冲突裁决不同:那里是两条矛盾记忆,这里是同一字段的并发写

3.3 乐观锁:共享画像的最小仲裁

x1_10_concurrent_write.py 针对 3.2 的并发冲突,对比三种写入策略:无保护、乐观锁、LWW。

共享画像可建模为带 version 的结构化字段。两个 Agent 同时改 preferred_language 时,乐观锁用 CAS(Compare-And-Swap)检测"读到的版本是否仍是最新":

python
shared_profile = {"preferred_language": "Java", "version": 1}

# Agent A:读到 version=1,尝试改为 Go
current_version = shared_profile["version"]
if shared_profile["version"] == current_version:
    shared_profile["preferred_language"] = "Go"
    shared_profile["version"] += 1          # 写入成功,version → 2

# Agent B:也读到 version=1,但 A 已改为 2 → 条件不成立,需重读再合并
if shared_profile["version"] != current_version:
    pass  # 冲突:重试或交给用户仲裁
策略优点缺点
无保护实现简单静默覆盖,数据丢失
乐观锁低开销,可重试高冲突时需人工仲裁
LWW实现简单后写覆盖前写,可能丢合理更新

不同 Agent 更新画像的不同 key 时通常无冲突;同一 key 需要仲裁时,乐观锁是成本最低的方案。

动手实验 2:并发写入谁赢

bash
python x1_10_concurrent_write.py

对照输出:无保护策略下,是否总有一个 Agent 的更新被覆盖?乐观锁下,失败写入有没有触发重试?这对应 3.2 里"并发写入冲突"的缓解路径。

第四部分:信任治理的工程清单

前三部分解决的是系统怎么画边界:隔离键、作用域、并发仲裁。但开篇第三个问题"用户怎么信",还需要用户侧能看见、修正、删除、理解影响。否则 promote 和隔离对用户仍是黑盒。

P3 从产品角度提过这四类能力。工程上它们对应一组 API,并与第二部分的 audit_log 衔接:

4.1 面向用户的记忆管理 API

能力API 形态与前面章节的关联
查看GET /memories?scope=all展示各 scope 下的记忆,而非原始行 dump
修正PUT /memories/{id}修正后检查是否与同话题其他记忆冲突(上篇冲突管线)
删除DELETE /memories/{id}级联清理主库、向量索引、缓存(见下表)
理解影响GET /memories/{id}/impact删除后哪些 Agent、哪些 query 的回答会变

用户需要的是"AI 眼中的我",有上下文、有操作入口,而不是 { "mem_003": "用户用 Go" } 这样的原始字段。

4.2 合规与安全

多 Agent 场景下,一条记忆往往同时存在于主库、向量索引、缓存甚至备份里;promote 还会改变谁能看到它。下面三项是上线前常见的检查清单。

工程要点与前面章节的关联
被遗忘权主库 + 向量索引 + 缓存 + 备份都要清第一部分:embedding 也可能携带隔离键外的内容
记忆投毒防护来源置信度、高敏感门控第二部分:错误 promote 会把恶意内容扩 scope
审计日志谁、何时、改了 scope 或内容第二部分:audit_log 的用户-facing 延伸

被遗忘权(用户要求删除某条记忆)

用户点删除时,不能只删数据库里的一行。检索链路里,同一条记忆可能有多份副本:

存储位置若只删主库会怎样
主库(记忆正文)已删,但检索仍可能命中旧副本
向量索引(embedding)语义搜索还能召回已删内容
缓存(近期检索结果)短时间内 Agent 仍用旧答案
备份 / 日志合规上仍算"未彻底删除"

工程上应为 DELETE /memories/{id} 设计级联清理:主库标记删除 → 异步删向量索引中的对应 embedding → 失效相关缓存 key → 备份保留策略与主库一致。第一部分讲过,检索先过隔离键再过语义;删除时也要按同样思路,把记忆流经的每个出口都走到。

记忆投毒防护(恶意内容被当成用户事实)

攻击者可能通过对话注入"用户偏好 XXX",系统提炼后写入记忆,再被 promote 扩大可见范围。缓解思路:

  • 写入前:对来源做置信度标记(用户亲口说的 vs 外部链接解析出的)
  • promote 前:健康、财务、身份类记忆强制用户二次确认(对应第二部分的 scope 提升流程)
  • 高敏感类别:默认 PRIVATE,不允许 LLM 自动 promote 到 PUBLIC

审计日志(事后追溯"AI 凭什么知道")

第二部分 promote() 里的 audit_log,记录谁在何时把哪条记忆从 PRIVATE 改到 AGENT_GROUP。合规场景下还需要覆盖:谁删除了记忆、谁修正了内容。用户投诉或监管问询时,能还原完整变更链,而不是只有当前快照。

上线前可以自问三件事:删一条记忆时四个存储出口是否都覆盖?promote 敏感记忆有没有人工或规则门禁?scope 和内容变更是否可查?

总结与参考资料

中篇把多 Agent 记忆的工程核心归纳为三条,并与开篇问题一一对应:

  1. 命名空间从第一天就要有(默认互不可见):user_id + agent_id 在检索入口过滤,再谈语义
  2. 默认隔离、显式共享(共享怎么控):PRIVATE 为默认,promote 可审计;敏感记忆不应自动跨 Agent 流通
  3. 信任是 API + 审计(用户怎么信):用户能查、改、删、理解影响;删除与 promote 都可追溯

逻辑链: 上篇单 Agent 生命周期 → 中篇多 Agent 边界与信任 → 下篇从海量记忆中蒸馏稳定认知,并量化系统好不好。

参考资料:

  1. P3 Agent 记忆系统设计
  2. ChatGPT Memory 产品文档 — openai.com/index/memory-and-new-controls-for-chatgpt

Built with VitePress | GitHub 仓库