我是如何理解 OpenViking 的
我是如何理解 OpenViking 的
这不是一篇 OpenViking 使用手册,而是我这次学习过程的记录。重点不是罗列它有哪些 API,而是保留我是从什么问题出发、形成过哪些判断、又为什么修正这些判断的。以后重新接触 OpenViking 时,我希望先恢复这套认知,而不是重新啃一遍官方文档。
一开始:它不就是一个资料库吗
我最初看到的 OpenViking 很像一个资料库:可以向里面添加各种类型的资源,然后列出、读取或搜索这些资源。如果只看这些能力,它和文件系统似乎没有本质区别,无非是换了一组 API。
这个判断不算错,但只看到了 L2,也就是具体内容。OpenViking 在普通文件之上增加了几件事:
- 为内容生成可供快速判断相关性的摘要和概览;
- 把内容、目录和语义索引组织到统一的 URI 空间;
- 用 embedding 和向量检索,从“我知道文件在哪里才能读取”变成“我描述需求,它替我找相关内容”;
- 把资源、长期记忆和 Skill 放进同一套可检索、可逐层展开的上下文系统;
- 记录 session,并从对话中提取可以跨 session 使用的长期记忆。
所以我后来更愿意把它理解成:
OpenViking 是建立在类文件系统之上的 Agent 上下文与长期记忆管理层。文件系统负责“保存”,它还负责“理解、索引、召回和逐步展开”。
这也解释了为什么它并不只适合 AI Agent。资源管理、语义搜索和分层浏览本身可以被普通应用使用;只是记忆提取、自动召回和 Skill 检索明显是围绕 Agent 工作流设计的。
我曾把 L0、L1、L2 理解成三层缓存
看到 .abstract.md、.overview.md 和具体文档后,我自然形成了一个映射:
.abstract.md = L0
.overview.md = L1
具体文档 = L2
这个映射对 Resource 目录基本成立,但“三层缓存”这个说法不准确。它们不是同一份内容在不同速度介质中的缓存副本,而是不同信息密度的表达:
- L0 回答“这是什么,是否值得继续看”;
- L1 回答“这里大致有什么,下一步该进入哪里”;
- L2 才是实际内容。
我的下一个疑问是:是不是每个文件夹都有 .abstract.md 和 .overview.md?答案也不是绝对的。受 OpenViking 语义处理和目录管理的资源目录通常会有这些派生文件,但不能把它当成任意文件夹都必须满足的物理文件系统规则。某些目录是预置的,某些内容按需生成,普通目录或尚未完成语义处理的目录未必具备完整的 L0/L1。
接着我追问 Memory 的目录是不是也完全复制这套结构。这里又需要修正:Memory 也会参与摘要、概览和向量化,但它首先由记忆 schema 决定目录和文件。例如 event 按日期组织,preference 按用户和主题组织,profile 是固定文件。不能先假定“所有 Memory 都是一棵标准 Resource 三层树”,再去套它。
Resource、Memory、Skill 是三类上下文,不是三种文件格式
我逐渐确认 OpenViking 面向使用者的核心上下文可以分成三类:
resource:外部资料、项目文件、网页、代码、图片等;memory:从交互中形成的长期信息,包括画像、偏好、实体、事件和经验;skill:告诉 Agent 如何完成某类任务的可调用能力说明。
它们底层都能以 URI 和文件形式出现,也都可能被搜索,但语义和生命周期不同。Resource 通常由人或程序导入;Memory 可以由 session commit 后自动提取;Skill 更像操作规程和能力入口。把它们理解成“同一个资料库里的三个文件夹”会漏掉这些行为差异。
接入 Agent 的本质:既要给能力,也要给使用时机
我随后把问题转向了 Agent 集成。我形成的第一个结论是:只要把 OpenViking 是什么、里面有什么、接口怎么调用告诉 Agent,Agent 就可以通过 API 或 MCP 获取 Resource、Memory 和 Skill。
这个结论基本正确,但后来我把接入拆成了三层:
Skill / 系统提示词:告诉模型 OpenViking 是什么、什么时候应该使用
MCP / API:给模型真正执行 search、read、recall、remember 的工具
Hook / Middleware:不依赖模型主动判断,在生命周期节点自动召回和捕获
纯 MCP 是最小接入:模型知道工具存在,需要时主动调用。自动召回则不是 MCP 自己完成的,而是 Agent 平台提供的 Hook 或中间件在每次 prompt 前调用 OpenViking,再把结果附加到上下文。
这也让我理解了官方给 Codex 的安装脚本。它并不是只“注册一个 MCP”:它安装了 Codex 生命周期 Hook、stdio 到 HTTP 的 MCP 代理,以及告诉 Agent 如何使用经验记忆的 Skill。它把主动工具和被动生命周期集成组合在了一起。
因此,OpenViking 的记忆并不只有“被动注入”一种使用方式:
- Hook 可以在每轮输入前自动召回;
- Agent 也可以主动调用
search、recall、read; - 对话结束或 compact 前,Hook 可以捕获并提交 session;
- Agent 还可以主动调用
remember或forget。
向量数据库只是索引,不是内容本体
我后来问:OpenViking 是否内置了向量数据库?答案是有向量检索层,但不是把所有内容只存进向量数据库。
我现在使用下面这个模型理解它:
实际文件、摘要、概览、关系
↓ 保存在 AGFS/RAGFS
URI、embedding、元数据、摘要字段
↓ 保存在 VectorDB
查询 → 生成查询向量 → VectorDB 找 URI → 回到文件系统读取内容
embedding 模型负责“把内容变成向量”,VectorDB 负责“保存并搜索向量”,两者不是一个组件。默认可以使用本地向量后端,并不意味着必须额外购买或部署某个固定的云向量数据库。
自动召回让我开始怀疑上下文成本
确认 Codex 插件会在每次用户输入前搜索并注入记忆后,我马上想到一个问题:如果在同一个 session 中反复问相同问题,同一份记忆是不是会被一遍遍放进上下文?如果是这样,上下文会更快撑满,更频繁 compact;而 compact 时,大量无用记忆又可能挤掉真正重要的信息。
这个风险是真实的。OpenViking 只能缓解,不能让它消失。当前实现会使用相关性阈值、分类配额、Token 预算、摘要压缩和默认 5 轮的 URI 冷却去重。刚注入过的 URI 通常不会下一轮立刻再次注入。
但这些机制仍有边界:
- 去重按 URI,而不是严格按语义;同一事实存在多个文件时仍可能重复;
- 冷却期以后可以再次召回;
- 排除最相关结果后,可能补进相关性更差的结果;
- 即使每轮都不重复,长期 session 仍会积累许多不同的召回内容;
- compact 是 Agent 侧行为,OpenViking 无法保证压缩摘要保留哪些细节。
这让我形成了一个更保守的集成偏好:
自动召回适合少量、高置信度、经常需要的用户事实;历史事件、项目资料和 Skill 更适合让 Agent 按需主动检索。
如果上下文纯净度比“不遗忘”更重要,可以关闭自动召回,只保留 MCP 工具。自动与主动不是二选一,也可以采用小预算自动召回加主动下钻的混合方式。
记忆不是简单追加,而是按类型决定新增或更新
我用“今天下雨,第二天今天晴天”来测试记忆更新逻辑。最终理解是:OpenViking 没有一条适用于所有记忆的覆盖规则,行为由记忆类型的 schema 决定。
events 是按日期存储的 add_only 类型。“今天”会结合消息时间转换成具体日期,所以两天的天气是两条历史事件,不应互相覆盖。当然,这种信息也可能因为价值太低而根本不被提取。
而 profile、preferences、entities 等是可更新类型:
- “我从北京搬到上海”适合更新 profile 中的当前居住地;
- “我以前喜欢咖啡,现在不喝了”适合修改同一主题的 preference;
- 对同一个人或项目的新事实适合 patch 到已有 entity。
自动提取不是纯代码规则。它大致经历:
代码整理会话和时间信息
→ 向量搜索候选旧记忆
→ LLM 判断什么值得记、属于哪一类、是否冲突
→ LLM 输出新增、patch 或 delete 操作
→ 代码校验并执行操作、更新文件和向量索引
因此记忆维护具有概率性。LLM 可能没有找到旧记忆、主题命名不一致,或者没有正确识别冲突。OpenViking 能保证操作格式和存储过程相对可控,但不能把语义判断变成强一致事务。
在淘汰方面,我没有发现长期记忆默认按天数或容量自动删除的策略。旧记忆通常持续保留,除非提取器在冲突合并时删除、Agent 调用 forget,或者用户直接删除。热度可以影响排序,但排序降权不等于物理淘汰。
跨 Session 之后,身份模型成了新的难点
长期记忆写入 user 或 peer 的记忆目录,所以它天然可以跨 session。只有尚未 commit、尚未完成提取的消息仍局限在当前 session。
当我想到多个职责不同的 Agent 时,account、user、peer_id、actor_peer_id 和 agent_id 一度混在了一起。现在我用下面的方式区分:
Agent 外部运行的 Codex 或我自己开发的程序,不是当前主要存储身份
account 哪个团队或租户
user 数据归谁,是记忆、会话、技能的主要所有者
session 哪一次具体对话
peer_id 一条 session 消息关联哪个稳定交互对象/子空间
actor_peer_id 当前请求允许查看和操作哪个 peer 子空间
agent_id 旧兼容字段,现在映射到 actor_peer_id,不应再作为新设计使用
典型结构是:
account: acme
└── user: alice
├── memories/ # Alice 的公共用户记忆
├── sessions/
└── peers/
├── coding-agent/memories/ # 编程上下文
└── research-agent/memories/ # 研究上下文
peer_id 和 actor_peer_id 常使用相同的值,但职责不同:前者影响写到哪里,后者影响这次请求能看到哪里。
如果多个 Agent 服务同一个人,希望共享用户画像而隔离职责上下文,可以使用同一 user、不同 peer。不过 actor peer 的隔离仍会保留 user 根记忆,所以这不是完全隔离。
如果任何长期记忆都不能串用,更可靠的方式是每个 Agent 使用不同的 user key。同一 account 下仍然可以共享 viking://resources/。如果连公共资源和权限域也必须隔离,则使用不同 account。
纯 MCP 和纯 Skill 的身份边界
最后一个容易误解的地方是:Skill 并不承载身份。Skill 只是告诉模型何时以及如何使用 OpenViking。真正的身份来自它最终调用的 MCP、CLI 或 SDK。
纯 MCP 连接中:
- user 通常由
Authorization: Bearer <user-key>决定; actor_peer_id由X-OpenViking-Actor-Peer请求头决定;- trusted 模式才会显式发送 account/user 请求头;
- 模型不能靠普通工具参数任意切换 user。
这里还有一个当前实现上的重要限制:MCP remember 的消息只有 role 和 content,没有 peer_id。所以纯 MCP 可以用 actor peer 限制读取视图,但 remember 默认仍把提取结果写到 user 公共记忆,而不会自动写入对应 peer。
如果一定要产生 peer 级记忆,需要插件 Hook,或者通过 REST/SDK 向 session 添加带 peer_id 的消息后再 commit。正因为这个限制,如果只打算使用纯 MCP,又要求多个 Agent 的写入严格隔离,“一个 Agent 一个 user key”比 peer 方案可靠。
我现在对 OpenViking 的整体认识
经过这一轮追问,我不再把 OpenViking 简单看作“带向量搜索的文件系统”,也不把它想象成会自动替 Agent 管好一切的黑盒记忆系统。
我目前的理解是:
OpenViking 提供了一套统一的上下文存储、分层表达、语义索引、session 归档和 LLM 记忆提取基础设施。它能让 Agent 获得跨 session 的外部长期记忆,但何时召回、召回多少、如何隔离身份,以及是否允许模型主动管理记忆,仍然是 Agent 集成层必须做出的设计选择。
对我而言,评估一个实际接入方案时,应该先回答这些问题:
- 我需要的是资料搜索、长期记忆,还是两者都有?
- 哪些内容可以自动注入,哪些应该由 Agent 主动搜索?
- 每轮召回的预算和去重窗口应该多大?
- 多个 Agent 是共享同一个人的公共记忆,还是必须完全隔离?
- 纯 MCP 是否足够,还是需要 Hook 捕获和 session commit?
- 我是否接受 LLM 提取和冲突判断的概率性?
- 是否需要自己的审计、纠错、过期或淘汰策略?
这些问题比“OpenViking 有哪些命令”更接近我真正需要做的架构决策。
仍值得继续验证的问题
这次学习形成了总体模型,但还有一些适合通过实际运行验证的问题:
- 自动提取对我真实对话的准确率如何,哪些内容最容易误记?
- 在长 session 中,自动召回实际增加了多少 Token,并使 compact 提前了多少?
- 关闭自动召回、完全依赖 MCP 主动搜索时,Agent 是否经常忘记查记忆?
- 同一 user 下使用 peer 隔离时,哪些记忆仍会落入 user 公共目录?
- 不同职责 Agent 应该共享哪些 Resource、Skill 和用户事实?
- 是否需要在 OpenViking 之上增加人工确认、记忆 TTL 或定期整理流程?
这些问题无法只靠阅读文档回答,需要用我自己的 Agent、对话和数据做实验。也正因为如此,这篇记录保留的是我的问题链路,而不是试图替代官方手册。