作者:逆境不可逃

技术永无止境

希望我的内容可以帮助到你!!!!


本文承接《RAG 不只是向量数据库:讲透 Chunk、混合检索、重排、引用与权限治理》。

如果 RAG 解决的是 “如何从外部知识库找到有来源的证据”,记忆系统解决的就是 “Agent 应该记住什么、何时召回,以及如何让长期任务在中断后安全恢复”。

摘要

很多人第一次给 Agent 增加记忆,会选择把聊天记录全部保存,再用向量检索找回相似片段。这种方案适合演示,却很容易在真实系统中产生错误:临时要求被误认为长期偏好,摘要覆盖真实任务状态,过期工具结果被当成当前事实,任务恢复后重复发送或发布,用户删除的信息仍残留在索引和缓存中。

一个可靠的 Agent 记忆系统,不是 “无限增长的聊天历史”,而是一套受控的信息生命周期:先区分会话记忆、摘要记忆、用户偏好、任务执行状态和外部知识,再通过写入门槛、来源、作用域、版本、有效期、权限、冲突规则和删除流程进行治理。召回时也不能只看语义相似度,而要先完成权限、版本、时间和状态过滤,再构造结构化 Memory Bundle。

本文围绕,以 “可恢复月度报告助手” 为贯穿案例 展开,系统讲解记忆边界、会话窗口、结构化摘要、Profile、Task/Step/Attempt/Artifact、Checkpoint、幂等恢复、知识候选、Recall Plan、冲突处理、纠错删除、审计与测试。目标不是让 Agent “好像记得”,而是让每条记忆都能回答:为什么保存、对谁有效、现在是否有效、为什么本次可以使用。

关键词

Agent、记忆系统、会话记忆、摘要记忆、用户画像、TaskState、Checkpoint、幂等、RAG、Memory Bundle、长期任务、后端工程


一、先纠正误解:模型不会自动拥有长期记忆

一次模型调用只会看到本次请求中提供的上下文:

System Prompt
+ 当前用户消息
+ 被选中的历史对话
+ 工具定义与结果
+ 检索资料
+ 应用层注入的记忆

请求结束后,模型不会自动知道上一轮发生了什么。所谓 “Agent 记忆”,本质上是应用系统完成的四件事:

采集信息
→ 判断是否值得保存
→ 写入合适的存储
→ 在未来请求中按条件召回

因此,下列概念必须分开:

概念 含义 典型载体
Context 本次模型真正看到的输入 Prompt、消息列表
History 过去发生过的原始事件 消息表、事件日志
Memory 未来可能复用的持久化信息 Profile、摘要、TaskState
Knowledge 面向多个请求复用的外部事实 文档库、RAG、知识图谱

历史不等于记忆,记忆也不等于知识。把所有内容统一塞进向量数据库,会失去精确更新、状态约束、权限隔离和删除能力。


二、五类记忆,各自解决不同问题

一个实用的 Agent 记忆系统至少包含五类信息。

类型 解决的问题 示例 合适存储
会话记忆 当前对话如何保持连续 最近消息、当前订单、临时约束 消息表、缓存
摘要记忆 长历史如何压缩与跨会话延续 已确认事实、决定、未决事项 版本化结构化摘要
用户长期偏好 用户通常希望系统怎么做 默认中文、详细回答、Markdown 关系数据库 Profile
任务执行状态 长任务做到哪里 等待审批、已生成哪些产物 状态机、事件表、Checkpoint
外部知识记忆 可复用的规则与经验 制度、Runbook、审核后 FAQ RAG、结构化知识库

最容易出错的是把它们混用:

  • 用聊天摘要代替 TaskState,恢复后重复执行;
  • 把 “这次用英文” 写成长期偏好;
  • 把昨天的账户余额当成当前事实;
  • 把一次未核验工单直接发布为正式知识;
  • 用向量检索读取本该精确查询的 Profile 字段。

可以使用下面的路由顺序:

秘密或凭证?
→ 不进入普通记忆,交给密钥管理系统

当前动态事实?
→ 调用权威工具

正在执行的任务进度?
→ TaskState

长期稳定的用户偏好?
→ Profile

只对当前请求或会话有效?
→ Request / Session Context

可复用且有来源的知识?
→ Knowledge Candidate,审核后进入知识库

三、完整记忆系统是写入和召回两条流水线

3.1 写入流水线

消息、工具结果或任务事件
→ 信息分类
→ 判断未来是否会复用
→ 检查来源、稳定性与敏感级别
→ 确定作用域和有效期
→ 创建记忆候选
→ 确认或审核
→ 版本化写入
→ 更新索引、缓存与审计

3.2 召回流水线

当前请求 + 可信身份 + 任务上下文
→ 判断是否需要记忆
→ 生成 Recall Plan
→ 按类型读取候选
→ 权限、作用域、状态和时间硬过滤
→ 选择当前有效版本
→ 计算相关性与优先级
→ 检测冲突和覆盖
→ 分配 Token 预算
→ 构造 Memory Bundle
→ 交给模型或确定性执行器

写入成功不代表每次都要召回。用户的饮食偏好适合午餐推荐,却不应影响 Java 并发问题。数据最小化不仅是 “少存”,也是 “少读”。


四、会话记忆:保存对话连续性,不承担长期真相

会话记忆负责当前 Session 中的连续交互:

  • 最近消息;
  • 活跃实体;
  • 指代关系;
  • 临时约束;
  • 待确认问题;
  • 最近工具调用与结果引用。

推荐把原始消息与派生状态分开:

{
  "event_id": "msg-7001",
  "session_id": "s-500",
  "sequence_no": 18,
  "role": "USER",
  "content": "继续上次报告,这次用纯文本",
  "request_id": "req-9001",
  "created_at": "2026-07-14T14:00:00+08:00"
}
{
  "session_id": "s-500",
  "active_task_id": "task-1001",
  "active_entities": {
    "report_month": "2026-06"
  },
  "temporary_constraints": {
    "response.output_format": "plain_text"
  },
  "pending_questions": []
}

4.1 为什么不能只按轮数截取

“保留最近 20 轮” 并不可靠。工具结果可能占数千 Token,一轮也可能包含多个消息事件。更合理的是按 Token 预算和优先级选择:

P0:当前请求、安全规则、待确认问题
P1:当前任务状态、最近工具结果、用户纠正
P2:相关历史和决定
P3:寒暄、重复解释、旧话题

工具调用消息还必须成对处理,不能保留工具结果却丢掉对应调用,也不能把巨大结果在每轮重复塞入上下文。推荐保存 Artifact 引用和简短摘要,需要时再读取原始结果。

4.2 会话、任务和请求不是同一生命周期

Request:一次 HTTP 或模型调用
Session:一段连续交互
Task:可能跨多个 Session 的业务目标

用户关闭聊天窗口时,Session 可以结束,但正在等待审批的 Task 不能因此消失。


五、摘要记忆:压缩历史,但不能升级事实

长对话不能永久完整放进上下文,摘要用于保留关键连续性:

Goal             当前目标
Confirmed Facts  已确认事实
Decisions        已做决定
Constraints      约束
Completed        已完成事项
Pending          未决事项
Unknowns         未知信息
Errors           错误与恢复情况

推荐使用结构化摘要,而不是一段自由文本:

{
  "summary_id": "sum-300",
  "session_id": "s-500",
  "task_id": "task-1001",
  "source_range": {
    "from_sequence": 1,
    "to_sequence": 18
  },
  "facts": [
    {
      "key": "report.month",
      "value": "2026-06",
      "source_event_ids": ["msg-100"]
    }
  ],
  "decisions": [
    {
      "key": "report.currency",
      "value": "CNY",
      "source_event_ids": ["msg-120"]
    }
  ],
  "task_observation": {
    "status": "WAITING_FOR_APPROVAL",
    "state_version": 18
  },
  "version": 5
}

这里的 task_observation 只是生成摘要时看到的状态。恢复执行时,必须重新读取 TaskStore。

5.1 摘要最危险的错误

原始事实:

报告草稿已经生成,但尚未审批,也没有发布。

错误摘要:

报告已经完成。

这种摘要丢失了 “草稿”“尚未审批”“没有发布” 三个关键状态。摘要压缩时必须保留否定词、条件、时间、来源、不确定性和未完成状态。

5.2 降低摘要漂移

  • 保留原始事件;
  • 保存来源映射;
  • 使用不可变摘要版本;
  • 定期从原始事件重建;
  • 用户纠正后让旧摘要失效;
  • 删除原始记忆后重建派生摘要;
  • 用 Schema 校验状态和必填字段。

摘要是可重建视图,不是最终事实数据库。


六、用户 Profile:保存稳定默认值,而不是性格猜测

适合进入 Profile 的内容包括:

  • 默认语言;
  • 回答详细度;
  • 输出格式;
  • 项目级技术偏好;
  • 工作流默认值;
  • 无障碍需求。

不适合直接进入 Profile:

  • “这次用英文”;
  • 一次点击行为;
  • 模型推断的性格标签;
  • 当前订单状态;
  • 权限和角色;
  • 密码、Token、私钥。

推荐使用受控字段:

{
  "preference_id": "pref-800",
  "tenant_id": "tenant-a",
  "user_id": "u-1024",
  "preference_key": "response.output_format",
  "value": "markdown",
  "scope": {
    "type": "GLOBAL",
    "scope_id": null
  },
  "source_type": "EXPLICIT_USER_STATEMENT",
  "confirmation_level": "USER_CONFIRMED",
  "status": "ACTIVE",
  "version": 3,
  "supersedes": "pref-700"
}

6.1 作用域与覆盖

同一个偏好可以存在不同作用域:

产品默认
→ 用户全局偏好
→ 项目级偏好
→ 当前请求临时约束

例如:

全局偏好:默认中文、Markdown
项目 A:技术文档默认英文
当前请求:这次用中文纯文本

本次结果是中文纯文本,但全局和项目偏好都不需要永久修改。

6.2 明确表达优于隐式推断

可以将来源分级:

E1:用户明确要求保存
E2:用户明确陈述,但未要求保存
E3:多次稳定选择
E4:模型单次推断

E4 不应直接写入正式 Profile。模型可以创建候选,但不能把一次推断包装成用户事实。


七、任务状态:让长任务真正可恢复

聊天摘要可以说 “报告处理得差不多了”,但无法精确回答:

  • 哪些步骤已成功?
  • 哪个 Attempt 失败?
  • 图表文件在哪里?
  • 发布是否已经执行?
  • 重试会不会产生重复副作用?
  • 当前在等待谁批准?

因此要把任务拆成四个核心对象:

Task      完整目标
Step      可独立执行和验证的步骤
Attempt   Step 的一次执行尝试
Artifact  步骤产生的可追踪产物

7.1 Task 数据模型

{
  "task_id": "task-1001",
  "tenant_id": "tenant-a",
  "owner_user_id": "u-1024",
  "task_type": "MONTHLY_SALES_REPORT",
  "goal": {
    "month": "2026-06",
    "currency": "CNY"
  },
  "status": "WAITING_FOR_APPROVAL",
  "current_step_id": "publish_report",
  "completed_step_ids": [
    "load_data",
    "validate_data",
    "calculate_metrics",
    "generate_charts",
    "generate_draft"
  ],
  "workflow_version": "monthly-report-v3",
  "state_version": 18
}

7.2 状态机

CREATED
→ READY
→ RUNNING
→ WAITING_FOR_USER / WAITING_FOR_EXTERNAL / WAITING_FOR_APPROVAL
→ RETRY_SCHEDULED / PAUSED
→ SUCCEEDED / FAILED / CANCELLED

状态转换必须经过程序守卫。例如:

RUNNING → WAITING_FOR_APPROVAL
条件:草稿 Artifact 有效,审批请求已经创建

WAITING_FOR_APPROVAL → READY
条件:审批人角色正确,审批绑定的 Artifact 哈希未变化

RUNNING → SUCCEEDED
条件:发布系统确认最终结果

模型可以建议下一步,但不能直接把状态从 WAITING_FOR_APPROVAL 改成 SUCCEEDED

7.3 Attempt 与重试

每次重试都要创建新 Attempt:

{
  "attempt_id": "attempt-3002",
  "task_id": "task-1001",
  "step_id": "load_data",
  "attempt_number": 2,
  "status": "SUCCEEDED",
  "input_digest": "sha256:...",
  "output_digest": "sha256:...",
  "tool_call_ids": ["tc-900"]
}

覆盖历史失败记录,会让故障诊断、计费和副作用对账失去依据。


八、Checkpoint、幂等与 UNKNOWN_OUTCOME

8.1 Checkpoint 保存什么

{
  "checkpoint_id": "cp-180",
  "task_id": "task-1001",
  "state_version": 18,
  "task_status": "WAITING_FOR_APPROVAL",
  "current_step_id": "publish_report",
  "completed_step_ids": [
    "load_data",
    "validate_data",
    "calculate_metrics",
    "generate_charts",
    "generate_draft"
  ],
  "artifact_refs": [
    "artifact-data-v1",
    "artifact-charts-v2",
    "artifact-draft-v1"
  ],
  "pending_conditions": [
    {
      "type": "APPROVAL",
      "approval_id": "approval-77"
    }
  ]
}

Checkpoint 应在 Step 成功、进入等待状态、审批结果到达、暂停或发生不可恢复错误时写入。它不是一句 “任务做到一半”,而是可验证的结构化恢复入口。

8.2 幂等是恢复基础

推荐幂等键:

创建任务:user_id + request_intent_id
执行步骤:task_id + step_id + logical_input_version
审批请求:task_id + artifact_hash + approval_type
发布报告:task_id + final_artifact_hash + destination

请求重试时,相同业务意图应返回同一结果,而不是重复生成、重复发送或重复扣费。

8.3 结果未知不等于失败

发布接口超时后可能出现:

请求未到达
已经发布但响应丢失
仍在处理中
明确失败

此时状态应是 UNKNOWN_OUTCOME,处理流程是:

保存 operation_id 和幂等键
→ 禁止普通自动重试
→ 查询权威系统对账
→ 成功则补记状态
→ 明确失败才按策略重试
→ 仍未知则人工接管

这是避免重复副作用的关键边界。


九、外部知识记忆:交互结果不能自动变成真理

正式文档、代码、FAQ、工单和事故复盘都可能成为知识来源,但权威性不同。

等级 示例 使用方式
P0 正式制度、批准的 API 规范 当前有效范围内可直接使用
P1 领域负责人审核的 FAQ、Runbook 可发布使用
P2 已确认解决的工单、事故记录 需限制适用范围
P3 聊天、模型总结、未审核推断 只能作为候选

最危险的闭环是:

模型生成结论
→ 自动写入知识库
→ 下次检索到该结论
→ 模型因为“检索到了”而更加确信

正确流程是:

交互中发现经验
→ 创建 Knowledge Candidate
→ 关联来源和证据
→ 脱敏、去重、冲突检测
→ 领域人员审核
→ 版本化发布
→ 建立全文和向量索引

正式知识至少要包含来源、作用域、版本、有效期、权限、审核者和替代关系。

9.1 工单不能整张入库

一次故障处理可能包含:

重启服务:失败
清理缓存:失败
重新生成证书:暂时恢复
最终根因:缺少中间证书
正式修复:补全证书链

知识沉淀应提取症状、确认根因、诊断方法、最终方案、验证证据和适用版本。失败尝试要明确标记,客户隐私必须删除。


十、召回治理:先过滤,再排序,最后组装

召回系统不能只接收一句问题,还需要当前身份与执行上下文:

{
  "request_id": "req-9001",
  "user_id": "u-1024",
  "tenant_id": "tenant-a",
  "session_id": "s-501",
  "task_id": "task-1001",
  "intent": "RESUME_TASK",
  "roles": ["analyst"],
  "access_scopes": ["reports:read"],
  "request_constraints": {
    "response.output_format": "plain_text"
  }
}

10.1 Recall Plan

{
  "intent": "RESUME_TASK",
  "sources": [
    {"type": "TASK_STATE", "required": true},
    {"type": "SESSION_MEMORY", "limit": 8},
    {"type": "SUMMARY", "required": false},
    {
      "type": "USER_PROFILE",
      "fields": ["response.language", "response.output_format"]
    },
    {
      "type": "KNOWLEDGE",
      "query": "monthly report publish approval policy"
    }
  ],
  "token_budget": 4000
}

required=true 表示读取失败时不能假装继续。例如 TaskState 不可用时,高风险任务恢复必须停止。

10.2 硬过滤与软排序

硬过滤不满足就绝不能使用:

  • 租户和用户不匹配;
  • 当前角色无权访问;
  • 作用域不匹配;
  • 已删除、撤销或未发布;
  • 版本不适用;
  • 已过有效期;
  • 当前处理目的未获得同意。

满足硬条件后,才比较:

  • 语义和关键词相关性;
  • 与当前任务步骤的匹配;
  • 新鲜度;
  • 来源权威性;
  • 用户确认程度。

无权限内容即使相似度是 0.99,也必须在进入重排器和 Prompt 之前删除。


十一、冲突与覆盖:不是所有记忆都平级

在同一个属性、同一个作用域内,可以使用下面的覆盖关系:

系统与安全规则
> 当前明确请求
> 当前 TaskState 或权威工具结果
> 当前会话中用户刚确认的信息
> 用户长期 Profile
> 正式外部知识
> 历史摘要
> 模型推断或未审核候选

这不是一个适用于所有字段的总榜。不同维度可以同时生效:

长期偏好:默认中文
任务状态:等待财务审批

二者没有冲突。

真正冲突的示例:

Profile:默认 Markdown
当前请求:这次使用纯文本

当前请求胜出,但只在本次有效,不修改 Profile。

另一个示例:

旧摘要:报告已经完成
TaskState v18:草稿已生成,等待审批
发布系统:没有发布记录

正确结论是 “草稿已生成、尚未审批和发布”。摘要必须被标记为陈旧或不准确。

11.1 时间也参与冲突处理

需要区分:

observed_at       事实何时被观察
created_at        记录何时创建
effective_from    何时开始生效
effective_to      何时停止生效
expires_at        何时必须重新确认
last_confirmed_at 最后确认时间

昨天的余额可以用于解释昨天的决策,却不能回答 “现在余额是多少”。当前动态事实应回到权威工具。


十二、Memory Bundle:不要把召回结果直接拼成一坨文本

推荐先构造结构化结果:

{
  "request_constraints": [
    {
      "key": "response.output_format",
      "value": "plain_text",
      "source": "CURRENT_REQUEST"
    }
  ],
  "effective_preferences": {
    "response.language": "zh-CN",
    "response.output_format": "plain_text"
  },
  "task_state": {
    "task_id": "task-1001",
    "status": "WAITING_FOR_APPROVAL",
    "current_step_id": "publish_report",
    "state_version": 18
  },
  "knowledge": [
    {
      "knowledge_id": "kb-report-policy",
      "version": 4,
      "claim": "报告发布前必须获得财务审批"
    }
  ],
  "conflict_decisions": [
    {
      "key": "response.output_format",
      "winner": "CURRENT_REQUEST",
      "suppressed": "USER_PROFILE",
      "persistent_change": false
    }
  ],
  "warnings": [
    "任务尚未获批,不得执行发布"
  ],
  "version_vector": {
    "profile_version": 3,
    "summary_version": 5,
    "task_state_version": 18,
    "knowledge_version": 4
  }
}

Memory Bundle 进入 Prompt 时应明确分区:当前要求、有效偏好、任务状态、正式知识、最近上下文和风险警告。

执行副作用前,还要重新比较 state_version。因为召回完成到真正执行之间,任务可能已经被取消、暂停或修改,这就是典型的 TOCTOU 风险。


十三、纠错、删除与隐私:记忆必须可治理

13.1 纠错不是追加一个相反值

用户说:

不是,我已经搬到杭州了,别再说我在上海。

错误做法是让 “上海” 和 “杭州” 同时保持 ACTIVE。正确做法是:

上海 v1 → SUPERSEDED
杭州 v2 → ACTIVE
杭州 v2 supersedes 上海 v1

随后重建相关摘要、失效缓存和索引,并验证后续请求不再使用旧值。

如果该字段由 HR 等权威系统管理,用户声明可以触发变更流程或重新查询,但不能直接绕过权威系统修改权限类事实。

13.2 删除不是只删主表

一条记忆可能派生到:

主记录
→ 会话摘要
→ Profile 聚合字段
→ Embedding 和全文索引
→ 查询缓存
→ 推荐特征
→ 离线评估集
→ 备份

可靠删除应是一个可恢复任务:

DELETE_REQUESTED
→ IDENTITY_VERIFIED
→ PRIMARY_DATA_DELETED
→ INDEXES_PURGED
→ CACHES_INVALIDATED
→ DERIVED_DATA_REBUILT
→ VERIFIED
→ COMPLETED

验证不能只检查数据库为空,还要使用原表达和同义改写进行搜索,确认旧值无法从摘要、向量索引和缓存中恢复。

13.3 目的限制与最小化

用户提供地址用于配送,不代表可以用于营销、长期画像或模型训练。记忆可以记录允许目的、同意版本和保留期限;召回时检查当前用途是否匹配。

敏感信息、密码、Token 和私钥不应进入普通记忆系统。历史消息和检索文档也属于不可信输入,不能因为它们被保存过就拥有系统指令权限。


十四、贯穿案例:可恢复月度报告助手

任务流程:

load_data
→ validate_data
→ calculate_metrics
→ generate_charts
→ generate_draft
→ request_approval
→ publish_report

14.1 第一次会话

用户:

帮我生成 2026 年 6 月销售报告。

系统读取默认货币,创建 Task 和 Steps,执行到:

Task.status = WAITING_FOR_APPROVAL
current_step = publish_report
state_version = 18
draft_artifact = artifact-draft-v1
approval_id = approval-77

随后写入 Checkpoint 和结构化摘要,Session 关闭。

14.2 新会话恢复

用户:

继续上次报告,这次不要 Markdown,直接用纯文本告诉我进度。

系统执行:

识别 RESUME_TASK
→ 根据 tenant_id + user_id 找到可恢复任务
→ 读取 TaskState v18
→ 读取 Profile:默认中文、Markdown
→ 当前纯文本临时覆盖 Markdown
→ 召回正式审批规则
→ 发现任务仍未审批
→ 构造 Memory Bundle
→ 不执行发布

回答:

报告草稿已经生成,目前正在等待财务审批,尚未发布。
本次使用纯文本。审批完成后才能继续发布。

14.3 审批与发布

审批必须绑定 artifact-draft-v1 的内容哈希。若草稿变化,旧审批失效。

发布前重新检查:

  • 当前用户或执行器仍有发布权限;
  • TaskState 仍允许发布;
  • Approval 仍为 APPROVED;
  • 审批 Artifact 哈希与当前草稿一致;
  • 发布幂等键没有产生其他结果。

如果发布接口超时,进入 UNKNOWN_OUTCOME,通过 operation_id 对账。确认外部已发布后补记成功,而不是重新发布一次。

这个案例把五类记忆的职责串了起来:

Session       保存当前表达
Summary       压缩历史背景
Profile       提供默认语言和格式
TaskState     保存真实执行进度
Knowledge     提供审批规则
Recall Layer  处理过滤、覆盖与组装

十五、实现参考:后端组件与伪代码

建议的最小组件可以分为四组:会话与摘要服务、Profile 与知识读取、Recall Planner 与治理层、Task Orchestrator 与 Checkpoint;横向再提供幂等、租约、纠错、删除和审计能力。

核心编排:

def handle_request(request, principal):
    session = session_service.get_or_create(
        tenant_id=principal.tenant_id,
        user_id=principal.user_id,
        session_id=request.session_id,
    )

    session_service.append_user_message(session, request)

    intent = intent_router.classify(request.text)
    constraints = request_constraint_parser.parse(request.text)

    plan = recall_planner.create(
        intent=intent,
        request=request,
        principal=principal,
    )

    raw_memories = memory_orchestrator.read(plan, principal)

    allowed = memory_governance.hard_filter(
        raw_memories,
        principal=principal,
        purpose=intent,
        now=clock.now(),
    )

    current = memory_governance.select_current_versions(allowed)

    bundle = memory_governance.resolve_and_build(
        memories=current,
        request_constraints=constraints,
        token_budget=plan.token_budget,
    )

    if intent == "RESUME_TASK":
        result = task_orchestrator.resume(bundle, principal)
    else:
        result = response_service.answer(request, bundle)

    audit_service.record(request, plan, bundle, result)
    summary_service.schedule_if_needed(session, result)
    return result

执行任务时还要使用租约防止多个 Worker 同时领取,并在提交结果时通过 state_version 做乐观锁检查。外部调用前创建 Attempt,调用后以幂等键和预期状态版本提交结果;租约过期也不能绕过版本校验。

第一版可以使用单体服务、PostgreSQL、数据库任务表和规则式 Recall Planner。重点是先证明状态语义、幂等和治理,再升级消息队列、向量检索和分布式执行。


十六、可观测性:回答 “为什么记住、为什么召回”

一次召回应记录:

谁发起请求
为了什么目的
读取了哪些记忆源
哪些记录被选中
哪些记录被过滤或覆盖
使用了哪个版本
是否发生冲突
最终是否产生副作用

示例审计事件:

{
  "event_type": "MEMORY_BUNDLE_BUILT",
  "trace_id": "trace-9001",
  "tenant_id": "tenant-a",
  "principal_id": "u-1024",
  "purpose": "RESUME_TASK",
  "selected_refs": [
    "task-1001@18",
    "pref-800@3",
    "sum-300@5",
    "kb-report-policy@4"
  ],
  "decisions": [
    {
      "key": "response.output_format",
      "reason": "CURRENT_REQUEST_OVERRIDE"
    }
  ]
}

指标可以分为:

维度 关键指标
写入质量 候选通过率、无来源比例、未确认推断写入率、摘要 Schema 失败率
召回质量 必要记忆漏召回率、无关注入率、过期或被替代版本使用率、冲突检测准确率
任务可靠性 跨会话恢复成功率、重复副作用次数、陈旧 Bundle 拦截数、UNKNOWN_OUTCOME 对账时间
安全治理 跨租户候选数、无权限拦截数、纠错和删除传播时间、删除后重新召回次数

审计日志不应默认复制完整敏感正文。优先记录 ID、版本、哈希、原因码和必要摘要。


十七、测试:正常流程远远不够

测试域 必测行为
会话与摘要 保留当前请求和否定状态;摘要有来源;旧摘要不能驱动新 TaskState
Profile 默认值正确应用;临时覆盖不增加版本;明确长期修改才创建新版本
任务恢复 新会话恢复正确任务;多个候选时确认;Artifact 不重复生成;陈旧 Bundle 被拒绝
幂等与故障 重复创建、审批和发布只产生一个结果;崩溃可恢复;超时先进入 UNKNOWN_OUTCOME 并对账
权限与治理 跨租户候选为零;无权限不能发布;纠错替代旧版本;删除覆盖 Profile、摘要、索引和缓存

练习项目可以设定以下硬门槛:

任务恢复正确率 = 100%
重复副作用次数 = 0
跨租户候选数 = 0
未审批发布次数 = 0
当前请求覆盖准确率 = 100%
已删除记忆召回次数 = 0

十八、常见故障与定位

故障 主要原因 修复方向
记住用户没说过的偏好 模型推断直接写入 Profile 推断只进入候选区,明确确认流程
“这次用英文” 影响以后 临时约束被写成长期偏好 保存作用域,只有明确长期表达才更新
修改后仍使用旧值 旧版本、缓存或摘要未失效 建立 supersedes 并传播版本事件
恢复后重复发送 只读取摘要,未查询执行证据 TaskState、幂等键和权威系统对账
Task 长期卡在 RUNNING Worker 崩溃后没有恢复机制 租约、心跳、Attempt 和恢复扫描
删除后仍能语义检索 只删除主表 按数据血缘清理索引和摘要,并用改写查询验证
跨租户偏好污染 查询或缓存键缺少身份作用域 权限过滤前置,缓存绑定租户、用户和权限版本
摘要完成但任务待审批 把摘要当状态真相 执行读取当前 TaskState,摘要只作背景

十九、推荐实现顺序

  1. 划清 Session、Summary、Preference、Task 和 Knowledge 的职责;
  2. 实现少量结构化 Profile 字段与请求级覆盖;
  3. 建立 Task、Step、Attempt、Artifact、状态守卫和 state_version
  4. 加入 Checkpoint 与幂等,模拟服务重启;
  5. 实现会话窗口和带来源的结构化摘要;
  6. 实现 Recall Plan、硬过滤和 Memory Bundle;
  7. 接入带版本、权限和有效期的正式知识;
  8. 实现纠错、删除和派生数据清理;
  9. 补齐 Trace、指标以及崩溃、并发、超时和越权测试。

先用单体和关系数据库证明语义,再引入消息队列、分布式缓存和向量检索,通常更稳妥。


二十、总结

可靠的 Agent 记忆系统,不是 “保存更多”,而是 “在正确边界内保存,并在正确条件下使用”。

需要记住的核心原则:

  1. 模型不会自动拥有长期记忆,记忆由应用层管理;
  2. 会话、摘要、Profile、TaskState 和知识必须分层;
  3. 临时要求不能自动升级为长期偏好;
  4. 摘要负责压缩历史,不能代替权威任务状态;
  5. Task、Step、Attempt 和 Artifact 分离,才能恢复和审计;
  6. Checkpoint 必须结构化,副作用必须幂等;
  7. 工具结果未知时先对账,不能盲目重试;
  8. 交互经验先成为候选,审核后才能进入正式知识;
  9. 召回先做权限、作用域、版本和时间过滤,再做相关性排序;
  10. 冲突要按属性、来源和作用域处理,不能让模型自由猜测;
  11. 纠错要替代旧版本,删除要覆盖完整数据血缘;
  12. 只有通过恢复、并发、越权和删除测试,才能说明记忆系统可靠。

完整闭环可以压缩为:

信息出现
→ 类型路由
→ 候选与写入门槛
→ 来源、作用域、版本和有效期
→ 分层存储
→ Recall Plan
→ 权限与状态硬过滤
→ 冲突处理和 Memory Bundle
→ 执行前重新验证
→ 幂等执行与 Checkpoint
→ 纠错、删除、审计和持续评估

当这条链路建立起来后,Agent 才不只是 “记得聊过什么”,而是能够在中断、重试、审批、纠错和删除发生时,仍然保持行为一致、状态可恢复、来源可解释。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐