Memory:PowerContext 怎样保存、更新和复用一条知识
发布于
Memory:PowerContext 怎样保存、更新和复用一条知识
项目里有些信息会反复影响后续工作:金额使用什么单位、错误信息必须包含什么、为什么选定某种实现。如果只留在对话中,新会话就需要重新寻找。
PowerContext 的 Memory(记忆) 把这些信息保存为可以独立维护的知识条目。后续任务可以找到相关条目;规则发生变化时,也可以修订或停用,并保留历史。
本文用“订单 CSV 导入器”贯穿讲解。金额规则、标识和版本变化都是教学示例,重点是理解系统如何组织这些知识。
1. 从一段讨论到几条知识
假设你和 Agent 确定了三个约定:
-
订单金额以整数分存储。
-
金额转换使用 Decimal,避免二进制浮点误差。
-
导入错误要包含 CSV 原始行号。
后续实现或检查代码时,这些约定都有用。可以把它们分别保存成三条 Entry(知识条目),让每条规则都能单独查询、修改和引用。
这里的“独立”是指内容可以分别维护。例如,后来调整金额转换方式,只需要更新对应条目,不必把其他约定一起改写。
2. Memory Artifact 和 Entry 是什么关系
2.1 一份 Memory,管理多条 Entry
先选定订单项目的 Scope。在这个范围里,Memory Artifact 管理一组知识;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 保存成功,只表示材料已经进入系统。什么时候开始整理、模型怎样提出条目、结果怎样保存,我们接着看。
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, 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:
图中的两层变化要分开看:
-
修订 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 版。
即使 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。
max_bytes 限制返回文本的 UTF-8 字节数。预算不足时,正文可能被缩短或条目被跳过;存储中的完整记忆保持不变。没有合适内容时,返回 empty 是正常结果。
7. 把保存、使用和维护连起来
第一天确认项目约定时,调用方可以显式保存;如果提交的是工作材料,则由配置的流程提取。服务端把知识组织为 Entry,并保留 Memory 的版本快照。
第二天,新会话在同一个 Scope 中请求相关背景。服务端检索有效条目、准备上下文,Agent 结合当前代码继续工作。规则后来有变化,就用当前 citation 修订;不再适用的规则则停用,历史仍然保留。
理解 Memory 时,可以抓住四个对象:Entry 表达一条知识,Memory Revision 记录一组条目的状态,citation 定位当时的具体内容,PreparedContext 把选中的历史带入当前任务。