OpenViking 0.3.x 到 0.4.0 升级与 User/Peer 数据模型迁移实战指南
本指南面向已经运行 OpenViking 0.3.x 的开发者与运维人员,系统讲解升级到 0.4.0 前后的完整动作:哪些旧用法保持兼容、何时必须执行数据迁移、迁移的底层规则与向量索引处理方式,以及业务代码如何从 viking://agent/...、viking://session/... 逐步迁移到新的 viking://user/<user_id>/peers/<peer_id>/... 模型。读完本文,你将掌握一条可安全执行的升级迁移路径,并理解 0.4.0 中 User / Peer / Session / Skill 新数据模型背后的源码级实现依据。
是否需要升级
如果继续停留在 0.3.x,现有 agent_id、viking://agent/...、viking://session/... 行为不会变化,但也无法获得 0.4.0 的能力:
- 没有 User / Peer 数据模型。
- 没有 legacy agent/session 数据迁移和 cleanup 命令。
- 没有
actor_peer_id请求级 peer 视图。 - 后续围绕新模型的修复和能力不会回补到旧模型。
如果升级到 0.4.0,可以先不迁移数据。0.4.0 提供运行期兼容,旧数据不会因为升级立即不可读:
agent_id仍可临时配置。当前 HTTP SDK client 会把它映射成请求级actor_peer_id。viking://agent/...仍可读旧 agent 数据,但只读。viking://session/...仍可读旧 session 数据,并会合并新 session 视图。
推荐顺序:
备份
-> 升级 server / CLI / SDK
-> 验证旧数据仍可读
-> 执行数据迁移
-> 验证新路径
-> 逐步迁移业务用法
-> 可选 cleanup
升级前备份
先用 0.3.x 兼容版本创建备份。建议使用 0.3.24:
pip install openviking==0.3.24 --upgrade --force-reinstall
ov backup ./backups/openviking-before-0.4.0.ovpack
确认当前版本:
python -c "import openviking; print(openviking.__version__)"
ov version
重要约束:不要在 0.3.x 上执行 ov --sudo admin migrate。迁移命令只在 0.4.0 或更新版本可用。从源码看,迁移入口 POST /api/v1/admin/migrate(见 admin.py)依赖 0.4.0 的 task tracker 与 LegacyDataMigration 服务,旧版本二进制中并不存在该能力。
升级服务和客户端
安装 0.4.0,重启服务端,并确保 CLI / SDK 也升级到同一版本线:
pip install openviking==0.4.0 --upgrade --force-reinstall
openviking-server --config ov.conf
如果使用仓库内 Rust ov CLI,需要重新构建或安装 CLI;否则本地 ov 可能仍是旧二进制。仓库内 Rust CLI 的迁移命令实现在 crates/ov_cli/src/commands/admin.rs,它通过 client.rs 中的 admin_migrate 以 migrate 或 cleanup 两种 action 调用服务端接口。
升级后先验证配置和旧数据读取:
ov config validate
ov ls viking://agent
ov ls viking://session
ov session list
关键点:viking://session 的兼容合并发生在服务端。只升级 CLI、不重启 server,不会改变服务端读取行为。0.4.0 的请求边界解析逻辑位于 openviking/core/namespace.py:viking://session/... 会在认证请求边界被解析为当前用户的 viking://user/<user_id>/sessions/...,而 ~ 是当前用户空间的 home alias,因此“只升级客户端”对服务端读取行为没有任何影响。
兼容性速查
| 旧用法 | 0.4.0 行为 |
|---|---|
client 配置 agent_id |
支持。当前 HTTP SDK client 会映射成请求级 actor_peer_id;它本身不再触发 legacy agent 模式。 |
ov ls viking://agent |
支持读;如果设置了 agent_id / actor_peer_id,只显示当前 actor peer 对应的 legacy agent。 |
读 viking://agent/<agent_id>/... |
支持读旧数据。 |
写 viking://agent/... |
不支持。新写入应进入 viking://user/<user_id>/peers/<peer_id>/...。 |
ov ls viking://session |
支持读,会合并新 session 和旧 session。 |
读 viking://session/<session_id>/... |
支持读,按新路径优先、旧路径兜底。 |
写 viking://session/... |
不支持。新 session 写入 viking://user/<user_id>/sessions/...。 |
HTTP SDK find / search 传 agent_id |
支持,只查选中的 actor peer 视图,不会自动查未迁移的旧 agent 数据。 |
find / search body 传旧 peer_id |
不支持。新 peer 视图使用 actor_peer_id 或 X-OpenViking-Actor-Peer。 |
同时配置 actor_peer_id 和 agent_id |
不支持,会报错。 |
HTTP SDK agent_id client 下显式传 message peer_id |
支持。该 message 使用显式 peer_id;未提供时不会从 agent_id 推导。 |
role_id 记忆隔离 |
不再支持,升级后忽略。 |
actor_peer_id 视图的源码依据
“只读当前 actor peer”这一行为不是文档约定,而是有真实实现支撑的:
- 在 openviking/core/namespace.py 中,
is_hidden_by_actor_peer_view与may_include_hidden_actor_peers会依据ctx.actor_peer_id过滤当前 user root 下peers集合中属于其他 peer 的内容——它只过滤peers集合,不会隐藏非 peer 的用户内容,也不会改变租户/用户身份。 - 在 openviking/core/namespace.py 的
is_accessible中,访问agentscope 时若配置了actor_peer_id,只有 agent id 与actor_peer_id一致的目录才可访问,这正是“ov ls viking://agent只显示当前 actor peer 对应 legacy agent”的实现原因。 - 请求上下文中的
actor_peer_id会随RequestContext传递(见 namespace.py 中构造RequestContext时保留actor_peer_id字段),说明 peer 视图是请求级的,而非全局状态。
执行数据迁移
确认升级后旧数据可读,再执行迁移:
ov --sudo admin migrate --output json
响应会返回 task id:
{
"task_id": "..."
}
查询任务:
ov --sudo task status <task_id>
ov --sudo task list --task-type legacy_migration
HTTP API:
POST /api/v1/admin/migrate
X-API-Key: <root-key>
请求体可以为空,等价于:
{
"action": "migrate"
}
查询任务:
GET /api/v1/tasks/{task_id}
X-API-Key: <root-key>
ROOT 查询迁移任务时不会按普通 account/user 过滤。迁移会为整个存储创建一个 root 级别 task,不会按 account 分别创建 task。从 admin.py 的实现可以看到:migrate_legacy_data 端点先构造 LegacyDataMigration,若 action 为 migrate 则先执行 preflight(),preflight 存在 errors 时直接抛出 FailedPreconditionError(task 根本不会被创建);通过后以 SYSTEM_TASK_ACCOUNT_ID / SYSTEM_TASK_USER_ID 创建 task 并用 asyncio.create_task 异步执行,因此接口立即返回 task_id,实际迁移在后台完成。对应的测试用例 tests/server/test_admin_api.py 专门验证了“preflight 失败不创建 task”的行为。
迁移规则
0.4.0 的新模型是 User / Peer:
User = 自然人或业务使用者
Peer = User 下的交互对象
Session = User 下的会话状态
Skill = User 下的可执行技能
迁移目标:
| 旧数据 | 新位置 |
|---|---|
viking://agent/<agent_id>/memories/... |
viking://user/<user_id>/peers/<agent_id>/memories/... |
viking://agent/<agent_id>/resources/... |
viking://user/<user_id>/peers/<agent_id>/resources/... |
viking://agent/<agent_id>/skills/<skill>/... |
viking://user/<user_id>/skills/<skill>/... |
viking://session/<session_id>/... |
viking://user/<user_id>/sessions/<session_id>/... |
共享 legacy agent 数据会复制到每个目标 user 的 peer 目录。如果旧路径已经表达了 user owner,只迁移到该 user。
迁移规划器的源码视角
迁移的核心实现在 openviking/service/legacy_migration.py:
- 三类 legacy 布局都会在 preflight 中识别:
/local/<account>/user/<user_id>/agent/<agent_id>(user 作用域,_plan_user_agent_data)、/local/<account>/agent/<agent_id>/user/<user_id>(agent 作用域,_plan_agent_user_data)、以及共享的/local/<account>/agent/<agent_id>(_plan_shared_agent_data)。 - 保留目录保护:
viking://agent/下的skills、endpoints、tools、payments属于新公共 scope 布局,定义在_AGENT_RESERVED_SUBDIRS(legacy_migration.py),迁移和 cleanup 规划器都会跳过它们,不会当作 legacyagent_id处理。 - 操作粒度:迁移按
TreeCopy描述每条复制操作,带category(如agent_memories、agent_skills、sessions),最终汇总到MigrationResult.operations字典,这就是任务结果中migrated.operations各项计数的来源。 - 幂等性:
_copy_path在目标文件已存在时记录skipped(reason 为target already exists; kept existing target)而不是覆盖;skill 复制同样以skip_tree_if_target_exists=True跳过已有同名 skill。
向量索引:不重新向量化,只重写 URI
迁移会一并处理已有向量索引:对实际复制成功的 memory / resource / skill 文件或目录,直接读取旧记录中的 vector / sparse_vector 和标量字段,重写 URI 后写入新记录。迁移不会重新向量化,也不会自动调用 reindex。共享 legacy agent 数据复制到多个 user 时,会按每个目标 user URI 写入多份向量记录。
实现位于 openviking/storage/vector_migration.py:
copy_vector_records(L200-L262)通过_records_in_scope按 URI scope 过滤旧向量记录,调用rewrite_vector_record保留vector/sparse_vectorpayload,仅重写uri、id、account_id、owner_user_id、owner_space、context_type等字段后upsert到新 URI。- 对
level为 0/1 的摘要型记录(abstract / content),会进一步用rewrite_viking_uri_references和rewrite_abstract_overview_for_transfer重写正文中嵌套的viking://引用,避免迁移后正文链接指向旧路径。 - 每个 scope 的向量记录上限为
_MAX_VECTOR_RECORDS_PER_SCOPE = 100_000(vector_migration.py),超过时会产生 warning 提示需手动 reindex。 - 没有向量 payload 的旧标量记录会跳过并计入
migrated.skipped_vector_records(_has_vector_payload只检查vector/sparse_vector字段)。Session 迁移只复制文件状态,不处理向量索引——这一点在_copy_vectors中显式判断if operation.category == "sessions" ... return(legacy_migration.py)。
Session owner 解析顺序
Session owner 按以下顺序解析:
.meta.json.created_by_user_id.meta.json.user_id、.meta.json.owner_user_id或.meta.json.created_by- 旧路径里的 user hint,例如
/session/alice/sess-001 - 单用户 account 下的唯一注册用户
多用户 account 下,如果某个 legacy session 无法识别 owner,preflight 会失败。升级后的运行期兼容可以临时读取旧 session,但正式迁移前仍应补齐 owner。源码中 _session_owner(legacy_migration.py)正是按 created_by_user_id → user_id / owner_user_id / created_by 的顺序读取 .meta.json;_looks_like_session_dir 则通过 .meta.json、messages.jsonl、history、tool-results、tools 等文件判断目录是否为 session。
Legacy agent instructions 不迁移:
viking://agent/<agent_id>/instructions
迁移会记录 warning,不创建替代目录(见 _plan_agent_tree 中对 instructions 路径的处理)。
迁移前检查
以下问题会在 task 创建前直接失败:
- 物理存储中存在 legacy 数据,但对应 account 不在 API key user registry 中。
- 多用户 account 下存在无法识别 owner 的 legacy session。
- session owner 存在,但不是合法的 OpenViking user id。
以下问题会记录为 warning 或 skipped,并继续迁移:
- 目标 user 已经存在同名 skill。旧 skill 会被跳过,不覆盖现有 skill。
- 发现 legacy agent instructions。Instructions 不迁移。
- 存在共享 legacy agent,但 account 下没有可迁移的目标 user。
如果迁移发现 legacy 数据 owner 不在 user registry 中,会自动注册该 user。迁移结果只记录自动创建了哪些用户,不返回明文 user key。源码中 _ensure_plan_user(legacy_migration.py)会先校验 user_id 合法性(非法 id 进入 plan.errors),再检查用户是否已存在,不存在则加入 plan.created_users;run() 阶段对每个自动创建的用户执行 register_user 并调用 initialize_user_directories 初始化用户目录。
如果开启了 api_key_hashing,明文 key 无法从存储中反查。需要重新生成:
ov --sudo admin regenerate-key <account_id> <user_id>
验证迁移结果
查看任务结果:
ov --sudo task status <task_id>
重点看:
migrated.files/migrated.directoriesmigrated.vector_records/migrated.skipped_vector_recordsmigrated.operationsskippedwarningscreated_users
这些字段的结构与 MigrationResult.to_dict 一一对应:migrated 下包含 files、directories、vector_records、skipped_vector_records 和按类别排序的 operations 字典,顶层还有 skipped、warnings、created_users。
验证新路径:
ov ls viking://user/<user_id>/peers/<agent_id>/memories
ov ls viking://user/<user_id>/skills
ov ls viking://user/<user_id>/sessions
迁移只复制数据,不删除 legacy 路径或旧向量记录。重复执行是幂等的:已存在的目标文件和 skill 会被跳过,不会覆盖。如果迁移后的检索结果不符合预期,再由用户对新路径手动执行 reindex;迁移流程本身不会触发 reindex。仓库测试 tests/server/test_admin_api.py 覆盖了共享 agent 数据扇出、skill 迁移、session 迁移以及 “Skipped legacy instructions” warning 等场景,可作为验证行为是否符合预期的参照。
业务用法迁移
Client 配置
旧配置可以先继续用:
{
"agent_id": "legacy-agent"
}
推荐逐步改成:
{
"actor_peer_id": "legacy-agent"
}
不要同时配置:
{
"actor_peer_id": "customer-a",
"agent_id": "legacy-agent"
}
这会报错。actor_peer_id 通过 openviking/core/peer_id.py 的 normalize_peer_id 校验与归一化,任何空值或非法格式都会被拒绝,因此旧 agent_id 与新的 actor_peer_id 二选一是硬约束而非建议。
文件路径
旧路径:
viking://agent/code-agent/memories/profile.md
viking://session/sess-001/messages.jsonl
新路径:
viking://user/alice/peers/code-agent/memories/profile.md
viking://user/alice/sessions/sess-001/messages.jsonl
viking://session/<session_id> 可以继续作为当前 user session 的读 alias 使用,但新写入和长期引用建议使用 viking://user/<user_id>/sessions/<session_id>。从 openviking/core/namespace.py 可以看到,canonical_session_uri 生成的规范路径始终位于 viking://user/<user_id>/sessions/ 之下;同时 resolve_uri 对顶层 session scope 会抛出 NamespaceShapeError(“Legacy session URI is not canonical”),说明旧 session URI 只在请求边界(resolve_request_uri / resolve_current_user_uri)作为 alias 解析,内部存储与写入一律使用规范化新路径。
find / search
find / search 不再接受 legacy agent 身份字段,也不会自动包含未迁移的旧 agent 数据。旧 viking://agent/... 路径仍可通过内容和文件系统接口只读访问,但应先完成迁移再使用新检索路径。
迁移完成并确认不再需要旧 agent 数据后,所有 client 都改为 client/request 级 actor_peer_id。
会话消息
Session 不再从 legacy agent id 推导消息归属;需要表达说话人时必须显式使用 message peer_id。
暂不迁移数据
升级后可以暂时不迁移,但要知道这些限制:
- 旧 agent/session 数据可读,但旧 namespace 不可写。
- 新 session 和新资源会写入新 namespace,数据会在新旧路径并存一段时间。
find/search不会默认查旧viking://agent数据。- 多用户 account 下 owner 不明确的旧 session,运行期可能可读,但正式迁移会被 preflight 拦截。
- cleanup 之前旧目录和旧向量记录仍会保留。
因此不迁移适合作为短期过渡,不建议作为长期状态。
可选 cleanup
确认迁移结果无误后,可以删除旧 namespace:
ov --sudo admin migrate --cleanup --output json
ov --sudo task status <cleanup_task_id>
HTTP 请求体:
{
"action": "cleanup"
}
Cleanup 只删除:
/local/<account>/agent
/local/<account>/session
/local/<account>/user/<user>/agent
Cleanup 会先删除上述 legacy URI scope 下的旧向量记录,再删除对应 AGFS 目录。若向量读取或删除失败,该目录会被跳过,避免旧文件已删除但旧索引仍残留。这一顺序在 legacy_migration.py 的 cleanup() 中体现:先 _delete_vectors(target) 并累加 result.vector_records,若 vector_result.failed 则将目标记入 skipped(reason 为 vector cleanup failed)并跳过目录删除;删除向量成功后才执行 _agfs.rm(target.source_path, recursive=True)。
Cleanup 不会删除新 user / peer 路径下的文件或向量记录。不会删除的新模型目录:
/local/<account>/user/<user>/peers
/local/<account>/user/<user>/sessions
/local/<account>/user/<user>/skills
另外注意:cleanup 规划器同样受 _AGENT_RESERVED_SUBDIRS 保护,/local/<account>/agent 下的 skills、endpoints、tools、payments 不会被当作 legacy agent 删除(_cleanup_preflight)。
Cleanup 后,viking://agent/... 不再用于读取迁移后的 peer 数据;请使用新路径。viking://session/... 仍可作为当前 user session 的 alias 读取新 session。
常见问题
ov ls viking://agent 只看到一个 agent
如果配置了 agent_id 或 actor_peer_id,这是预期行为。viking://agent 根目录会过滤到当前 actor peer,只显示对应 legacy agent。实现依据见上文对 is_accessible 与 is_hidden_by_actor_peer_view 的分析(openviking/core/namespace.py)。
ov ls viking://session 仍为空
确认服务端已经重启并加载 0.4.0。viking://session 的合并读发生在服务端;只升级 CLI 不会改变服务端读取行为。旧 session 的兼容读位于请求边界解析层(namespace.py),只有运行 0.4.0 的服务端才会执行该解析。
配置同时有 actor_peer_id 和 agent_id
这是不允许的。保留 agent_id 进入 legacy 模式,或删除 agent_id 后改用 actor_peer_id。
Preflight 报告 unknown account
物理存储中存在某个 account 的 legacy 数据,但 API key registry 中没有这个 account。先恢复或重新创建该 account,再重新执行迁移。对应的 preflight 检查位于 legacy_migration.py:遍历物理存储的 account 与 registry 的 account 差集,若该 account 存在 legacy 数据则记录 error。
Preflight 报告 unresolved session owner
给 legacy session 的 .meta.json 补充 owner 字段,或把 session 移到能明确识别 owner 的旧路径下,然后重新执行迁移。可补充的字段包括 created_by_user_id、user_id、owner_user_id 或 created_by,并按上述解析顺序读取。
某个 skill 没有迁移
查看 task 的 skipped 列表。最常见原因是目标 user 已经存在同名 skill。迁移不会覆盖现有 skill——目标已存在时,规划器直接将该操作标记为 skipped 而不生成 TreeCopy(_plan_agent_skills)。
小结
0.3.x 到 0.4.0 的升级本质上是数据所有权模型的升级:从全局 agent / session namespace 收敛为 User → Peer / Session / Skill 的归属结构。本文给出的完整路径——备份、升级、兼容验证、preflight 检查、后台迁移任务、结果核验、业务代码切换、可选 cleanup——每一步都有对应的源码实现(legacy_migration.py、vector_migration.py、namespace.py、admin.py)与测试用例(tests/server/test_admin_api.py)作为依据。迁移不重新向量化、幂等可重跑、cleanup 先删索引再删目录等设计,保证了整个升级过程在旧数据仍可读的前提下平滑推进。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00