OpenViking 深度拆解:为什么 Agent 记忆需要目录和摘要?
从 L0/L1/L2 和分层检索出发,拆解上下文导航,再看队列、摘要与索引的一致性为什么决定系统是否可靠。
博客本文目录
Memory Agent 深度拆解 · 第 4 / 8 篇。查看系列目录。
一个助手同时参与三个项目,三个项目都有叫“部署说明”的文档,也都提到 PostgreSQL、Docker 和灰度发布。
你问“这个项目怎么上线”,它检索出了另一套环境的操作步骤。每一段都很相似,组合起来却不对。
OpenViking 给这个问题的回答,是让资源、记忆与技能拥有可浏览的目录结构,并让目录摘要参与查找。它把“资料放在哪里”纳入了上下文检索。
本文依据 2026 年 9 月 22 日读取的提交 b8bed5a1ad3a 与官方文档,未执行部署或基准测试。
1. 虚拟文件系统,先给上下文一个地址
OpenViking 用 viking:// URI 为内容提供统一地址。项目资料、用户记忆和技能可以在同一套上下文体系中组织,但保留不同的类型与作用域。官方项目说明
有了地址,你可以表达“在这个项目目录里找”,而不是让一次查询面对所有内容。
目录还提供人可理解的边界:一个部署文件属于哪个项目,一段用户偏好属于谁,一项技能解决什么任务,都有了组织位置。
这不代表底层只用普通文件完成所有事情。内容存储、向量索引与处理队列仍然需要协作。URI 是组织和寻址方式,不是对全部物理存储实现的概括。官方架构说明
2. L0、L1、L2:先判断值不值得读
面对一整个知识库,Agent 没必要先把所有文件读一遍。
OpenViking 用 L0 摘要、L1 概览与 L2 详细内容提供逐级阅读入口。目录中的 .abstract.md 与 .overview.md 帮助判断主题和结构,完整内容留到需要时再读取。官方内容层级说明
这个设计节约的是“为判断相关性而阅读全文”的成本。
不过摘要也是一种信息压缩。部署文档里一句“仅限测试环境”如果没有被保留下来,摘要可能看上去适用于所有环境。因此,我会把安全限制、版本范围、适用对象视为摘要中不能轻易丢失的内容。
这是一项应用层的质量要求,不是说任何自动摘要都已经做到。
3. find 与 search,差的是一段查询规划
find() 接收明确查询,search() 还会结合会话上下文做意图分析。后者可以把用户问题拆成面向 memory、resource、skill 的不同查询。官方检索文档
源码中的 IntentAnalyzer 收集压缩摘要、近期消息、当前问题与目标目录摘要,再调用规划模型,解析成 QueryPlan 与 TypedQuery。源码:意图分析器
比如“按上次的方法,把这个项目发出去”,单看当前句子信息很少。规划阶段可以利用会话找出“这个项目”和“上次的方法”分别是什么,再寻找相关资料与技能。
但规划会增加模型调用、解析与等待。对于已经很明确的“查某个接口参数”,未必值得每次先做一次意图分析。选择接口时,应把效果收益和额外延迟一起测。
4. 层级检索不是简单地递归读文件
HierarchicalRetriever 使用优先队列组织待搜索的目录。它从起点展开子节点,按分数选择后续目录,对结果去重,并在若干轮展开后判断是否继续。源码:层级检索器
在当前递归路径里,子节点最终评分还可以结合父目录评分,形式可概括为:
子节点最终分数
= α × 子节点查询分数 + (1 − α) × 父目录分数
具体分支与参数以源码为准;没有父评分时走对应的子节点评分。配置合适时,目录背景可以为下层内容提供额外相关性线索。
它的代价也由公式直接体现:父目录判断有偏差,可能影响下层候选。所以目录摘要和文件自身相关性之间的权重,需要在自己的材料上验证。
重排同样是有条件启用。当前递归代码在相应模式且存在可用重排客户端时,才调用重排;不能把所有查询都画成固定经过一次外部重排模型。
5. 会话怎样变成长时记忆?
记忆写入不只是把对话存成文件。
当前 session 提交流程会归档消息,补充提取所需的工具内容,按 policy 运行记忆提取,并决定哪些内容进入自己或 peer 的记忆空间。公开设计文档还区分普通用户记忆抽取与基于 case 的后续经验、轨迹及技能形成。当前会话提取流程
一个值得注意的条件是:执行相关的训练输出有其触发要求,不能因为会话执行了工具,就认定一定生成了技能。
另一个细节是写入归属。提取器给出候选操作之后,还需要根据允许的目标决定最终 URI。自然语言中出现一个名字,不等于模型可以任意向那个人的记忆目录写入。
这体现了一项很实用的原则:让模型提出内容,让确定性的路由与权限逻辑决定可以写到哪里。
6. 为什么队列为空,仍然可能没完成?
这是这个项目里很值得单独学习的一页设计。
假设队列原来有一个任务。worker 将它取走,开始生成摘要。此刻待领取队列已经是零,但摘要还没完成。
如果监控只看队列长度,就会过早显示“完成”。OpenViking 的队列文档明确区分 pending 与 processing,完成条件是二者都为零,也就是 unacked = 0。队列生命周期文档
更进一步,两个计数应由队列后端从一致状态中提供。用后端的 pending 加上某个 Python 进程的本地计数,在多个进程并发时不一定能描述完整状态。
这不仅影响仪表盘。导入完成后立即检索、测试程序何时开始判分、服务退出前是否排空工作,都依赖同一项完成语义。
本文依据设计文档解释该协议,尚未对各队列后端做重启与故障注入验收。
7. 文件、索引和队列之间,没有凭空出现一笔大事务
OpenViking 的事务文档明确说明,路径锁与持久队列恢复,并不组成横跨文件系统、向量库和队列管理器的原子事务。一致性与恢复说明
这点非常重要。内容可能已经提交,而摘要或 embedding 等派生状态还在追赶。路径锁约束竞争写入,恢复流程帮助继续未完成工作,但不能把所有状态变化压成一个瞬间。
因此,应用最好能观测“文件已存在”“派生内容已就绪”“查询已可见”几个阶段。出错后也应该知道重试哪一步,而不是只得到一个笼统的导入失败。
从产品角度看,用户可以接受“正在处理资料”;更难接受的是系统声称已经记住,下一轮却完全查不到,又没有任何解释。
8. 如何判断目录路线是否值得?
我会准备两个内容相似的项目,放入几份同名文档,再加入一份跨项目通用规范。分别测试指定目录查询、含糊会话查询、文档移动、摘要更新和后台任务恢复。
不仅记录答案,还记录最终读取了哪些 URI,走过多少目录,花了多少规划与重排成本。这些路径信息能帮助解释收益来自哪里。
当知识天然分项目、主题与技能,且希望人能检查和修改记忆结构时,这条路线值得优先研究。对只有几条用户偏好的小应用,它的组织和处理链路可能过重。
OpenViking 的核心启发是:检索质量也取决于资料如何组织。 当目录、摘要、版本和处理状态都成为系统的一部分,Agent 才有机会知道自己在读哪里,以及那里的内容是否已经准备好。
系列阅读
上一篇:Letta 深度拆解。
下一篇:Hindsight 深度拆解。