跳到主要内容

文件系统范式: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.SCHEMEopenviking_cli/utils/uri.py:35)。
  • scope:顶层命名空间,决定"这块上下文属于谁、可见范围多大"。
  • path:scope 内部的路径。

3.1 scope:顶层命名空间

对外公开的 scope 只有三个,另有三个内部 scope 不能被外部 URI 直接寻址(依据:openviking_cli/utils/uri.py:37-4704-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-121peers 定义 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_uriopenviking/core/namespace.py:241),它把 URI 归一成"规范 URI + 属主"两样东西,装进 ResolvedNamespacenamespace.py:26-33)。

判断"这是不是省略了 user_id 的短形式"的逻辑在 _is_current_user_relative_urinamespace.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_urinamespace.py:390)负责这一步:短形式则拼上当前用户根再递归解析;显式形式则校验 user_id 合法后直接规范化。存储和检索一律用规范形式,短形式只是给人和 CLI 的书写便利(依据:04-viking-uri.md:117-119)。

3.4 标识符安全:路径段不能乱来

{user_id} / {peer_id} 这些身份段会变成真实路径的一部分,所以必须是"安全的单段"。校验规则集中在 identifiers.py

  • validate_user_ididentifiers.py:51)走通用的 validate_identifier_partidentifiers.py:11):非空、不能是 ...、必须匹配白名单正则 ^[a-zA-Z0-9_.@-]+$(常量 _VALIDATION_PATTERNidentifiers.py:8)、@ 最多一个。
  • peer_idnormalize_peer_idopenviking/core/peer_id.py:10),复用同一套规则,只是把错误信息包一层 Invalid peer_id:
  • 命名空间层面还有 _validate_peer_id_segmentsnamespace.py:329):碰到 .../peers/{peer_id} 的位置,强制那一段必须是合法非空 peer_id。

这套校验挡的就是 ..、斜杠注入这类路径穿越——文件系统范式要成立,地址就必须干净、可信。

3.5 路径变量:地址里带动态段

URI 还支持模板变量 {namespace:key},特别适合按时间组织数据(依据:04-viking-uri.md:137-194;实现 openviking/core/path_variables.py)。

内置 calendar 命名空间(CalendarVariableProviderpath_variables.py:42)提供一批日期变量:

变量含义示例(2026-05-07)
{calendar:today}完整日期路径2026/05/07
{calendar:ym}年/月2026/05
{calendar:yq}年/季度2026/Q2
{calendar:yw}年/ISO 周2026/w18

解析在服务端执行:CLI/SDK 原样传模板,服务器按当前上下文(时间、身份)渲染成具体路径(04-viking-uri.md:192-194)。核心是 PathVariableResolver.resolvepath_variables.py:135)用正则 VARIABLE_PATTERNpath_variables.py:19)扫出 {ns:key} 逐个替换,解不出来就报错——不静默留坑。便捷函数 resolve_path_variablespath_variables.py:206)是对外入口。

# 示意:模板 → 具体路径(等价于服务端渲染)
resolve_path_variables("viking://resources/emails/{calendar:today}/inbox")
# → "viking://resources/emails/2026/05/07/inbox"

4. URI 分类与深度语义

结论先行: 光有地址还不够,系统还要只看路径结构、不查库就判断出"这个 URI 指的是记忆还是技能、是命名空间根还是某个具体条目"。这套纯结构推断就是 classify_uri这正是"确定性"的技术底座——分类不依赖语义、不依赖存储内容,只数路径段。

4.1 先把 URI 拆成段:uri_parts / uri_depth

一切从把 URI 切成路径段开始(namespace.py:86 uri_parts):去掉 ? 查询串、归一化、剥掉 viking:// 前缀、按 / 切、丢掉空段。uri_depthnamespace.py:96)就是段数。

# 示意
uri_parts("viking://user/alice/memories/preferences")
# → ["user", "alice", "memories", "preferences"]
uri_depth(...) # → 4

4.2 分类:classify_uriUriClassification

classify_urinamespace.py:137)吃一个 URI,吐一个 UriClassificationnamespace.py:36-83),里面四个字段:

字段含义
parts路径段元组
scope顶层 scope(parts[0]
content_index"内容类型段"在 parts 里的下标(找不到为 None
context_type三类之一:resource / memory / skill

关键在 content_index:由 _content_segment_indexnamespace.py:116)算出——它在路径里找第一个"内容类型段"memories/resources/skills)出现的位置。找到后,context_type 就查表 _CONTENT_TYPES_BY_SCOPEnamespace.py:13)把段名映射成类型(memories→memoryresources→resourceskills→skill)。

它要处理好几种"内容段可能出现的深度",因为有短形式、有 peer 嵌套(依据:namespace.py:116-130):

路径形态(规范化后)content_indexcontext_type
user/{uid}/memories/…2memory
user/{uid}/skills/…2skill
user/memories/…(短形式)1memory
user/{uid}/peers/{pid}/memories/…4memory
user/peers/{pid}/memories/…(短形式)3memory
resources/{project}/…Noneresource(兜底默认)

注意 resources 顶层域没有 content_index(_content_segment_index 对非 user/agent 开头直接返回 None),context_typeclassify_uri 的兜底默认值 resourcenamespace.py:141-144)。这不矛盾:整个 resources/ 域本来就全是 Resource。

4.3 深度语义:同一类型,根还是叶?

UriClassification 上挂了一组只读属性,用段数和 content_index 的差来判断"处在这类内容的哪一层"(依据:namespace.py:62-83):

属性判定条件含义
is_memory_rootis_memorylen(parts) == content_index + 1URI 正好指到 memories 这个命名空间根本身
is_skill_namespaceis_skilllen(parts) == content_index + 1指到 skills 根本身
is_skill_rootis_skilllen(parts) == content_index + 2指到 skills/{某技能} 这一具体技能目录

用例子把"深度即语义"讲透(ctx.user_id == alice,规范形式):

# 示意,非源码
c = classify_uri("viking://user/alice/skills")
c.context_type # "skill"
c.content_index # 2
c.is_skill_namespace # True ← len(parts)=3 == 2+1,指到 skills 根

c = classify_uri("viking://user/alice/skills/search-web")
c.is_skill_namespace # False
c.is_skill_root # True ← len(parts)=4 == 2+2,指到某个具体技能

对外的便捷函数 context_type_for_urinamespace.py:153)就是 classify_uri(uri).context_type 的薄封装,预置目录初始化时就用它给每个目录打类型标签(directories.py:297)。

为什么这套"数段数"很重要? 因为它让"这是不是一个命名空间根/一个具体条目"的判断完全确定、零查询——不用碰数据库、不用跑向量匹配。写入路径要落到哪一层、检索要从哪个根往下钻,全靠这套结构推断打底。具体怎么用在后续章展开(写入见 02,检索见 03)。


5. 为什么用 ls/find/grep 取代模糊语义匹配

结论先行: 因为确定性。有了唯一 URI + 纯结构分类,Agent 就能像开发者操作真实文件系统一样,用一套标准命令精确、可追溯地操作上下文,而不是每次都把问题丢给"哪块向量最像"(依据:README:704;04-viking-uri.md:35)。

传统 RAG 与文件系统范式的差别,一张表说清:

维度传统 RAG(扁平向量)OpenViking(文件系统范式)
定位方式语义相似度"猜最像的块"URI 确定性寻址 + 结构分类
浏览做不到(只有一堆孤立块)ls / tree 逐层浏览目录
精确匹配grep 在指定子树里精确查
语义检索全库扁平匹配find 结合目录定位做递归检索
可观测黑盒,命中原因不透明路径可读、检索轨迹可视化
全局视野缺(块之间无结构)有(目录层级即上下文全景)

这几种操作各司其职(CLI 动词,依据:README:573-580;SDK 对应 client.ls/read/find04-viking-uri.md:311-325):

ls / tree → 浏览结构:这块上下文里有哪些目录、哪些条目
read → 确定读取:我已知路径,直接把这份读进来
grep → 精确匹配:在某个 URI 子树下按字面查
find → 语义检索:跨库/指定域按"意思"找,但落点仍是有结构的目录

一句话收束:URI 把"上下文管理"从"模糊的语义猜测"变成"直观、可追溯的文件操作"(README:704)。find 的语义检索并没有被丢掉——它被装进了文件系统骨架里:先靠目录结构定位、再语义细化,既保留语义能力又拿回确定性与可观测性。检索算法的具体机制是后续章的事,本章只需你记住这个心智转变。


6. 边界与本章不讲什么

  • 本章只讲心智模型:三类上下文、URI 命名空间、确定性操作的动机。
  • 不讲检索算法("先锁高分目录再逐层细化")→ 见 03-directory-recursive-retrieval.md
  • 不讲写入建树与 L0/L1/L2 分层(每块上下文怎么被切成三层)→ 见 02-write-path-tiered-tree.md
  • 不讲存储引擎(VikingFS 门面、RAGFS、向量库、底层 C++ ANN 索引)→ 见 04 / 06
  • 不讲会话记忆自迭代(Memory 到底怎么从会话里抽出来)→ 见 05-session-memory-self-iteration.md

一个诚实的提醒:agent 域的分类有个边角——viking://agent/skills(只两段)不会被 _content_segment_index 命中(该分支要求 len(parts) >= 3,即 agent/{peer}/skills 这种形态才算),此时 context_type 落到兜底默认 resource,但它另有 is_agent_namespace_root 属性(namespace.py:58-59)标识"agent 域根"。本章不深挖 agent 域的完整语义,只提醒读者:分类属性要结合具体形态看,别只认 context_type 一个字段。


7. 代码地图(导航索引)

主题文件路径符号名
URI 切段 / 深度openviking/core/namespace.py:86,96uri_parts / uri_depth
URI 结构分类openviking/core/namespace.py:137classify_uri
分类结果对象(含深度属性)openviking/core/namespace.py:36-83UriClassification
内容段下标推断openviking/core/namespace.py:116_content_segment_index
内容段→类型映射表openviking/core/namespace.py:13_CONTENT_TYPES_BY_SCOPE
URI 归一化 + 属主解析openviking/core/namespace.py:241resolve_uri / ResolvedNamespace
当前用户短形式判定openviking/core/namespace.py:434_is_current_user_relative_uri
当前用户根openviking/core/namespace.py:157canonical_user_root
context_type 便捷入口openviking/core/namespace.py:153context_type_for_uri
身份段合法性校验openviking/core/identifiers.py:11,51validate_identifier_part / validate_user_id
peer_id 归一化openviking/core/peer_id.py:10normalize_peer_id
API 边界 URI 校验(scope 白名单)openviking/core/uri_validation.py:54validate_viking_uri
内容目标 URI 校验openviking/core/uri_validation.py:119validate_content_target_uri
路径变量解析openviking/core/path_variables.py:135,206PathVariableResolver.resolve / resolve_path_variables
日历变量提供者openviking/core/path_variables.py:42CalendarVariableProvider
预置目录树 / 懒创建openviking/core/directories.py:39,186PRESET_DIRECTORIES / initialize_user_directories
scope 常量集合openviking_cli/utils/uri.py:35-47VikingURI.SCHEME / PUBLIC_SCOPES / INTERNAL_SCOPES
三类上下文概念docs/en/concepts/02-context-types.md
viking URI 规范docs/en/concepts/04-viking-uri.md