Memory:PowerContext 怎样保存、更新和复用一条知识

7476 字
19 分钟

Memory:PowerContext 怎样保存、更新和复用一条知识

发布于

Memory:PowerContext 怎样保存、更新和复用一条知识

项目里有些信息会反复影响后续工作:金额使用什么单位、错误信息必须包含什么、为什么选定某种实现。如果只留在对话中,新会话就需要重新寻找。

PowerContext 的 Memory(记忆) 把这些信息保存为可以独立维护的知识条目。后续任务可以找到相关条目;规则发生变化时,也可以修订或停用,并保留历史。

本文用“订单 CSV 导入器”贯穿讲解。金额规则、标识和版本变化都是教学示例,重点是理解系统如何组织这些知识。

1. 从一段讨论到几条知识

假设你和 Agent 确定了三个约定:

  • 订单金额以整数分存储。

  • 金额转换使用 Decimal,避免二进制浮点误差。

  • 导入错误要包含 CSV 原始行号。

后续实现或检查代码时,这些约定都有用。可以把它们分别保存成三条 Entry(知识条目),让每条规则都能单独查询、修改和引用。

一段讨论可以整理为三条独立维护的 Memory Entry
一段讨论可以整理为三条独立维护的 Memory Entry

这里的“独立”是指内容可以分别维护。例如,后来调整金额转换方式,只需要更新对应条目,不必把其他约定一起改写。

2. Memory Artifact 和 Entry 是什么关系

2.1 一份 Memory,管理多条 Entry

先选定订单项目的 Scope。在这个范围里,Memory Artifact 管理一组知识;Entry 是其中可以独立维护的单元。

Scope 内的 Memory Revision 用条目目录指向各 Entry 的具体版本,正文保存在条目版本中
Scope 内的 Memory Revision 用条目目录指向各 Entry 的具体版本,正文保存在条目版本中

一份 Memory 记录了哪些信息? 它沿用 Artifact 的基本结构,可以先认识下面几部分:

| 信息 | 作用 | 示例或含义 |

| --------------- | ------------------------------ | ------------------------------------------------------ |

| family | 说明制品类型 | 固定为 memory |

| artifact_id | 标识是哪一份 Memory | 例如 mem_orders,更新后身份不变 |

| revision | 标识整份 Memory 的快照版本 | 例如 2,表示第二版 |

| content | 记录这一版管理的条目及本次变化 | 哪些条目、采用哪个内容版本、是否有效,以及本次改了什么 |

| lineage | 记录形成这一版的来源关系 | 所依据的 Source 或其他 Artifact 引用 |

其中,前三项组合起来,就能在相应 Scope 内定位一份 Memory 的精确版本:


family / artifact_id @ revision

memory / mem_orders  @ 2

这里的 memory 表示制品类型,mem_orders 是这份制品的稳定身份,2 是它的第二版。更新同一份 Memory 时,身份保持不变,正式提交的变化会产生新 Revision。

接着看 content。可以把 Memory 想成一本项目规则册,Entry 是其中一条规则。系统需要记下这版册子收录了哪些规则,以及这次修改了什么,因此把 content 的主要信息分成两部分:

  • manifest(条目目录):例如,E1 采用 E1-v2,E2 采用 E2-v1,并分别记录它们是否仍然有效。

  • changes(本次变更记录):例如,这次修订了 E1;它从 E1-v1 变成了 E1-v2。

条目正文保存在独立的 Entry 版本中,目录通过标识指向它。这样,每份 Memory 快照都能确定当时采用哪些内容,而不必每次复制所有条目的正文。

2.2 提交一条知识,需要哪些内容

显式保存时,调用方主要提供:

| 字段 | 示例 | 用途 |

| ------------ | ------------------------------ | ---------------------- |

| scope_id | 订单项目的 Scope ID | 确定保存到哪个范围 |

| kind | decision | 描述这条知识的业务类别 |

| text | amount: 金额以整数分存储。 | 实际保存和检索的正文 |

| reason | 团队确认的存储约定 | 可选的变更原因 |

kind 常见的值有 fact、preference、decision、constraint 和 working_note。显式写入接口允许业务自行组织类别;内置模型提取则使用约定的类别集合。它们都属于 Memory,kind 不会创建新的 Artifact 类型。

一条知识最好表达一个可以单独判断、单独修改的事实或规则。

2.3 保存后,系统维护哪些身份

调用方不需要自己编造条目版本。系统维护:

| 信息 | 回答的问题 |

| -------------------- | ---------------------------- |

| artifact_id | 属于哪一份 Memory? |

| revision | 整份 Memory 的哪次快照? |

| entry_id | 是其中哪一条知识? |

| entry_version_id | 这条知识的哪个具体内容版本? |

| version | 这条知识自身更新到了第几版? |

先记住:整份 Memory 有快照版本,每条 Entry 也有自己的内容版本。 修改其中一条知识,不要求其他条目的内容版本一起变化。

3. 一条知识怎样进入 Memory

3.1 显式保存:调用方已经知道要记什么

你明确告诉 Agent:“请记住,金额使用整数分。”调用方可以直接调用 remember_memory。对应的 HTTP 请求如下,示例省略认证信息:


POST /v1/memory/remember

Content-Type: application/json


{

  "scope_id": "服务端返回的 Scope ID",

  "kind": "decision",

  "text": "amount: 订单金额以整数分存储;100 表示 1 元。",

  "reason": "团队确认的金额存储约定"

}

服务端检查输入,生成条目身份和内容版本,更新 Memory 的目录与变更记录,并维护相应检索索引。成功保存新的条目后,响应会带回 Memory 的精确版本,以及条目内容和 citation。

citation(精确引用) 像一枚书签,记下“哪份 Memory 的哪一版、其中哪条知识的哪个内容版本”。保留它,后续就能准确读取当时的内容,或在修改时让服务端检查版本是否已经变化。

这条路径不需要生成模型替你概括正文;如果部署启用了向量索引,写入时需要调用 Embedding 模型生成向量。

重复提交相同的规范化内容可能被去重,不一定产生新条目或新 Revision。这种情况下,响应中的 entry 可能为空,应检查实际结果。

3.2 从 Source 提取:让模型提出值得保存的知识

另一种情况是,你提交了一段工作记录,希望系统从中找出以后有用的信息。这段记录先保存为 Source,再由模型总结,提出值得保存的知识。

显式保存提交的是“要记住的内容”;Source 采集提交的是“用于整理的材料”。 比如一份工作记录里既有调试过程,也有最终确认的金额规则,自动提取要从这些材料中找出后续任务值得复用的部分。

显式写入与 Source 提取在校验、保存条目版本和更新索引阶段汇合
显式写入与 Source 提取在校验、保存条目版本和更新索引阶段汇合

Source 保存成功,只表示材料已经进入系统。什么时候开始整理、模型怎样提出条目、结果怎样保存,我们接着看。

4. 模型自动总结怎样工作

4.1 什么时候开始:后台调度与 flush

自动总结需要配置生成模型和 Memory 提取能力。这里的“自动”,是指由模型从材料中提出知识;处理可以由后台发起,也可以由调用者主动触发。

  • 后台调度:配置 memory_schedule_seconds,并运行相应的后台处理组件,让系统按配置调度待处理材料。这个配置默认关闭,需要主动配置。

  • 主动触发:调用 Memory 的 flush,让系统现在处理一批待办材料。例如,一段工作结束后,希望整理刚提交的工作记录。

HTTP 调用方式是向 POST /v1/memory/flush 提交:


{

  "scope_id": "服务端返回的 Scope ID"

}

这里的 flush 可以理解为“整理下一批材料”。当前 Memory 接口会等待本批处理完成,并返回处理结果。但它也不保证一次处理完所有积压材料。

4.2 每次整理哪些材料:游标与窗口

系统为 Scope 中的 Memory 提取记录一个 Cursor(游标),表示 Source 日志已经处理到哪里。开始整理时,再查看当前日志的末尾位置,即 high_watermark,然后从游标后面取一个有限大小的窗口。

比如,已经处理到第 2 个位置,当前共有 7 个位置;为方便说明,假设每次最多推进 3 个位置:

从旧游标 2 取出位置 3 到 5,成功后推进到 5,位置 6 到 7 留给后续处理
从旧游标 2 取出位置 3 到 5,成功后推进到 5,位置 6 到 7 留给后续处理

本次只处理 (2, 5] 这个窗口。成功后游标前进到 5,剩下的材料留给后续处理;处理中又进入系统的材料,也不属于这次已经确定的窗口。

窗口大小由运行配置 source_window_limit 控制,默认是 100。上面的 3 只是示例;当前 HTTP 请求只传 Scope,不接收窗口大小参数。

窗口中的材料还要经过生成用途和可用性筛选。 因此,游标前进了几个位置,不一定就有几份 Source 交给模型;它记录的是扫描进度,不是生成了多少条知识。

4.3 怎样判断这一批是否处理完成

了解游标和窗口后,就可以看懂 flush 返回的处理结果。它回答两个问题:材料处理到哪里了,以及处理后关联哪份 Memory。

沿用上面的窗口示例,假设位置 3 到 5 的三份 Source 都进入处理,返回结构可以是下面这样(数值和标识为示意):


{

  "status": "processed",

  "previous_cursor": 2,

  "current_cursor": 5,

  "high_watermark": 7,

  "processed_source_count": 3,

  "memory": {

    "family": "memory",

    "artifact_id": "mem_orders",

    "revision": 2

  }

}

这表示本批已处理到位置 5,还有位置 6 到 7 待处理;结果关联的是 mem_orders 的第 2 版。各字段含义如下:

  • status:idle 表示没有待推进的窗口;processed 表示本次游标已前进,不等于新增了知识。

  • previous_cursor / current_cursor:本次处理前后的游标位置。

  • high_watermark:本次开始时看到的 Source 日志末尾。

  • processed_source_count:本批筛选后进入处理的 Source 数量,不是生成的 Entry 数量。

  • memory:处理结果关联的 Memory 精确版本,也可能为空。

memory 有值也不一定代表产生了新版本:如果候选为空或内容没有实际变化,可能返回原有 Memory 的引用。要确认知识变了什么,应查看对应 Revision 和条目内容。

如果 current_cursor < high_watermark,说明本次看到的材料还没有全部处理完,可以由后续调度或再次调用 flush 继续。这样,一长串工作记录就能分批整理,而不必在一次模型调用里塞入全部历史。

4.4 模型看到什么,又提出什么

知道怎样发起处理、确认进度后,再来看这一批内部怎样提取知识。

程序准备好本批可用证据,以及所选 Memory 当前有效的条目,再交给模型。已有条目让模型有机会判断:这是新知识,还是对原有知识的补充?

假设已有一条记忆:


E1:amount: 金额以整数分存储。

本次材料中有两段记录:


S1:金额转换改用 Decimal,仍然以整数分存储。

S2:导入失败时,错误信息必须包含原始行号。

模型可能提出两项变化:

  • 修订 E1:补充“转换使用 Decimal”,依据 S1。

  • 新增条目:错误信息必须包含原始行号,依据 S2。

这只是示意,实际如何拆分和表达由模型输出决定。模型也可以认为没有值得保存的新知识,返回空候选列表。

内置提取允许模型提出 add(新增)或 revise(修订)。每项提议要带上知识类型、正文,以及本次输入中的证据编号;修订还要指出对应的已有条目。程序随后把这些临时证据编号对应回真实材料。

模型不会直接分配正式条目身份、版本号。

4.5 谁决定保存:程序校验与提交

模型返回的是候选变化,还不是已经保存的 Memory。程序会检查:

  • 提议是否符合规定的结构、知识类型和正文要求。

  • 是否引用了本次输入中的证据,不能凭空编造来源。

  • 修订目标是否是提供给模型的有效条目,同一条目不能在一次提取中被重复修订。

接着,程序按内容规范化、去重和版本规则整理候选。有实际变化时,保存新的 Entry 内容版本,创建 Memory Revision,更新目录、变更记录及检索数据。

Memory 的落库变化与本批游标推进在同一个事务中提交。 如果提取或提交失败,不会把这一批直接标成已经处理完;如果成功处理但没有需要保存的变化,游标仍然可以前进。

这里的候选是程序内部的提交前结果,不需要先进入人工审核收件箱。程序校验可以约束结构、引用和版本关系,但知识内容是否准确,仍要结合原始材料判断。

5. 规则变化后,版本怎样演进

5.1 修改一条 Entry,会发生什么

假设第一版 Memory 已有两条规则,后来先修订 E1,再停用 E2:

三个 Memory 快照展示条目修订与停用:只修改目标条目的内容版本,历史仍保留
三个 Memory 快照展示条目修订与停用:只修改目标条目的内容版本,历史仍保留

图中的两层变化要分开看:

  • 修订 E1:entry_id 保持不变,产生新的 entry_version_id,条目自身的版本号增加;整份 Memory 同时产生新 Revision。

  • E2 没有修改正文:可以继续使用原来的内容版本。

  • 停用 E2:新的 Memory 目录把它标成 inactive,不需要为相同正文再生成一个内容版本。

每次提交的是一份新的状态快照,旧快照仍能回答“当时保存了什么”。head 指向当前最新版本,读取历史时则指定精确 Revision。

5.2 Citation:明确指向当时那条知识

citation 可以理解为精确书签:它既标记整本规则册的版本,也标记其中某条知识的内容版本。


memory_ref:memory / mem_orders @ 2

entry_id:E1

entry_version_id:E1-v2

这些标识是示意值,实际使用服务端返回的 citation。搜索、列举或读取条目时得到它,修改条目时再把它一起提交。

“引用了哪条知识”与“知识依据什么产生”也要区分:citation 定位内容;条目的来源引用及 Artifact 的 lineage 用来追溯所依据的材料。两者都不表示历史内容一定仍然适用。

5.3 为什么会返回 409 冲突

假设你拿到的 citation 指向 Memory 第 2 版,准备修改 E1;此时另一位调用者新增了 E3,使 Memory 前进到第 3 版。

甲持有第二版引用,乙新增条目使 Memory 前进到第三版,甲提交修改时触发整份 Memory 的版本冲突
甲持有第二版引用,乙新增条目使 Memory 前进到第三版,甲提交修改时触发整份 Memory 的版本冲突

即使 E1 的正文没有变化,你基于第 2 版提交修改,当前接口仍会返回版本冲突:它会检查 citation 中的整份 Memory Revision 是否仍是当前版本。

遇到冲突,应重新取得当前条目及 citation,比较变化后再决定如何修改。旧 citation 仍可用于精确读取历史,但不能直接当成修改当前状态的依据。

5.4 停用:退出当前召回,保留历史

假设导入接口已经改为提交完整错误报告,不再沿用旧的行号提示规则,可以对对应条目调用 retire_memory_entry。

停用把条目从当前有效召回范围中移出,保留旧正文和版本。列举时可以通过 include_inactive 查看停用条目,也可以按历史 citation 读取当时的内容。

搜索当前知识与读取历史版本回答的是两个不同问题:“现在有哪些可用规则”和“当时记录了什么”。

6. 后续任务怎样找到并使用 Memory

6.1 搜索返回的是相关条目

新会话问:“amount 字段应该怎么处理?”客户端可以在同一个 Scope 中搜索:


POST /v1/memory/search

Content-Type: application/json


{

  "scope_id": "同一个 Scope ID",

  "query": "amount",

  "mode": "fts"

}

服务端检索当前 Memory 中有效条目的索引,返回命中的正文、分数及精确 citation。停用条目也不会作为当前有效结果参与普通召回。

| 模式 | 怎样找相关内容 | 需要什么 |

| ---------- | ------------------------ | ---------------------------- |

| fts | 根据词项匹配进行全文检索 | 后端的全文检索能力 |

| vector | 根据向量距离寻找相近内容 | Embedding 模型和可用向量索引 |

| hybrid | 结合全文和向量通道的结果 | 两种检索能力 |

| auto | 按当前能力选择模式 | 以响应中的实际模式为准 |

6.2 PreparedContext:从命中到本次上下文

如果目的是给 Agent 准备背景,客户端可以调用 prepare_context。服务端会根据当前问题进行召回,再按组装规则和大小预算选择内容,生成 PreparedContext。

当前有效条目经过检索与预算组装成为临时上下文,再由客户端提供给 Agent
当前有效条目经过检索与预算组装成为临时上下文,再由客户端提供给 Agent

max_bytes 限制返回文本的 UTF-8 字节数。预算不足时,正文可能被缩短或条目被跳过;存储中的完整记忆保持不变。没有合适内容时,返回 empty 是正常结果。

7. 把保存、使用和维护连起来

第一天确认项目约定时,调用方可以显式保存;如果提交的是工作材料,则由配置的流程提取。服务端把知识组织为 Entry,并保留 Memory 的版本快照。

第二天,新会话在同一个 Scope 中请求相关背景。服务端检索有效条目、准备上下文,Agent 结合当前代码继续工作。规则后来有变化,就用当前 citation 修订;不再适用的规则则停用,历史仍然保留。

Memory 的完整使用循环:保存或提取、持久化、召回使用,再修订或停用
Memory 的完整使用循环:保存或提取、持久化、召回使用,再修订或停用

理解 Memory 时,可以抓住四个对象:Entry 表达一条知识,Memory Revision 记录一组条目的状态,citation 定位当时的具体内容,PreparedContext 把选中的历史带入当前任务。

Last updated on