文件系统范式:viking:// URI 与三类上下文
30 秒导读: OpenViking 是给 AI Agent 用的"上下文数据库"。它不像传统 RAG 那样把记忆、资料、技能拆成一堆扁平的向量块,而是把它们统统当成一个虚拟文件系统里的文件和目录——每块上下文都有一个像文件路径一样的地址
viking://scope/path。Agent 于是能像程序员敲ls/find/grep一样,确定性地定位、浏览、检索上下文,而不是每次都靠"语义猜"。
本章是全篇最浅的地基,只讲一件事:OpenViking 的核心心智模型。看完你应该能回答三个问题——上下文分哪三类、它们的地址长什么样、为什么要用文件系统那套确定性操作。检索算法、建树实现、存储引擎留给后续章(02 / 03 / 04)。
1. 这是什么(零基础也能懂)
一句话定义: OpenViking 把 Agent 需要的所有上下文——用户记忆、外部资料、可调用技能——组织成一棵带唯一地址的虚拟文件系统树,地址就是 viking:// 开头的 URI(依据:README「The OpenViking Solution」节)。
它要替谁解决什么问题。 传统做法(RAG,检索增强生成)把上下文切成小段文本、算成向量、丢进 向量库,检索时拿"意思相近"去匹配。这带来两个老毛病:
- 碎片化:记忆写在代码里、资料在向量库、技能散落各处,没法统一管(README:40)。
- 模糊、不可观测:检索是"语义猜哪块最像",命中哪些块、为什么命中,像黑盒,出错难 debug(README:42-43)。
OpenViking 的解法,一句话:换范式。 不再把上下文看成"一堆扁平文本切片",而是映射成 viking:// 协议下的虚拟目录,每块都有唯一 URI(README:702)。于是"上下文管理"从"模糊语义匹配"变成"直观、可追溯的文件操作"(README:704)。
用起来什么样。 直接看一段命令行(依据:README:573-580)——注意它和你平时敲 shell 的手感几乎一样:
ov ls viking://resources/ # 列目录,看有哪些资料
ov tree viking://resources/volcengine -L 2 # 看两层目录树
ov find "what is openviking" # 语义检索(跨全库)
ov grep "openviking" --uri viking://resources/.../docs/zh # 在指定目录下精确 grep
一句话直觉/类比。 把 Agent 的"大脑"当本地磁盘来管:上下文窗口是内存、这套 viking:// 文件系统是磁盘;要用什么先 ls 看、find 找、read 读进来,而不是把整块硬塞进 prompt。
2. 三类上下文:Resource / Memory / Skill
结论先行: OpenViking 把所有上下文归成三 种基本类型,因为它们的"谁来写、多久变一次、拿来干嘛"根本不同(依据:docs/en/concepts/02-context-types.md:3)。
| 类型 | 定位(拿来干嘛) | 生命周期 | 谁来写 |
|---|---|---|---|
| Resource 资源 | 外部知识与规则(文档、代码库、手册、论文) | 长期、相对静态 | 用户主动加 |
| Memory 记忆 | Agent 对用户和世界的认知 | 长期、动态更新 | Agent 自动抽取记录 |
| Skill 技能 | 可声明、可调用的 Agent 能力配置 | 长期、静态(调用经验另记进 Memory) | 用户或系统加 |
三者的差别,用三句话点透:
- Resource = 死知识。 用户塞进来补 LLM 不知道的东西,加完基本不动(
02-context-types.md:17-22)。 - Memory = 活认知。 Agent 从每次会话里自己抽取,越用越多、可更新——这是 OpenViking 相比传统"只记录对话"的记忆系统的关键增量。用户记忆细分成 8 个类目(profile / preferences / entities / events / trajectories / experiences / tools / skills),各自有不同的更新策略(可合并 / 可追加 / 不更新)(
02-context-types.md:57-66)。 - Skill = 手脚。 定义"Agent 怎么和外部系统交互"的能力(工作流、通信端点、工具、支付配置);定义本身运行时不变,但"这技能好不好用"的经验会写回 Memory(
02-context-types.md:84-91)。
三类上下文各自住在文件系统的哪一片。 这就自然过渡到 URI——每类都有默认的落脚目录:
viking://
├── resources/{project}/ ← Resource:账号内全局共享的客观知识
├── user/{user_id}/
│ ├── memories/ ← Memory:当前用户的长期记忆(8 类)
│ ├── resources/ ← 当前用户私有的 Resource
│ └── skills/ ← Skill:当前用户的技能(默认落点)
└── agent/skills/ ← Skill:账号内全局共享的技能
一个约束值得记住:
viking://resources/只放客观知识(文档、代码、规范、论文),工具/端点/支付/技能定义这类"能力配置"禁止塞进去,要走viking://agent/域(依据:04-viking-uri.md:366-371)。这条约束正是"三类分明"在目录层面的体现。
3. viking:// URI 命名空间
结论先行: URI 是这套文件系统的地址格式,长这样——
viking://{scope}/{path}
- scheme:永远是
viking(常量VikingURI.SCHEME,openviking_cli/utils/uri.py:35)。 - scope:顶层命名空间,决定"这块上下文属于谁、可见范围多大"。
- path:scope 内部的路径。
3.1 scope:顶层命名空间
对外公开的 scope 只有三个,另有三个内部 scope 不能被外部 URI 直接寻址(依据:openviking_cli/utils/uri.py:37-47;04-viking-uri.md:17-31):
| scope | 可见范围 | 生命周期 | 对外可寻址 |
|---|---|---|---|
resources | 账号内全局 | 长期 | ✅ |
user | 当前用户(含 session) | 长期 / 会话内 | ✅ |
agent | 账号内全局(能力配置) | 长期 | ✅ |
session | 当前用户 session 的兼容别名 | 会话内 | ✅(旧格式) |
temp / queue / upload | 内部实现 | 临时 | ❌ |
代码里这套分级是硬编码的常量集合(openviking_cli/utils/uri.py:37-47):PUBLIC_SCOPES(可对外)、LEGACY_SCOPES = {session}(向后兼容)、INTERNAL_SCOPES = {temp, queue, upload}(内部专用)。API 边界的校验器 validate_viking_uri 默认只放行公开 scope,temp/queue/upload 直接拒(openviking/core/uri_validation.py:54-95)。
3.2 根命名空间树
把三个公开 scope 展开,就是 OpenViking 初始化时铺好的目录骨架。任务里点名的 resources/user/{user_id}/peers 结构在这张图里一目了然(依据:04-viking-uri.md:37-65;预置树定义见 openviking/core/directories.py:39 PRESET_DIRECTORIES):
viking://
├── resources/ # 全局客观知识
│ └── {project}/ …
│
├── user/{user_id}/ # 当前用户的私有空间
│ ├── memories/ # 8 类长期记忆(preferences/entities/events/…)
│ ├── resources/ # 用户私有资料
│ ├── skills/ # 用户技能
│ ├── privacy/ # 用户级敏感配置快照
│ ├── sessions/{session_id}/ # 会话状态、消息、工具输出、历史
│ └── peers/{peer_id}/ # ← 关于某个"稳定交互对象"的记忆/资料
│ ├── memories/
│ └── resources/
│
└── agent/ # 账号内全局共享的能力配置
├── skills/ # 技能定义(当前)
├── endpoints/ tools/ payments/ # 端点/工具/支付(规划中)
peers 这一支值得单独点一句。 它是"当前用户对稳定交互对象(访客、队友、外部联系人)的长期记忆"。目录不是预建的,而是从会话里的 peer_id 懒创建——第一次遇到某个 peer 才落地它的目录(依据:directories.py:117-121 的 peers 定义 overview;懒创建语义见 initialize_user_directories docstring directories.py:186-192)。
预置 vs 懒创建(一个重要设计): 初始化只铺"用户根 + 一级入口目录"(
memories/resources/skills/peers/sessions…),像memories/preferences/这种更深的叶子命名空间等到真有内容写入时才创建(directories.py:186-219)。好处:新用户根一开就"可被发现、可ls",又不用把整棵空树物化出来。
3.3 短形式 → 规范形式:resolve_uri
问题: 用户敲 viking://user/memories/ 时,系统怎么知道是"哪个用户"的记忆?
答案: user 域支持当前用户短形式——省略 {user_id},由请求身份补全。解析入口是 resolve_uri(openviking/core/namespace.py:241),它把 URI 归一成"规范 URI + 属主"两样东西,装进 ResolvedNamespace(namespace.py:26-33)。
判断"这是不是省略了 user_id 的短形式"的逻辑在 _is_current_user_relative_uri(namespace.py:434-439):viking://user/ 后面第一段如果是 memories/resources/skills/peers/privacy/sessions 这类约定的相对根段,就认定省略了 user_id,用 canonical_user_root(ctx)(namespace.py:157)把当前用户 id 填进去。
一段示意,把"短→规范"演出来:
# 示意,非源码。重点看:同一份内容,两种写法解析到同一个规范 URI
# 当前请求身份 ctx.user.user_id == "alice"
resolve_uri("viking://user/memories/preferences", ctx).uri
# → "viking://user/alice/memories/preferences" # 短形式被展开
resolve_uri("viking://user/alice/memories/preferences", ctx).uri
# → "viking://user/alice/memories/preferences" # 显式写法,原样规范化
_resolve_user_uri(namespace.py:390)负责这一步:短形式则拼上当前用户根再递归解析;显式形式则校验 user_id 合法后直接规范化。存储和检索一律用规范形式,短形式只是给人和 CLI 的书写便利(依据:04-viking-uri.md:117-119)。