Skip to content

Latest commit

 

History

History
545 lines (474 loc) · 31.2 KB

File metadata and controls

545 lines (474 loc) · 31.2 KB

架构

语言: English · Tiếng Việt · 中文

OpenSpace 与 OpenViking 在运行时如何交互的端到端全景。


组件地图

┌──────────────────────────────────────────────────────────────────────┐
│              Host Agent(OpenClaw、Claude Code、Codex、…)           │
│                                                                       │
│   路径 A: execute_task(instruction) ─── 委派任务                      │
│   路径 B: openviking_* MCP 工具 ──── 直接访问 Viking(新)          │
└──────┬─────────────────────────────────────────┬──────────────────────┘
       │                                         │
       ▼                                         ▼
┌─────────────────────────────────────┐   ┌──────────────────────────┐
│       OpenSpace(保持不变)          │   │  mcp_tools.py(新)      │
│                                      │   │                          │
│  ┌──────────┐ ┌──────────────┐      │   │  暴露给 host agent 的    │
│  │ Skill    │ │ Grounding    │ ◀─┐  │   │  5 个 MCP 工具:         │
│  │ Registry │▶│ Agent        │   │  │   │    retrieve_memory       │
│  └──────────┘ └──────────────┘   │  │   │    remember              │
│       ▲            ▲             │  │   │    forget_memory         │
│       │ hints      │ iter-1 ctx  │  │   │    report_stale_memory   │
│       │            │ + mid-iter  │  │   │    memory_status         │
│       │            │ retrieve    │  │   └────────────┬─────────────┘
│       │            │ _memory     │  │                │
│  ┌────┴────────────┴────────┐   │  │                │
│  │    Execution Analyzer    │   │  │                │
│  └────────────┬─────────────┘   │  │                │
│               │ evolved skills   │  │                │
│  ┌────────────▼─────────────┐   │  │                │
│  │       Skill Evolver      │   │  │                │
│  └────────────┬─────────────┘   │  │                │
│               │                  │  │                │
└───────────────┼──────────────────┼──┼────────────────┼─────────────┘
                │                  │  │                │
┌───────────────┴──────────────────┴──┴────────────────┴─────────────┐
│                 openspace/viking/                                    │
│                                                                      │
│  ┌────────────────┐  ┌──────────────────┐  ┌────────────────────┐  │
│  │ OpenVikingClient│  │ VikingEnrichment │  │ RetrieveMemoryTool │  │
│  │ • HTTP + URIs   │  │ • compose_query  │  │ • 迭代中使用的     │  │
│  │ • health cache  │◀─│ • enrich_pre     │  │   LocalTool        │  │
│  │ • score_thresh  │  │ • feedback_evo   │  │                    │  │
│  │ • delete res    │  │ • feedback_neg   │  │                    │  │
│  │ • find_antipat  │  │ • report_stale   │  └────────────────────┘  │
│  └────────────────┘  │ • push_skills    │                           │
│  ┌────────────────┐  │ • env fingerprint│  ┌────────────────────┐  │
│  │   scrubber.py  │─▶│ (应用 scrub)   │  │    config.py       │  │
│  │ • 正则 PII     │  └──────────────────┘  │ • 身份解析         │  │
│  │ • Luhn CC 校验 │                         │ • push/scrub/score │  │
│  │ • 私钥         │                         │ • stats            │  │
│  └────────────────┘                         └────────────────────┘  │
└──────────────────────┬───────────────────────────────────────────────┘
                       │ HTTP(5s 超时,graceful fail)
                       ▼
┌──────────────────────────────────────────────────────────────────────┐
│              OpenViking server(localhost:1933)                     │
│                                                                      │
│  /api/v1/search/find   —— L0 abstracts(带 score_threshold)         │
│  /api/v1/sessions      —— 丰富的反馈 session(positive + negative)  │
│  /api/v1/skills        —— 结构化 skill 资源推送                      │
│  /api/v1/resources     —— DELETE 用于 forget_memory                  │
│  /health               —— 存活探针                                   │
│                                                                      │
│  后台:记忆抽取(8 类,包括 antipatterns)+ 向量化                  │
└──────────────────────────────────────────────────────────────────────┘

文件概览

文件 角色
openspace/viking/client.py OpenVikingClient —— 异步 HTTP、URI 构建器、二级健康缓存、skill push、find_antipatterns、score threshold、delete_resource
openspace/viking/enrichment.py VikingEnrichment —— 执行前(6 个并行 find)、分析阶段、丰富反馈、negative feedback、skill push、staleness 上报、环境指纹
openspace/viking/config.py resolve_viking_identityresolve_viking_push_enabledresolve_viking_min_scoreresolve_viking_scrub_piiVikingExecutionStats(零依赖)
openspace/viking/scrubber.py [新] 基于正则的 PII / 密钥清理器 —— API keys、JWT、邮箱、电话、信用卡(Luhn)、私钥
openspace/viking/mcp_tools.py [新] 暴露给 host agent 的 5 个 MCP 工具:retrieve_memoryrememberforget_memoryreport_stale_memorymemory_status
openspace/viking/retrieve_memory_tool.py [新] RetrieveMemoryTool —— 作为 LocalTool 供 grounding-agent 迭代中使用
openspace/viking/__init__.py Public API 导出
openspace/tool_layer.py 配置字段、client 初始化、执行前 enrichment、反馈接线(positive + negative)、遥测、provide_feedback() 公共 API
openspace/agents/grounding_agent.py _viking_context 属性、iter-2 剥离、_viking_client 属性、迭代中 RetrieveMemoryTool 注册
openspace/skill_engine/registry.py select_skills_with_llm 接受 cross_session_hints
openspace/skill_engine/analyzer.py 注入 viking_client、分析 prompt 增强、遥测属性
openspace/mcp_server.py _maybe_register_viking_tools() 在启动时自动注册 5 个 host MCP 工具

四种用户流

Flow A —— 新用户的第一个 task(冷启动)

User(新)→ OpenSpace.execute("分析 sales.xlsx 并生成 dashboard")
  │
  ├─ Viking /health: 200(或超时 → 空增强,流程继续)
  ├─ Viking /search/find × 5 并行(tool/pattern/skill/pref/case)
  │    → 全部返回 [](还没学到任何东西)
  ├─ viking_context = "" → 跳过注入(无开销)
  ├─ Skill-first Phase 1(和今天一样)
  ├─ 执行... 12 次 iteration, 45 次工具调用
  ├─ Analyzer 运行 → evolution_suggestions: [DERIVED(sales-dashboard-builder)]
  ├─ Evolver 在本地创建新 skill
  ├─ Viking 丰富反馈 session(task + 响应 + 工具序列 + skill 元数据)
  │    → commit → Viking 异步抽取
  │      • 抽取到 viking://.../agent/memories/skills/
  │      • 从 task 文本抽取用户偏好到 user/memories/preferences/
  │      • 抽取工具知识(shell:xlsx_to_csv 成功)
  ├─ Viking skill 资源推送(如果 OPENVIKING_PUSH_SKILLS=true)
  └─ 响应 → user,遥测数据一并返回

结果:这个 task 从 Viking 得到零收益。Baseline 成本 = 和今天完全一样。开销:约 5 次失败 HTTP 调用 = 几毫秒(有超时保护)。这里创建的记忆将在未来的 task 中变得可用。

Flow B —— 同一用户的第二个相似 task(温缓存)

User → OpenSpace.execute("生成本周销售的 dashboard")
  │
  ├─ compose_query = task + 最后 3 个用户轮次(来自 conversation_history)
  ├─ Viking /search/find × 5 并行:
  │    • find_user_preferences → "用户偏好柱状图,保存 XLSX 而非 CSV"
  │    • find_tool_knowledge   → "shell:xlsx_to_csv 对 .xlsx 有效,对 .xlsm 失败"
  │    • find_skill_knowledge  → "sales-dashboard-builder 用 8 次 iteration 解决过类似的"
  │    • find_cases            → "过往 task 在聚合前清理了 NaN 行"
  │
  ├─ 构建两个增强块:
  │    1. context_injection(~800 tokens,完整 L0 abstract)
  │    2. selector_hints(~150 tokens,仅用户偏好 + 顶级 skill/case)
  │
  ├─ _select_and_inject_skills(task, cross_session_hints=selector_hints)
  │    → Selector LLM 看到 hints 块 → 自信地选择 sales-dashboard-builder
  │
  ├─ grounding_agent._viking_context = context_injection
  ├─ Phase 1 执行 → LLM 已经知道:
  │    ✓ 用户想要柱状图 → 跳过澄清
  │    ✓ 文件很可能是 .xlsx → 跳过 .xlsm 探测
  │    ✓ NaN 处理 → 跳过 2 次调试 iteration
  │    → 用 6 次 iteration 完成,而不是 12 次
  │
  ├─ iter 2+:viking_context 和 skill_context 都被剥离(grounding_agent.py:232-251)
  ├─ Analyzer 运行 → 自信,无需建议 evolution
  └─ 响应 → user

结果:iteration 数减少约 50%,这个 task 的 token 减少约 25–40%。详细计算见 Token 经济学

Flow C —— 跨 Agent 知识传播(静默、异步)

Agent A(Claude Code 用户,周一 10am)
  └─ OpenSpace → 修复 "chromedriver 版本不匹配" task
     └─ Viking 抽取工具知识:
        "chromedriver 124 需要 Chrome 124+"
        → viking://tenants/acme/agent/memories/tools/chromedriver.md

几小时后...

Agent B(Codex 用户,不同机器,周一 2pm)
  └─ OpenSpace → "爬取 example.com"
     └─ Viking find_tool_knowledge("chromedriver 设置")
        → 返回 [{abstract: "chromedriver 124 需要 Chrome 124+", score: 0.88}]
     └─ Agent B 跳过整个 3 次 iteration 的调试循环

关键洞察:两个 Agent 连接到同一个团队的 Viking 服务器(相同的 OPENVIKING_NAMESPACE)。Agent A 的 session commit 自动触发记忆抽取;Agent B 之后的 find_tool_knowledge 调用自然而然地检索到它。没有手工 upload 步骤,没有显式的"分享"按钮。

这是 OpenSpace 公开宣传的 "一个 Agent 学会,所有 Agent 受益" 口号从一个向往中的云端 upload workflow 变成了一个自动的跨 Agent 反馈闭环。

Flow D —— 分析阶段增强(沉睡的胜利)

Task 在 iter 15 失败 —— analyzer 启动
  │
  ├─ analyzer._build_analysis_prompt(context) [async]
  │    ├─ 构建正常 prompt(traj + skill 内容 + 对话)
  │    ├─ 检查 self._viking_client(OpenSpace 初始化时共享 —— 没有新 HTTP 连接)
  │    ├─ 从 traj_records 中提取 status=error 的 tool_issues
  │    ├─ enricher.enrich_analysis_context(task, tool_issues):
  │    │    • find_tool_knowledge(task)   —— 通用工具模式
  │    │    • find_cases(task)             —— 历史解决方案
  │    │    • find_tool_knowledge(tool_issues[0])  —— 特定的已知修复
  │    ├─ 追加格式化 context 到 resource_info
  │    └─ 设置 self._last_viking_context_chars(遥测)
  │
  ├─ 分析 LLM 现在知道 "这个错误模式 → 上周应用过的解决方案 X"
  │    → 发出 evolution_type=FIX(精准编辑)
  │    → FIX 比 CAPTURED(从头新建 skill)便宜
  │
  └─ Evolver 处理建议

为什么这很重要:Analyzer 通常是 OpenSpace task 中最贵的 LLM 调用(来自 traj + 对话 + skill 内容的 30k+ input tokens)。一个小小的增强(额外 ~500 tokens)就能把推测性的"新建 skill"建议转化为精准的两行修复,节省整个后续 evolution 周期。


数据流序列

D1 —— 执行前增强(热路径,阻塞)

OpenSpace.execute(task, context={conversation_history, ...})
    │
    ├── [if viking_client && is_available()]
    │   VikingEnrichment.enrich_pre_execution(task, conversation_history)
    │     │
    │     ├── compose_query(task, history)
    │     │     • 提取最后 3 个 USER 轮次(multimodal 仅提取文本部分)
    │     │     • 用 "Prior user turns: ... || ..." 连接
    │     │     • 截断到 500 字符
    │     │
    │     ├── asyncio.gather(    ── 6 个并行 FINDS(之前是 5 个)──
    │     │     find_tool_knowledge(query, limit=5, score_threshold=min),
    │     │     find_pattern_knowledge(query, limit=5, score_threshold=min),
    │     │     find_skill_knowledge(query, limit=5, score_threshold=min),
    │     │     find_user_preferences(query, limit=3, score_threshold=min),
    │     │     find_cases(query, limit=3, score_threshold=min),
    │     │     find_antipatterns(query, limit=3, score_threshold=min),  ← 新增
    │     │   )
    │     │   → 6 个并行 HTTP POST,每个 5s 超时
    │     │   → 每个都带作用域化的 target_uri 命中 /api/v1/search/find
    │     │     以及 payload 中的 score_threshold
    │     │   → 客户端安全网会 drop 任何低于 score_threshold 的命中
    │     │
    │     ├── 去重 abstract,构建两个输出块:
    │     │   1. context_injection(markdown 5 节,包括
    │     │      "## Known Failure Modes (AVOID)" 用于 anti-patterns)
    │     │   2. selector_hints(更短 —— 偏好 + 顶级 skill + 顶级 case
    │     │      + "AVOID (prior failures)" 条目)
    │     │
    │     └── return {context_injection, selector_hints, hit_counts,
    │                 antipattern_hints, query, ...}
    │
    ├── _viking_stats.query / hit_counts / enrichment_chars 填充
    ├── grounding_agent._viking_context = context_injection
    │
    └── _select_and_inject_skills(task, cross_session_hints=selector_hints)
          └── registry.select_skills_with_llm(task, ..., cross_session_hints=...)
                └── prompt 包含 "# Cross-Session Hints (non-authoritative)"

Latency 预算:6 个并行 HTTP 调用,每个 5s 超时。典型缓存响应:50–200ms(并行意味着 6 个 find 成本约等于 1 个)。最坏情况:超时 → [] → 空 context → 零 prompt 开销

注入的 token 成本:6 个类别 × 约 3 条 abstract × 约 100 tokens ≈ 1,800 tokens,仅 iter 1。由 grounding_agent.py 从 iter 2 起剥离。

质量过滤:当设置 OPENVIKING_MIN_SCORE=0.5(或 openviking_min_score 配置字段)时,低置信度命中会在服务器端 score_threshold 参数和 find_memories() 中的客户端安全网两处都被丢弃。防止错误的记忆进入 prompt。

D2 —— 执行后反馈(冷路径,非阻塞)

OpenSpace.execute() → finally: _maybe_analyze_execution(task_id, ...)
    │
    ├── analyzer.analyze_execution() → 带 evolution_suggestions 的分析
    │     └── _build_analysis_prompt() [ASYNC]
    │           └── 使用 self._viking_client(共享,不是新连接)
    │           └── enrich_analysis_context → resource_info 块
    │           └── self._last_viking_context_chars = N(遥测)
    │
    ├── _viking_stats.analysis_context_used = (chars > 0)
    │
    ├── evolver.process_analysis() → evolved_records
    │
    ├── scrub_enabled = resolve_viking_scrub_pii(config.openviking_scrub_pii)
    │
    ├── [POSITIVE PATH —— if evolved && viking_client]
    │   │
    │   ├── 为每个 evolved record 从磁盘读取 SKILL.md 内容
    │   ├── 从 execution_result.tool_executions 提取 tool_sequence(前 30 个)
    │   ├── 从 grounding_agent._current_instruction 提取 task_description
    │   ├── 从 execution_result.response 提取 final_response
    │   │
    │   ├── VikingEnrichment.feedback_evolution(
    │   │       task_id,
    │   │       feedback_data,
    │   │       task_description=...,
    │   │       final_response=...,
    │   │       tool_sequence=...,
    │   │       scrub_pii=scrub_enabled,    ← 新:应用 scrubber.py
    │   │   )
    │   │   │
    │   │   ├── clean = _make_scrub(scrub_enabled) — 正则 PII redactor
    │   │   ├── create_session("openspace-evo-<task_id>")
    │   │   ├── add_session_message("user", clean(task_description))
    │   │   ├── add_session_message("assistant", clean(f"Final response:..."))
    │   │   ├── add_session_message("assistant", clean("Tool sequence: ..."))
    │   │   ├── add_session_message("assistant",
    │   │   │     f"Environment: {_env_fingerprint()}")  ← 新增
    │   │   ├── add_session_message("assistant", clean(f"Skill 'X' evolved..."))  # × N
    │   │   └── commit_session(...)
    │   │
    │   ├── _viking_stats.feedback_status = "committed"
    │   │
    │   └── [if resolve_viking_push_enabled()]
    │       push_evolved_skills(feedback_data, scrub_pii=scrub_enabled)
    │       → 每个 skill content 在 POST /api/v1/skills 前经过 scrubber
    │       → metadata 包含 env fingerprint
    │       _viking_stats.pushed_skills = N
    │
    └── [NEGATIVE PATH —— 新 —— if status in (error, incomplete) && viking_client]
        │
        ├── failure_reason = execution_result.error or response
        ├── tool_sequence = execution_result.tool_executions[:30]
        │
        └── VikingEnrichment.feedback_negative(
                task_id,
                task_description=...,
                failure_reason=...,
                tool_sequence=...,
                scrub_pii=scrub_enabled,
            )
            │
            ├── create_session("openspace-neg-<task_id>")
            ├── add_session_message("assistant",
            │     "POLARITY: negative — 这是一个失败的执行记录...")
            ├── add_session_message("user", clean(task_description))
            ├── add_session_message("assistant", clean(f"Failure reason: ..."))
            ├── add_session_message("assistant", clean(f"Tool sequence: ..."))
            ├── add_session_message("assistant",
            │     f"Environment: {_env_fingerprint()}")
            └── commit_session(...)
                  → Viking 抽取到 viking://.../agent/memories/antipatterns/
                  → 未来的 task 会看到它作为 "AVOID" 警告

隐私防御:当 OPENVIKING_SCRUB_PII=true(默认)时,每个用户编写的字符串在到达 Viking 之前都会通过正则清理器。API keys、JWT、邮箱、电话、Luhn 校验通过的信用卡和私钥块都会被替换为 [REDACTED_*] 占位符。幂等 —— 对已清理的文本再次清理是 no-op。

OpenSpace 侧成本:每个 evolved skill 约 4–6 次 HTTP 调用(session 创建 + 3 条消息 + commit + 可选资源推送)。对用户感知的延迟零影响 —— 在 execute() 已经有返回值之后的 finally 块中运行。

D3 —— 分层成本模型

OpenViking 把每个上下文存储在三层:

大小 何时获取
L0 abstract ~100 tokens /api/v1/search/find 默认返回
L1 overview ~1–2k tokens 按需通过 /api/v1/resources/overview
L2 detail 无限 按需通过 /api/v1/resources/read

我们目前的集成只获取 L0。28 条 abstract(6 类 × ~5 limit)× 100 tokens = 最坏情况 ~2.8k tokens;经过去重和截断后通常是 800–1,800 tokens。这是有意的保守。未来的增强可以在置信度超过阈值时为评分最高的 top-1 case 获取 L1,但目前没有接入。

D4 —— 迭代中 retrieve_memory 工具(动态,在 agent loop 内部)

grounding_agent.process() → iteration loop
    │
    │  iter 1: 获得执行前 enrichment(D1 路径)
    │  iter 2: skill_context 和 viking_context 都被剥离
    │  iter 3+: LLM 可能发现它需要不同的知识
    │
    ▼
LLM 发出 tool call → retrieve_memory(query="error X", category="antipatterns")
    │
    ▼
RetrieveMemoryTool._arun(query, category, limit=5)
    │
    ├── is_available() 检查(cached)
    ├── 规范化 category → target_uri 通过 client.agent_memory_uri(...)
    │   或 client.user_memory_uri(...)
    ├── find_memories(query, target_uri, limit, score_threshold)
    │     → 1 个 HTTP POST 到 /api/v1/search/find
    │
    └── 格式化结果为纯文本块:
          "# Retrieved from OpenViking — query: 'error X'
           # Category: antipatterns, 3 result(s)
           ## WARNING: these are FAILED approaches...
           1. [score=0.82] chromedriver 124 fails on macOS <14
              uri: viking://.../antipatterns/...
           2. ..."
    │
    ▼
LLM 收到 tool result → 决定下一步行动
    → 可能避免失败的方法
    → 可能用不同的 category 再次调用 retrieve_memory
    → 可能采用替代策略

为什么这很重要:执行前 enrichment 只能看到初始查询。如果 agent 在执行中发现真正的问题与查询所暗示的不同,D4 让它可以拉取新鲜知识而无需等待下一个 task。该工具在 grounding_agent.py 中与 retrieve_skill 并排注册,条件是 openviking_mid_iter_tool=True_viking_client 已设置。

成本:每次调用 1 个 HTTP。Agent 只在真正卡住时调用 —— 每个 task 通常 0–2 次。相对于替代方案(可能需要 3+ 次 iteration 的探索循环),开销可忽略。

D5 —— Host agent 直接 MCP 工具访问(OpenSpace 执行之外)

Host agent 的 chat LLM(例如 OpenClaw Claude 或 Codex)
    │
    │  用户输入: "我之前说过 dashboard 颜色偏好是什么来着?"
    │
    ▼
Host LLM 看到 MCP tool 列表 → 选择 openviking_retrieve_memory
    │
    ▼
MCP 服务器把调用路由到 tool_retrieve_memory(get_client, query, category, limit)
    │
    ├── get_client() → _get_viking_client()
    │     → lazy-init OpenSpace → return os._viking_client
    │
    ├── 验证:非空 query、is_available() 为 True
    ├── _resolve_target_uri(client, category="preferences")
    │     → viking://tenants/<ns>/user/<uid>/memories/preferences/
    │
    ├── client.find_memories(query, target_uri, limit)
    │
    └── 返回 JSON 给 MCP 客户端:
          {
            "status": "ok",
            "data": {
              "query": "...",
              "category": "preferences",
              "target_uri": "viking://...",
              "count": 2,
              "results": [
                {"uri": "...", "abstract": "prefers teal accent, white background",
                 "score": 0.91, "category": "preferences"},
                ...
              ]
            }
          }
    │
    ▼
Host LLM 接收结果 → 用它来个性化聊天回复
    → 不需要 OpenSpace execute_task() 调用
    → 用户立即得到个性化响应

这是 Round 6 最大的架构改进。 之前的设计要求通过 OpenSpace 进行完整的 execute_task() 往返,host 才能从 Viking 记忆中受益。现在 host 可以直接 retrieve(以及 write、forget、report stale)记忆 —— 使跨会话个性化对每个支持 MCP 的 host agent 普遍可用

register_viking_mcp_tools(mcp, _get_viking_client) 注册的 5 个工具:

工具 用途 典型调用方
openviking_retrieve_memory(query, category?, limit?) 获取 L0 abstracts Host LLM 个性化回复
openviking_remember(content, category, polarity?) 写入新记忆 Host LLM 捕获用户明确陈述
openviking_forget_memory(uri, reason?) 删除 / 弃用 Host LLM 响应"忘了这个"
openviking_report_stale_memory(uri, reason) 标记为过时(比 forget 轻) Host LLM 注意到过时记忆
openviking_memory_status() 健康 + 配置自省 Host LLM 在 UI 中显示集成状态

所有工具都返回结构化 JSON({"status": "ok"|"error", ...}),以便 host LLM 能够优雅地处理 Viking 不可用的情况。


迭代循环的上下文纪律

grounding agent 迭代循环强制严格的 context 卫生,让每次 iteration 的 token 成本保持常数:

# openspace/agents/grounding_agent.py:226-251
while current_iteration < max_iterations:
    current_iteration += 1

    # 在 iteration 2 剥离 skill context 和 viking context。
    # 两者都是规划提示 —— 对构建执行计划的第一次 LLM 调用有用,
    # 一旦工具结果开始在消息历史中累积,它们就变成纯粹的噪音。
    if current_iteration == 2:
        drop_contents = set()
        if self._skill_context:
            drop_contents.add(self._skill_context)
        viking_ctx = getattr(self, "_viking_context", "")
        if viking_ctx and viking_ctx.strip():
            drop_contents.add(viking_ctx)
        if drop_contents:
            messages = [
                m for m in messages
                if not (m.get("role") == "system" and m.get("content") in drop_contents)
            ]

    # 每次 iteration 的上限和截断也适用
    if current_iteration >= 2:
        messages = cap_message_content(messages)
    if current_iteration >= 5:
        messages = truncate_messages(messages, keep_recent=8, max_tokens_estimate=120000)

没有 iter-2 剥离,每次 iteration 都会把 1,500 tokens 的 Viking 块重新发给 LLM。一个 12 次 iteration 的 task 会在陈旧提示上浪费 ~18k tokens。剥离是 Viking 集成中最重要的 token 节省机制。


身份与 URI 作用域

OPENVIKING_NAMESPACE=acmeOPENVIKING_USER_ID=alice 时,client 构建:

Agent 共享(团队级):
  viking://tenants/acme/agent/memories/tools/
  viking://tenants/acme/agent/memories/patterns/
  viking://tenants/acme/agent/memories/skills/
  viking://tenants/acme/agent/memories/cases/

用户隔离(团队内按用户):
  viking://tenants/acme/user/alice/memories/preferences/

单租户开发机(无 namespace、无 user_id 设置,回退到 $USER=jimmy):

Agent 共享(全局):
  viking://agent/memories/tools/

用户隔离(全局用户目录):
  viking://user/memories/preferences/

完整的解析链和部署模式见 配置


失败模式树

OpenSpace.execute(task)
    │
    ├─[viking_enabled=False]────────────────────▶ 没有 Viking 代码运行,never。
    │
    ├─[httpx 未安装]────────────────────────────▶ _get_client() 返回 None,
    │                                              所有方法返回 {}/[]
    │
    ├─[服务器不可达]────────────────────────────▶ 5s 超时 → 异常 →
    │                                              捕获 → 返回 {}/[]
    │
    ├─[服务器返回 4xx/5xx]──────────────────────▶ 状态检查 → None →
    │                                              返回 {}/[]
    │
    ├─[服务器返回 malformed JSON]───────────────▶ json.loads 异常 →
    │                                              返回 {}
    │
    ├─[find 返回意外格式]───────────────────────▶ 解析器尝试多种包络
    │                                              → 返回 []
    │
    ├─[添加消息后 commit 失败]──────────────────▶ 异常捕获,
    │                                              logger.warning,不 raise
    │
    └─[某个 record 的 skill push 失败]──────────▶ 每 record try/except,
                                                   pushed 计数只反映
                                                   成功

每条路径都通向 graceful degradation。面向用户的契约是:如果 Viking 坏了,OpenSpace 运行时就像 Viking 不存在一样


下一页Token 经济学 用真实 baseline 量化实际节省。