Agent 记忆不只是聊天记录:讲透会话、Profile、TaskState、召回与删除治理

作者:逆境不可逃
技术永无止境
希望我的内容可以帮助到你!!!!
本文承接《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,摘要只作背景 |
十九、推荐实现顺序
- 划清 Session、Summary、Preference、Task 和 Knowledge 的职责;
- 实现少量结构化 Profile 字段与请求级覆盖;
- 建立 Task、Step、Attempt、Artifact、状态守卫和
state_version; - 加入 Checkpoint 与幂等,模拟服务重启;
- 实现会话窗口和带来源的结构化摘要;
- 实现 Recall Plan、硬过滤和 Memory Bundle;
- 接入带版本、权限和有效期的正式知识;
- 实现纠错、删除和派生数据清理;
- 补齐 Trace、指标以及崩溃、并发、超时和越权测试。
先用单体和关系数据库证明语义,再引入消息队列、分布式缓存和向量检索,通常更稳妥。
二十、总结
可靠的 Agent 记忆系统,不是 “保存更多”,而是 “在正确边界内保存,并在正确条件下使用”。
需要记住的核心原则:
- 模型不会自动拥有长期记忆,记忆由应用层管理;
- 会话、摘要、Profile、TaskState 和知识必须分层;
- 临时要求不能自动升级为长期偏好;
- 摘要负责压缩历史,不能代替权威任务状态;
- Task、Step、Attempt 和 Artifact 分离,才能恢复和审计;
- Checkpoint 必须结构化,副作用必须幂等;
- 工具结果未知时先对账,不能盲目重试;
- 交互经验先成为候选,审核后才能进入正式知识;
- 召回先做权限、作用域、版本和时间过滤,再做相关性排序;
- 冲突要按属性、来源和作用域处理,不能让模型自由猜测;
- 纠错要替代旧版本,删除要覆盖完整数据血缘;
- 只有通过恢复、并发、越权和删除测试,才能说明记忆系统可靠。
完整闭环可以压缩为:
信息出现
→ 类型路由
→ 候选与写入门槛
→ 来源、作用域、版本和有效期
→ 分层存储
→ Recall Plan
→ 权限与状态硬过滤
→ 冲突处理和 Memory Bundle
→ 执行前重新验证
→ 幂等执行与 Checkpoint
→ 纠错、删除、审计和持续评估
当这条链路建立起来后,Agent 才不只是 “记得聊过什么”,而是能够在中断、重试、审批、纠错和删除发生时,仍然保持行为一致、状态可恢复、来源可解释。

更多推荐


所有评论(0)