User as Code:面向个性化 Agent 的可执行记忆
- 关联论文:2606.16707
- 作者:flyP
- 更新:2026-09-12
一句话结论
把 Agent 对用户的建模从「bag-of-facts」检索式记忆,改为「活的 Python 软件项目」——类型化对象承载状态、普通函数编码规则、append-only 日志定期 checkpoint 进代码——在标准 long-term conversation 基准(LOCOMO)召回上追平 SOTA(78.8%),并在「聚合型问题」上把检索式记忆的 6–43% 直接拉到 99%,且原生支持确定性的安全告警(如药物过敏冲突)。
解决什么真问题
个性化 Agent(personalized AI agent)需要一个跨多轮对话持久维护的「用户模型」。主流做法是 unstructured text / knowledge graph / flat store of facts,并由 retrieval 在每次新请求时召回相似条目。这条路线有四个结构性缺陷:
- 真问题 1:bag-of-facts 召回好但「思考」弱。可以召回单条事实,但存储和行动分离,无法解决矛盾、跨多条记录做聚合、或者强制规则。
- 真问题 2:聚合问题崩塌。「去年我出过几次国际差旅?」这类问题本质是对结构化状态的一次计算;检索式记忆把它当作相似度搜索,正确率只剩 6–43%。
- 真问题 3:矛盾无法收敛。同一事实的新陈述与旧陈述冲突时,纯文本/纯检索路径只能靠 LLM 自圆其说,没有 ground truth。
- 真问题 4:安全敏感规则不可执行。「新开的药方与三个月前记下的过敏冲突」这类规则,需要「状态一旦变化,规则就立刻重算」——query-driven 的检索式记忆天然做不到。
核心方法
1. 范式:User as Code(UaC)
UaC 把「用户模型」视为一个活的软件项目:
- 状态层:用类型化 Python 对象(typed Python objects)表达用户状态——比如
allergies: Set[str]、trips: List[Trip]、prescriptions: List[Drug]。 - 规则层:用普通 Python 函数(ordinary Python functions)编码治理规则——比如
def drug_conflict_check(new_drug)、def aggregate_trips(year)。 - 运行时层:表示与推理发生在「解释器可运行的同一媒介」——同一个 Python 进程里。
存储与行动从此在同一介质中:状态变化 → 规则重算 → 告警/聚合结果立刻可得。
2. 使能机制:两阶段 pipeline
UaC 的关键设计是一个两阶段 ingest pipeline,让 memory 既可执行、又不会因为「重写代码」丢失原始事实。
- 阶段 A:append-only log。所有用户陈述(对话、偏好、状态变化)按到达顺序写入只追加日志,从不删除事实。这是 UaC 的「事实无损」保证。
- 阶段 B:周期 checkpoint into typed code。定期把日志 fold 成类型化代码与对象——合并重复、解析矛盾、抽出规则。这一步把「自然语言事实流」转化为「结构化可执行状态」。
# UaC 简化骨架
class User:
allergies: Set[str]
trips: List[Trip]
prescriptions: List[Drug]
def drug_conflict_check(new: Drug) -> List[str]:
return [a for a in user.allergies if conflicts(new, a)]
# append-only log + 周期 checkpoint
def ingest(event):
log.append(event) # 事实无损
if should_checkpoint():
code = fold(log) # 把 log 折成 User + 函数
exec(code, user_ns) # 重新加载到解释器
这条 pipeline 同时回答了「不能丢事实」与「代码要可执行」两个原本看似冲突的需求。
3. 聚合型问题的「一行计算」优势
聚合问题的关键洞察:答案不是检索结果,而是对 typed state 的一次确定计算。
- 例:「去年我出过几次国际差旅?」→
len([t for t in user.trips if t.year == last_year and t.is_international]) - 检索式记忆要把这个问题转成「last_year international trip」相似度查询,召回相关文本段落,再让 LLM 数出来——召回错了就崩塌。
- UaC 直接对结构化字段 filter + 计数,因为
trips列表里每条都是 typed object。
这是 abstract 中给出的最大反差:retrieval 6–43% vs UaC ~99%。
4. 安全告警:状态变化即触发
UaC 的规则在状态变化时同步执行。例如「新药方与已知过敏冲突」:
- 旧系统:用户在几个月后的某次对话里问「我最近吃什么药?」时,LLM 不会主动去对照新处方和旧过敏——因为它是 query-driven,不是 event-driven。
- UaC:每条
prescriptions.append(new)触发drug_conflict_check(new),冲突即抛 unsolicited alert。
这条能力是 query-driven memory 「无法提供」的能力。
关键实验与数据
- LOCOMO(long-term conversation 基准)召回:UaC 达到 78.8%,与 full-context upper bound 和最强 prior memory systems 持平。
- 聚合型问题:retrieval-based memory 正确率 6–43%,UaC 接近 99%。
- 安全敏感告警:UaC 原生支持 unsolicited safety-critical alerts(药物-过敏冲突)——这是 query-driven 路线结构上做不到的。
- 定性机制:append-only log + periodic checkpoint = 事实无损 + 代码可执行。
⚠️ LOCOMO 基准出自 Maharana et al., ACL 2024(Snap Research),原文中已注明。⚠️ abstract 没有给出 LOCOMO 的细分子分数(单跳/多跳、跨会话等)、聚合问题的具体子集规模、安全告警的 false positive rate 等细节。原文未明确。
亮点
- 范式级抽象:把「user memory」从「文本/向量」挪到「软件项目」。这是抽象层级(representation level)的提升,而不是某个模块的微调。
- append-only log + checkpoint 兼顾两难:既不丢事实,又让代码可执行——常见 memory 系统的「重写即丢」陷阱被绕开。
- 聚合问题近满分:6–43% → 99% 的反差是 abstract 中最具说服力的单条证据。
- 原生安全告警:把「event-driven 规则触发」从「LLM 自觉」变成「解释器语义」,安全敏感场景下是质变。
- LOCOMO 追平 SOTA:在「普通召回」维度不输,证明 UaC 不是靠牺牲常规指标换聚合优势。
- 工程可落地:Python 对象 + 普通函数 = 任何团队都能在两周内搭出 MVP。
局限与待核
- ⚠️ checkpoint 的「何时折、如何折」是核心工程问题——abstract 没给具体调度策略与合并规则。
- ⚠️ 矛盾解析(同一事实的新旧陈述冲突)的具体冲突解决算法在 abstract 中未明确。
- ⚠️ 当
User对象规模膨胀时,规则链的复杂度管理未在 abstract 中讨论(执行顺序、循环依赖等)。 - ⚠️ 跨会话、跨设备的 checkpoint 同步策略与一致性保证 abstract 未明确。
- ⚠️ 「LLM 写的代码本身就是新攻击面」——若 checkpoint 阶段由 LLM 生成 typed code,安全/正确性验证如何做?原文未明确。
- ⚠️ LOCOMO 之外的 long-term benchmark(e.g. MSC、LoCoMo 扩展、UltraChat 记忆子集)是否同表现待核。
- ⚠️ aggregate 99% 是单数据集结果,跨域泛化待核。
- ⚠️ 原文 abstract 未给出 GitHub 仓库链接,UaC 目前无法直接获取实现代码。
对工程落地的启发
- 谁先用得上:做个性化 Agent、AI 助理、个人知识库、agent-as-coworker 的团队。
- MVP 接入路径:用 Python dataclass / pydantic 定义 User 类,先把最常用的 5–10 条规则写成函数(如冲突检查、聚合统计);append-only log 用 SQLite/JSONL;checkpoint 频率先按每日/每周手动触发,再自动化。
- 与现有 memory 系统共存:UaC 不必替代 vector store,可以让 vector store 负责「自由文本回忆」,UaC 负责「结构化状态 + 规则」。两条路各管一摊。
- 安全敏感场景的最小闭环:药物过敏、金融风险偏好、儿童保护规则——这些用 UaC 实现 1 个 alert function 通常 < 100 行代码,但能挡掉真实事故。
- 类比 OpenClaw:如果你做的 agent 需要在多轮/多会话后保持一致的用户模型,且对「不能丢事实」「必须主动告警」有要求,UaC 是直接对路的抽象层级——比纯向量召回更可工程化。
与同方向工作的关系
- vs Retrieval-based memory(MemGPT、MemoryBank、A-Mem 等):这是 UaC 主要对比对象。LOCOMO 上 UaC 追平 SOTA,但在聚合 + 主动告警两条上结构性胜出。
- vs Knowledge Graph memory:KG 提供结构但难聚合「跨多条记录的统计」;UaC 把 KG 的结构优势与代码的执行能力合并。
- vs Toolformer / ReAct 风格的 agent:这些是「行动」抽象,UaC 是「状态 + 规则」抽象——两者正交,可叠加。
- vs Program-aided LM(PAL / Program-of-Thought):PAL/PoT 是「用代码辅助单次推理」,UaC 是「用代码持久表示用户」——使用代码的语境和生命周期都不同。
- vs Profiles / System Prompt 注入:传统做法是把用户画像塞进 prompt,受上下文窗口与「模型是否真的会读」双重制约;UaC 把画像从 prompt 里拿出来,放进运行时。
适合谁读
- 个性化 AI Agent / 个人助理方向的工程师与 PM
- Agent 长期记忆 / Memory architecture 方向的研究者
- 安全敏感场景(医疗、金融、儿童)产品负责人
- 对「representation matters」方法学感兴趣的 LLM 应用研究者
- ⚠️ 不适合:只做一次性对话、无长期记忆需求的 chatbot 团队
§0 元层五问(写作自检)
- R1 命名反方:是否仅是「又一个 memory 系统」?答:不是。它把抽象层级从「文本/向量」提升到「软件项目」,并把「存储 = 行动」合并。
- R2 工程反方:checkpoint 调度与矛盾合并的具体算法?原文未明确。
- R3 安全反方:LLM 生成的 typed code 本身是否引入新攻击面?原文未明确。
- R4 数据反方:聚合 99% 是否跨数据集泛化?原文未明确。
- R5 边界反方:UaC 适用边界(用户状态稳定 vs 剧烈变化)?原文未明确。
- 撞名检查:本标题与本目录下其他 explainer 无重复。
- 边界:仅写本文件
promo/explainers/2606-16707.md。
工程落地与核查(Jay)
事实核查摘要
| 声明 | 核查结果 |
|---|---|
| LOCOMO 召回 78.8% 追平 SOTA | ✅ 原文 abstract:"78.8% on LOCOMO";LOCOMO = Maharana et al., ACL 2024 |
| 聚合问题 retrieval 6–43% vs UaC 99% | ✅ abstract:"retrieval-based memory collapses (6-43%) while UaC stays near-perfect (99%)" |
| 药物-过敏冲突 unsolicited alert 示例 | ✅ abstract 原文示例 |
| checkpoint 两阶段 pipeline | ✅ abstract 原文 |
| append-only log 事实无损 | ✅ abstract 原文 |
| ⚠️ 存疑:各 LOCOMO 子类分数 | abstract 未给出,读 PDF §X |
| ⚠️ 存疑:checkpoint 调度算法 | abstract 未给出 |
| ⚠️ 存疑:矛盾合并规则 | abstract 未给出 |
| ⚠️ 存疑:安全/攻击面验证方案 | abstract 未给出 |
| ⚠️ GitHub URL | abstract 未给出,当前无公开代码仓库 |
可读性精修
- 全文术语统一性良好,「UaC」作为缩写首次出现有 full name。
- §4「状态变化即触发」段落中「新药方与已知过敏冲突」例子与 abstract 原文一致,建议保留为具体锚点。
- §1「bag-of-facts」一词在首次出现时应加引号注明其含义,后文使用保持一致。
- §3「typed object」和「type化 Python 对象」混用,建议统一为「类型化 Python 对象」或「typed Python object」。
工程落地:实际系统怎么用,坑在哪
MVP 搭建路径(2–4 周可出 demo):
Week 1: 状态建模
- 用 pydantic 定义 User 类(UserProfile, Allergy, Trip, Prescription 等)
- 确定 append-only log 格式(推荐 JSONL,每行一个事件)
- 写最基础的 ingest(event) 和 checkpoint()
Week 2: 规则编码
- 上线 3–5 条核心规则(冲突检测、聚合统计)
- 用真实对话数据跑一轮,验证规则触发逻辑
Week 3–4: 与现有 Agent 对接
- UaC 作为独立服务,Agent 通过 API 调用
- LLM 只负责把用户陈述转为结构化事件,规则执行由 Python 解释器负责
必踩的 5 个坑:
- checkpoint 时机选择:若频率太高(每句话都 checkpoint),则失去「合并矛盾」的机会;若太低(每月一次),则内存中 User 对象与 log 的偏差累积。推荐:按对话 session 边界触发 + 每日增量合并。
- 矛盾解析没有银弹:同一用户两次说「我没有过敏」——是纠正了旧记录、还是信息不确定?UaC 的 append-only log 保证了事实不丢失,但「哪条是对的时候」需要业务规则或人工审核介入,不能推给 LLM 自圆其说。
- typed code 生成的安全性:若 checkpoint 阶段用 LLM 生成 Python 代码,直接
exec()是 RCE 漏洞。必须沙箱化(subprocess / Docker container / RestrictedPython),或在生成后加类型注解审查。 - 冷启动问题:UaC 对新用户(log 为空时)没有记忆,需要 fallback 到传统 retrieval memory 或 system prompt——两种记忆路径的切换逻辑需要显式设计。
- 序列化与迁移:User 对象 checkpoint 成 .py 文件后,若格式变化(如加字段),历史 checkpoint 如何 migration?建议用 JSON 或 msgpack 序列化,避免直接 pickle Python 对象。
与 vector store 的混合架构(推荐生产使用):
用户输入 → LLM 解析事件 → UaC 状态更新
↓
检索记忆(vector store)→ 回答"我上个月去过哪些地方"(简单回忆)
UaC 规则引擎 → 回答"我去年国际出差几次"(聚合计算)
→ 主动推送"你的新药和已知过敏冲突"
两套系统各司其职:检索记忆负责「回忆」,UaC 负责「计算 + 告警」。
适用规模估算: - 单用户 < 10,000 条事件:Python in-memory 对象 + JSONL log 完全够用 - 单用户 10,000–100,000 条:需要定期压缩/合并 log,或迁移到 SQLite - 100,000+ 条/用户:建议对象数据库(如 objectsDB)或时序数据库,专门做 checkpoint 版本管理