docs/design/account-namespace-shared-session-design.md
当前 OpenViking 已具备 account_id / user_id / agent_id 三元身份模型,但 user 与 agent 的关系语义仍然不够明确,session、目录和检索对这层关系的表达也不一致,主要问题有:
user scope 是否再按 agent 切分、agent scope 是否再按 user 切分user / agent 关系session 仍然偏向单 user 视角,不能准确表达一个 account 内多 user / 多 agent 协同场景中的真实参与者身份本次方案的目标,是把 user / agent 关系显式提升为 account 级命名空间策略,并为后续混合参与者 session 的提取、审计和协同能力打基础。
isolate_user_scope_by_agent: boolisolate_agent_scope_by_user: boolisolate_user_scope_by_agent = falseisolate_agent_scope_by_user = falseuser 与 agent 目录都采用显式嵌套 canonical URI,不再依赖 hash space。session 升级为 account 级共享作用域,统一落在:
viking://session/{session_id}session add-message 新增 role_id:
role=user 时绑定真实 user_idrole=assistant 时绑定真实 agent_idaccount_iduriowner_user_idowner_agent_idcommit / extract 分流user 和 agent 的共享边界user / agent / session 的逻辑 URI 与底层目录拓扑session 升级为 account 级共享会话session add-message 中显式记录消息归属身份commit / extract 分流算法推荐字段名:
isolate_user_scope_by_agent: boolisolate_agent_scope_by_user: bool语义如下:
isolate_user_scope_by_agent = false
user 作用域只按 user_id 分区isolate_user_scope_by_agent = true
user 作用域按 (user_id, agent_id) 分区isolate_agent_scope_by_user = false
agent 作用域只按 agent_id 分区isolate_agent_scope_by_user = true
agent 作用域按 (agent_id, user_id) 分区默认值:
isolate_user_scope_by_agent = falseisolate_agent_scope_by_user = false这个默认值的含义是:
user 记忆按 user 隔离,不区分 agentagent 记忆按 agent 隔离,不区分 user采用这个默认值的原因是:
user 绑定,应该能跨 agent 复用这两个字段在 account 创建时配置,并跟随 account 生命周期持久化。
持久化位置:
/local/{account_id}/_system/setting.json示例:
{
"namespace": {
"isolate_user_scope_by_agent": false,
"isolate_agent_scope_by_user": false
}
}
配置入口:
POST /api/v1/admin/accounts请求示例:
{
"account_id": "acme",
"admin_user_id": "alice",
"isolate_user_scope_by_agent": false,
"isolate_agent_scope_by_user": false
}
如果请求里省略这两个字段,则服务端按默认值补齐:
{
"isolate_user_scope_by_agent": false,
"isolate_agent_scope_by_user": false
}
读取入口:
GET /api/v1/admin/accountsGET /api/v1/admin/accounts/{account_id}(如果后续补充单 account 查询接口)持久化要求:
/{account_id}/_system/setting.json 的 namespace 节点AccountNamespacePolicy修改策略:
setting.json 的方式在线切换 policy原因:
如果后续业务确实需要调整:
这两个字段只在 account 上定义,不在 user、agent、session 或请求级覆盖。
原因:
user、agent、session 都有 canonical URIisolate_user_scope_by_agent=false,isolate_agent_scope_by_user=falseviking://user/{user_id}/...
viking://agent/{agent_id}/...
viking://session/{session_id}/...
底层目录:
/local/{account_id}/user/{user_id}/...
/local/{account_id}/agent/{agent_id}/...
/local/{account_id}/session/{session_id}/...
访问规则:
(user_id=ua, agent_id=aa) 时,可以访问 viking://user/ua/... 下的全部 user 数据,不受当前 agent 影响(user_id=ua, agent_id=aa) 时,可以访问 viking://agent/aa/... 下的全部 agent 数据,不受当前 user 影响viking://user/{other_user_id}/...viking://agent/{other_agent_id}/...session 按 account 共享,访问规则独立于这两个字段适用场景:
isolate_user_scope_by_agent=false,isolate_agent_scope_by_user=trueviking://user/{user_id}/...
viking://agent/{agent_id}/user/{user_id}/...
viking://session/{session_id}/...
底层目录:
/local/{account_id}/user/{user_id}/...
/local/{account_id}/agent/{agent_id}/user/{user_id}/...
/local/{account_id}/session/{session_id}/...
访问规则:
(user_id=ua, agent_id=aa) 时,可以访问 viking://user/ua/... 下的全部 user 数据,不受当前 agent 影响(user_id=ua, agent_id=aa) 时,只能访问 viking://agent/aa/user/ua/...viking://agent/aa/user/{other_user_id}/...viking://agent/{other_agent_id}/user/ua/...session 按 account 共享,访问规则独立于这两个字段适用场景:
isolate_user_scope_by_agent=true,isolate_agent_scope_by_user=falseviking://user/{user_id}/agent/{agent_id}/...
viking://agent/{agent_id}/...
viking://session/{session_id}/...
底层目录:
/local/{account_id}/user/{user_id}/agent/{agent_id}/...
/local/{account_id}/agent/{agent_id}/...
/local/{account_id}/session/{session_id}/...
访问规则:
(user_id=ua, agent_id=aa) 时,只能访问 viking://user/ua/agent/aa/...viking://user/ua/agent/{other_agent_id}/...(user_id=ua, agent_id=aa) 时,可以访问 viking://agent/aa/... 下的全部 agent 数据,不受当前 user 影响viking://agent/{other_agent_id}/...session 按 account 共享,访问规则独立于这两个字段适用场景:
isolate_user_scope_by_agent=true,isolate_agent_scope_by_user=trueviking://user/{user_id}/agent/{agent_id}/...
viking://agent/{agent_id}/user/{user_id}/...
viking://session/{session_id}/...
底层目录:
/local/{account_id}/user/{user_id}/agent/{agent_id}/...
/local/{account_id}/agent/{agent_id}/user/{user_id}/...
/local/{account_id}/session/{session_id}/...
访问规则:
(user_id=ua, agent_id=aa) 时,只能访问 viking://user/ua/agent/aa/...viking://user/ua/agent/{other_agent_id}/...(user_id=ua, agent_id=aa) 时,只能访问 viking://agent/aa/user/ua/...viking://agent/aa/user/{other_user_id}/...viking://agent/{other_agent_id}/user/ua/...session 按 account 共享,访问规则独立于这两个字段适用场景:
(user, agent) 组合都应视为独立工作单元,不能共享任何用户记忆或 agent 沉淀memories/
preferences/
entities/
events/
profile.md
memories/
cases/
patterns/
skills/
instructions/
messages.jsonl
history/
tools/
.meta.json
保留如下简写:
viking://user/...viking://agent/...但内部一律按当前 account policy 和请求身份展开为 canonical URI。
示例:当前身份为 (account=acme, user=ua, agent=aa)
当 isolate_user_scope_by_agent=false 时:
viking://user/memories/preferences/
=> viking://user/ua/memories/preferences/
当 isolate_user_scope_by_agent=true 时:
viking://user/memories/preferences/
=> viking://user/ua/agent/aa/memories/preferences/
当 isolate_agent_scope_by_user=false 时:
viking://agent/memories/cases/
=> viking://agent/aa/memories/cases/
当 isolate_agent_scope_by_user=true 时:
viking://agent/memories/cases/
=> viking://agent/aa/user/ua/memories/cases/
要求:
将 session 从 user 目录下移出,统一为 account 级共享:
viking://session/{session_id}
底层目录:
/local/{account_id}/session/{session_id}
设计原因:
推荐权限:
create / list / get / add-message / commit
delete
ADMINROOT这意味着:
session/.meta.json 新增字段:
created_by_user_idparticipant_user_idsparticipant_agent_ids说明:
created_by_user_id 表示“这个 session 最初是由哪个请求身份创建出来的”.meta.jsonPOST /api/v1/sessions 创建auto_create 语义,则第一次自动创建时也写入RequestContext.user.user_idadd-message 的 role_id 变化而变化participant_* 只用于索引、展示、后续提取分流,不承担 ACL也就是说:
created_by_user_id 不是客户端额外传的参数role_id 不是一回事
created_by_user_id 解决“这个 session 最初由谁创建”role_id 解决“这条消息是谁说的”add-message 接口扩展在 POST /api/v1/sessions/{session_id}/messages 中新增 role_id。
示例:
{
"role": "user",
"role_id": "ua",
"content": "你好"
}
{
"role": "assistant",
"role_id": "aa",
"parts": [...]
}
规则:
role == "user" 时,role_id 表示真实 user_idrole == "assistant" 时,role_id 表示真实 agent_idrole_idRequestContext 解析服务端需要区分“真实调用者身份”和“本次请求按谁的视角执行”。
在不同认证模式下,X-OpenViking-Account、X-OpenViking-User、X-OpenViking-Agent 的语义如下:
trusted 模式
X-OpenViking-Account / User / Agent 表示真实调用者身份api_key + ROOT
X-OpenViking-Account / User / Agent 表示本次请求的生效上下文ROOT 可以显式指定本次请求作用到哪个 account / user / agentapi_key + ADMIN
X-OpenViking-User / Agent 表示本次请求的生效 user / agent 上下文ADMIN 只能在自己的 account 内使用这组请求头模拟用户视角ADMIN 不允许通过 X-OpenViking-Account 切换到其他 accountapi_key + USER
user_id 只能从当前 user key 对应的身份解析X-OpenViking-Agent 仍可用于指定当前请求的 agent 上下文USER 不允许通过 X-OpenViking-User 切换到其他 userRequestContext 的语义统一为:
ctx.user 表示本次请求的生效身份ctx.role 表示真实调用者角色ctx.user 执行ctx.role 判断add-message 校验与默认填充role_id 的语义如下:
role = "user" 时,role_id 表示消息所属的 user_idrole = "assistant" 时,role_id 表示消息所属的 agent_id校验与填充规则:
trusted、ROOT、ADMIN
role_idrole = "user" 时,默认填充 ctx.user.user_idrole = "assistant" 时,默认填充 ctx.user.agent_idUSER
role_id,服务端以传入值为准role = "user" 时,默认填充 ctx.user.user_idrole = "assistant" 时,默认填充 ctx.user.agent_id额外约束:
role = "user" 时,role_id 语义表示消息所属的 user_id,由调用方保证为当前 account 下合法的 user_idrole = "assistant" 时,role_id 作为 agent_id 使用role_id 做格式或上下文一致性校验,语义一致性由调用方承担Message 增加字段:
{
"id": "msg_xxx",
"role": "assistant",
"role_id": "aa",
"parts": [...],
"created_at": "2026-04-09T10:00:00Z"
}
role_id 表示这条消息的 actor 身份,不等于真实调用者身份。后续从消息派生 tool / skill / memory 归属时,应以消息自身的 role + role_id 为准。
tool_uri 统一调整为:
viking://session/{session_id}/tools/{tool_id}
后续凡是从 message 中派生 tool / skill / memory 归属时,都应优先使用消息上的 role + role_id,而不是默认使用当前请求上下文里的 agent / user。
当前很多逻辑依赖单一字符串字段 owner_space。这个字段在以下场景下不够稳定:
向量索引和 Context 的归属字段调整为:
account_iduriowner_user_idowner_agent_idowner_scope其中:
uri 使用标准路径scope 从 uri 的前缀解析得到,不单独存 owner_scopeowner_user_id / owner_agent_id 用来表达这条数据绑定到哪个 user / agenturi 和 owner_user_id / owner_agent_id 需要保持一致,不能互相冲突所有写入到索引的数据,都必须先确定标准 URI,再由标准 URI 生成 owner_user_id / owner_agent_id。
写入时规则如下:
uri = viking://resources/...
owner_user_id = null
owner_agent_id = null
uri = viking://user/{user_id}/... if isolate_user_scope_by_agent = false
uri = viking://user/{user_id}/agent/{agent_id}/... if isolate_user_scope_by_agent = true
owner_user_id = user_id
owner_agent_id = null if isolate_user_scope_by_agent = false
owner_agent_id = agent_id if isolate_user_scope_by_agent = true
uri = viking://agent/{agent_id}/... if isolate_agent_scope_by_user = false
uri = viking://agent/{agent_id}/user/{user_id}/... if isolate_agent_scope_by_user = true
owner_agent_id = agent_id
owner_user_id = null if isolate_agent_scope_by_user = false
owner_user_id = user_id if isolate_agent_scope_by_user = true
uri = viking://session/{session_id}/...
owner_user_id = null
owner_agent_id = null
检索时先加:
account_id == ctx.account_id
然后根据当前 (user_id, agent_id) 和 account policy,算出本次请求可见的路径根:
viking://resources/
viking://session/
viking://user/{user_id}/... if isolate_user_scope_by_agent = false
viking://user/{user_id}/agent/{agent_id}/... if isolate_user_scope_by_agent = true
viking://agent/{agent_id}/... if isolate_agent_scope_by_user = false
viking://agent/{agent_id}/user/{user_id}/... if isolate_agent_scope_by_user = true
检索过滤由两部分组成:
account_id == ctx.account_iduri 必须落在上述可见根路径下owner_user_id / owner_agent_id 必须满足当前 policy 对应的可见范围如果请求显式传了 target_uri,则:
target_uri 归一化成标准路径target_uri 是否在当前请求可见范围内target_uri 前缀下说明:
viking://session/uri 用来表达真实路径范围owner_user_id / owner_agent_id 用来表达绑定到哪个 user / agent检索返回后,服务端仍需对命中结果做一次基于 uri + owner_user_id + owner_agent_id 的一致性校验,避免索引脏数据或历史遗留数据泄漏。
错误处理如下:
target_uri 路径形状和当前 policy 不匹配:返回 400target_uri 本身形状没问题,但当前身份无权访问:返回 403target_uri 时,按当前身份可见范围做全局检索需要引入统一的 namespace resolver,承担以下职责:
(account_id, user_id, agent_id) 可见VikingFS 的以下能力都需要切到新规则:
_uri_to_path_path_to_uri_is_accessiblels / tree / stat / read / write目录初始化要区分两类节点:
memories / skills / instructions 等预置结构例如在 isolate_user_scope_by_agent=true 且 isolate_agent_scope_by_user=true 下:
viking://agent/{agent_id}
只是容器;
viking://agent/{agent_id}/user/{user_id}
才是实际的 agent scope 根。
POST /api/v1/admin/accounts 新增:
isolate_user_scope_by_agentisolate_agent_scope_by_userGET /api/v1/admin/accounts 返回同样两项配置。
account 级 namespace policy 持久化在:
/local/{account_id}/_system/setting.json文件示例:
{
"namespace": {
"isolate_user_scope_by_agent": false,
"isolate_agent_scope_by_user": false
}
}
如果创建请求未显式传值,则服务端使用默认值:
isolate_user_scope_by_agent = falseisolate_agent_scope_by_user = false本阶段不提供修改 account policy 的更新接口。
原因:
setting.json 也不在支持范围内需要扩展:
AccountInfoResolvedIdentityRequestContextMessageSessionMetaContext本次方案预计影响如下模块族:
UserIdentifierVikingFSDirectoryInitializerSessionSessionServicesessions routerMessageContext本次不采用“全量兼容”或“双读双写”策略,而是按 scope 分别处理:
user 侧默认路径本身就是 viking://user/{user_id}/...user 侧做目录迁移session 只在 AGFS 中维护,不进入 VectorDBsession_id 的前提下,session 迁移按纯目录移动处理:viking://session/{user_id}/{session_id}
-> viking://session/{session_id}
memory.agent_scope_mode 参与服务端命名空间决策ovpack 做离线备份推荐迁移方式:
ov export viking://agent/{legacy_agent_space_hash}/memories ./agent_memory.ovpack
# isolate_agent_scope_by_user = false
ov import ./agent_memory.ovpack viking://agent/{agent_id}/ --force
# isolate_agent_scope_by_user = true
ov import ./agent_memory.ovpack viking://agent/{agent_id}/user/{user_id}/ --force
.ovpack 直接导入到 .../memories/ 本身,否则会得到 .../memories/memories/...isolate_agent_scope_by_user = true 时,viking://agent/{agent_id}/user/ 不是合法目标;必须显式提供 user_id原因:
agent_iduser_id / agent_id本次兼容策略总结如下:
user:天然兼容,不迁session:直接 mvagent:不自动兼容,只提供 ovpack 导出这个取舍的目的是把新命名空间模型尽快收敛为单一存储契约,而不是把 legacy hash 逻辑继续带入新实现。
当一个 session 同时包含多个 user 和多个 assistant 时,后续 commit / extract 如何分流到对应 user / agent 作用域,是独立问题。
本次只解决:
本方案采用 account 级共享 session。也就是说,同 account 内用户都可以看见 session。
如果未来需要“仅参与者可见”的 session,需要额外引入 participant ACL 模型,这不在本次范围内。
一旦 account 已有数据,切换 isolate_user_scope_by_agent 或 isolate_agent_scope_by_user 会导致:
因此本阶段不支持在线修改。
false / falseuser / agent canonical URI 生成正确viking://session/{session_id}role_id 校验正确Message.role_id 持久化正确participant_user_ids / participant_agent_ids 正确累积ADMIN / ROOT 删除权限正确ls / stat / read 与检索结果一致role_id建议按以下顺序实施:
UserIdentifier、RequestContext、Admin APIVikingFS 与目录初始化session 路径与 Message.role_idContext、embedding、vector schema 与过滤逻辑这样可以先把“路径与身份语义”固定,再去改造检索与接入层,风险更可控。
本方案的核心是把 user、agent 的共享关系显式提升为 account 级策略,并将 session 升级为 account 级共享容器,再用 role_id 和标准 URI 把消息、目录和检索统一到同一套身份模型上。
如果接受这份方案,后续实现应严格遵守两条主线:
account_id + uri + owner_user_id + owner_agent_id这两条一旦立住,后续 mixed session extraction、participant ACL、审计追踪才有稳定的基础。