You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
关联:RFC #3330 Session 上下文优化(token 预算估算器共享、turn_id 锚定去重时钟等协调项);实验分支
feat/auto-recall-v2(本提案的原型验证素材)。1. 现状盘点(main 实测)
main 上现有三个检索端点,能力矩阵如下:
/find/search/recall(v1,自 v0.4.9)FindRequestsession_idquotas / max_chars / min_score / peer_scope / render(bool)limit条数limit条数max_chars字符预算 + full→summary→uri 降级query_planentries + rendered + stats组装包四个关键事实(源码位置见附录):
/search≡/find。不带session_id调用时永远落入此分支。hierarchical_retriever.py);配置项default_search_mode存在但未接线(死配置)。/recall无任何 LLM 参与,本质是分桶/find+ 预算渲染——type_quota_recall.py是「一份实现、两个入口」(REST 与 MCP 工具都直调它),组装逻辑本就架在 find 之上。/recall的生产消费方仅限官方插件族;SDK 与 CLI 从未暴露过 recall,端点处置自由度很高。1.1 实验分支验证了什么
我们曾在实验分支(
feat/auto-recall-v2)上尝试过另一条路径:继续增强/recall。原型已全部跑通、实测数据良好,但代价也暴露无遗:它在/recall内部造出了与/search平行的第二条 session/LLM 处理链路——这恰好反证了这些能力的正确归宿是/search本身。原型成果转化为本提案的设计素材:query_expansion(§3.2)rewrite(§3.2)exclude_uris跨轮去重(≤200)detail档位模型(§3.4)default_search_mode接线("fast"跳过 rerank)2. 问题陈述:为什么要合并
/recall是唯一有预算渲染的端点,但只识别四种记忆类型;「coding 场景需要更多 resources/skills」这类用途配比需求在 API 面上无处表达。/search既保证路径唯一,还让旧链路顺带获得熔断保护。# Summary段落 vs 索引自带 abstract);render是布尔值,调用方必须读源码才能理解降级语义。/recall失败回落/find」双协议;文档要反复解释三个端点边界。反方向验证(已做过的思想实验):把组装逻辑退回客户端、只留 find/search 原语,是技术倒退——HTTP 调用 ×15–25、查询扩展 LLM 调用被分桶放大 ×3–4、N 份客户端实现漂移(vikingbot 与服务端移植版之间已出现 3 处语义分歧)。服务端单份组装实现的价值毋庸置疑,本提案改变的只是入口形态。
3. 新设计
定位前提:两种消费模型。 同一套 OV 存储上并存两类消费方:接管型(vikingbot——OV 输出什么模型就看到什么,session 子系统是其引擎,#3330 属该范畴)与旁挂型(Claude Code / Codex / OpenCode / pi 等 harness 自管上下文窗口,OV 只在固定注入点提供受预算封顶的补充块)。本提案的统一检索面服务于旁挂型契约:响应必须是无状态渲染块、预算由客户端申报、去重必须 opt-in、不做 Context Delta / 注入回执等接管型概念(§5)。
目标:G1
/search成为唯一复合检索面,不传新参数时行为与 main 基本一致;G2 recall v1 全部能力 + 原型增强能力均可经/search参数表达;G3 组装能力泛化至 resources/skills 域;G4 档位模型对齐ContextLevel三级阶梯,任何档位 URI 必须在场;G5 session/LLM 路径唯一化(有界、fail-closed,废弃无保险丝直调)。非目标:console/前端改动;跨类型 rerank 与召回命中率遥测闭环(后续观察项);插件侧能力(客户端压缩、TTL 去重旋钮族等——与端点选型正交,独立演进)。
3.1 端点布局
POST /api/v1/search/findPOST /api/v1/search/searchmode="list"(默认,≡今日 search)/mode="context"(吸收 recall)POST /api/v1/search/recallrecall工具「薄」的不变式(严防再次变厚):① 无独占参数——/recall 的每个参数必须在 search context 中存在且语义一致,差异仅允许在默认值;② 无业务分支——recall handler 出现业务 if 即视为违规;③ 兼容钉——显式同参时两入口响应逐字节一致(单测钉死);④ 职责仅为「叠默认值 + v1 别名翻译」后透传给与
mode="context"相同的编排函数。两套默认值只差在 auto-recall 场景真正需要不同的四处:
mode="context"/recallpurpose"coding"dedup_turnssession_id时)score_thresholdmax_tokens(默认 1600)max_chars别名折算v1 别名(仅 /recall 接受,deprecated):
max_chars、min_score(→score_threshold)、render:bool(→固定detail档)、v1 quotas 键。命中别名时响应附Deprecationheader + 服务端 warning 日志;下一个 minor 拆除别名(不是拆端点)。3.2 SearchRequest 参数分层
四层组织,每层独立、默认关闭、优雅降级:
L0 检索域——现有参数全部保留、语义不变(
query, image_url, target_uri, context_type, limit, score_threshold, filter, tags, since/until, level, …)。L1 查询理解(session-aware):
session_idstr?(已有)query_expansion"off" | "auto",默认"auto"auto= 有 session 上下文时做一次有界 IntentAnalyzer 扩展(≤3 查询、超时熔断、失败退化原查询)默认
"auto"是为兼容旧 search 语义(携带 session 即触发分析),核心改变是换成有界实现并删除无保险丝直调路径。扩展查询数受 ≤3 约束属微小语义变更,记入 CHANGELOG。L2 上下文组装(仅
mode="context"生效):mode"list" | "context",默认"list"context= 服务端组装 Context 包max_tokensint,默认 1600(≈ v1 6500 字符 /4)quotasDict[str,int]?,默认不启用events/entities/preferences/experiences/resources/skillspurpose"chat" | "coding" | null,默认 nulldetail"auto" | "abstract" | "overview" | "full",默认"auto"auto= 预算驱动先广后深dedup_turnsint,默认 0session_id时启用服务端记账去重:本次返回的 URI 在该 session 后续 N 轮内冷却exclude_urisList[str]≤200dedup_turns并用(并集过滤)peer_scope/other_peer_penalty预算单位:为什么是
max_tokens而不是max_chars。现行预算按len()字符计费,但同样 6500 字符,纯英文 ≈1.6k token、纯中文 ≈9.7k token,失真可达 6 倍——中文部署下注入成本被悄悄放大数倍。提案将 CC 插件已验证的 CJK 感知估算器(codepoint ≥ 0x3000 记 1.5 token/字符,其余 chars/4;线性单遍、无 tokenizer 依赖,中文误差 ±10–20%)上移服务端,max_tokens成为组装内核唯一计价单位;max_chars仅在 /recall 作别名在边界折算(max_tokens = max_chars/4,同传时 tokens 优先)。启发式 ±15% 的误差远胜纯字符预算的 ±500%。竞品对照:未见任何同类系统实现 token 计价预算(有按条目数、按结构上限的),这将是 OV 独有优势且随上移惠及全部 harness。协调项:#3330 的retained_message_token_budget需同一估算器——沉淀为服务端共享工具函数,避免「N token」出现两套算法。去重:服务端 session 记账为主路径。auto-capture 场景下客户端本就持续 add_message,服务端握有消息流,「轮距」= 当前 session 消息数 − 记录时消息数(时间戳兜底)。实现轻量:
dedup_turns>0且带session_id时,返回前把本次 entries 的{uri, detail, msg_index, ts}记入 session 目录下的隐藏 sidecar{session_uri}/.recall_log.json(不塞SessionMeta热路径;写时自剪枝,生命周期随 session 目录),轮距时钟用SessionMeta.total_message_count(单调递增、L1 阶段已驻留内存;#3330 的turn_id落地后切换为 User Turn 锚定)。服务端还能做客户端做不准的事:uri-only 宽限(上轮只给了裸 URI 的记忆,本轮允许升 full 档)。收益即本提案主论点:opencode、pi、cursor、trae 及任何手工集成方零实现成本获得去重,消灭 N 份客户端状态文件漂移。如实记账的残余代价:①「已返回」≠「真正注入」(客户端可对确知未注入的轮次不带dedup_turns来不记账);② 记账污染靠 opt-in 隔离(只有显式传参的调用才记账并受账本影响);③ opted-in 查询带写副作用,与「session 自动管理」定位一致。exclude_uris降级保留为无状态原语(无 session、capture 未开、或客户端自定义策略时的逃生通道)。L3 LLM 加工(仅
mode="context"生效):rewritefalse | true | "auto",默认falsedigest=""、rendered照常)rewrite_max_bulletsint1–20,默认 6相对原型的增强——结果重写的会话感知:带
session_id时,L3 重写 prompt 追加 L1 已加载会话上下文的有界切片(archive overview 优先、近几条 user 消息补齐、字符硬帽),零额外 I/O、解码量不变(熔断公式不受影响)。取材优先取 OV 生成的稳定产物(overview / 未来的 checkpoint 摘要)而非 raw 消息尾巴——旁挂模型下原始消息只是插件异步抄送的有损镜像。rewrite 的 token 成本账:每次开启 rewrite 的调用即一次服务端 LLM 推理——prefill ≈ 重写模板 + rendered 包(≤
max_tokens,默认 1600)+ 会话切片(带 session 时,字符硬帽),decode ≈ 默认 6 bullets 约 300–500 token,单次合计约 2–3k token;成本由部署方的 query_planner 模型(未配置时回退主 vlm)承担,不消耗终端用户的订阅额度。auto-recall 场景下该开销随对话轮数线性放大,是一笔真实的新增消耗;收益端则是注入主对话的内容从 rendered 全文换成 digest 摘要——省下的是主模型(通常更贵)的窗口 token,一增一减是否划算取决于部署的模型价差与召回频率。对应的设计选择:① 默认false,纯 opt-in;② 重写任务对模型能力要求不高,建议部署方为 query_planner 配置小模型;③ 响应stats透传rewrite_usage: {prompt_tokens, completion_tokens}(模型响应的 usage 现成),让部署方可核算与监控这笔开销。对照说明:L1query_expansion不构成新增成本——旧/search携带 session 时本就调用 IntentAnalyzer,本提案只是给它加上界。参数交互规则(校验层执行):
mode="list"时出现 L2/L3 参数 → 422(显式报错优于静默忽略);配额采样启用时limit不生效(quota-free 默认态下自然生效);context 模式下level失效(档位归detail管)、filter/tags/since/until无损透传到每个来源子桶(组装模式免费获得时间窗与标签过滤——v1 recall 至今不具备);首版 context 模式拒绝target_uri(422,桶与 target 求交语义待真实需求出现再定义);purpose只提供默认值,显式参数逐项覆盖。3.3 响应模型
mode="list":完全不变。mode="context":{ "entries": [ { "uri": "viking://user/.../events/2026/07/14/x.md", // 永远在场 "category": "events", "score": 0.72, "detail": "full" | "overview" | "abstract" | "uri", // 实际渲染档位;uri 为降级状态 "text": "..." } ], "rendered": "...", // 注入就绪的最终文本,永远填充 "digest": "", // rewrite 开启且成功时非空 "stats": { "query_expansion": "off|used|failed", "rewrite": "off|ok|failed|timeout", "rewrite_usage": { "prompt_tokens": 0, "completion_tokens": 0 }, "excluded": 2, ... } }rendered必须存在的两个硬理由:① 预算核算必须发生在文本最终形态上,预算与渲染逻辑不可分割;② 拼装格式只能有服务端一份实现,杜绝 N 个插件各自拼装的规则漂移(vikingbot 与服务端移植版已漂移 3 处,是付过的学费)。entries是给自定义呈现客户端的结构化原料,rendered是官方默认成品,同宗同源。格式改版:扁平可读 XML。记忆正文几乎必然含 markdown 标题,纯 markdown 无法可靠划界,边界必须用 XML;要修的是 v1 的深嵌套(元数据全是子标签、正文深埋
<content>内)。改为每条记忆一个标签对、元数据全部属性化、正文即标签体:标签深度 3 层压平为 1 层;最外层
<openviking-context>包裹仍由客户端负责(同现状)。属性取舍、按类型消费提示(如 preferences=遵循)等微观细节实现期定稿(D10)。不变式底线:URI 永远在场——每个 entry 必须携带
uri;rendered 中每个<memory>必须带uri属性;digest 每条 bullet 必须显式引用合法viking://URI(缺引用直接丢弃,原型的normalize_recall_digest已提供实现与测试)。3.4 档位概念模型
main 代码事实:系统分层
ContextLevel:L0=abstract、L1=overview、L2=full(core/context.py)。目录有真实的.abstract.md/.overview.mdsidecar;单文件只有 abstract(写入期生成的 summary 同时进父目录 overview 和该文件向量行的 abstract payload,~447B 封顶),resources 同样有 abstract。/find返回的 abstract 直接取自向量 payload,零文件读。历史债:v1 recall 的 "summary" 档混称两种数据源(events=读全文后正则抽# Summary段;其他类型=索引 abstract)。统一模型(对齐 ContextLevel,每档都带 URI):
detail是上限:固定值 = 全体条目钉在该档、超预算截条数;auto(默认)= 三轮填充。纯 uri 不是可请求档位,只是降级状态(预算溢出或去重冷却时 entry 如实记detail:"uri")。render:true的贪深策略(top 几条塞满全文、尾部只剩裸 uri 甚至被丢弃):实测命中分数集中在 0.38–0.50 窄带、区分度弱,把预算押在 top-1 全文上风险高;auto 保证所有命中至少暴露一次可见度(URI+摘要),再按分逐条加深。filename + score,语义价值近乎为零。竞品 claude-mem 的对偶哲学是「宁可全员降级为单行摘要,绝不让任何一条退化成不可读指针」。档 1 保底 + 先广后深正是对此的机制化回应;同时detail固定档位把业内「宽索引+下钻 vs 全文推送」的注入形态之争收敛为同一参数的不同取值,A/B 成本降为改一个值(D13)。detail是全局上限,各来源另有更严默认上限——memory 上限 full;resources/skills 默认封顶 abstract(防大文件或含敏感配置的 skill 被自动全文注入;显式放开留作后续参数);目录命中的 full(子树聚合读)默认封顶 overview。# Summary开头,full 档按条封顶截头会自然保住该段——v1 summary 档的核心价值被承接,无需专门提取机制。rewrite,与detail正交(detail="abstract" + rewrite=true合法:把摘要档喂给重写器)。ContextPart{uri, abstract}——两个子系统独立演进收敛到同一形态,说明档位阶梯应是平台级概念。3.5 选择策略与 purpose
默认(不传 purpose/quotas):quota-free,纯分数排序。 现网 v1 默认配额
{events:10, entities:10, preferences:3, experiences:0}继承自 vikingbot 的场景性动机(瘦身 profile 后靠配额保稳定类型),且配比数值在源码与提交记录中找不到任何评测依据(纯经验拍板)。场景性经验参数不应固化为通用接口默认。统一后默认路径:一次跨域 find(+可选查询扩展)→ 全候选纯分数排序 → auto 档位阶梯渲染;副产品是默认路径更简单便宜(省掉分桶多次检索),limit自然生效。配额采样 opt-in:显式传
quotas或指定purpose才触发分桶采样(按来源子树独立 find,机制上保证弱势类型不被淹没)。预设配比每类保底 1。示意配比(初版建议,非最终承诺):chatcoding预设放
retrieval.purpose_profiles配置(ov.conf 可覆写/扩展),内置chat/coding两档(D5)。兼容性:v1 语义可由显式 quotas 100% 复现(已发布插件本就显式发送 quotas,迁移后行为零漂移)。诚实声明:配比数值同样缺乏评测数据支撑,本 PR 交付的是机制(API 参数面 + 配置面),数值最优解依赖后续召回命中率遥测迭代——这正是设计为配置项而非硬编码的原因。3.6 初始化上下文托管(bootstrap,fast-follow 构思项)
现状:SessionStart 注入完全在客户端拼装(profile + 目录索引 + archive),每次启动 5–7 个 HTTP 往返,预算/剪除/格式逻辑散落在四个 harness 各一份变体——正是合并 recall 前「N 份拼装」处境的重现。
提案:新增
mode: "bootstrap"——复用同一 search 端点与同一组装内核,不新增端点。与mode="context"的唯一差异是候选来源:不是检索命中,而是声明式槽位 manifest;预算、渲染、档位逻辑完全同源。基于一个重度使用约 3 个月的真实记忆库实测校验(profile ~20k 字符 / preferences 170 文件 / 单月 events 688 文件)后的槽位设计:identityworking_statesince/time_field——槽位即检索原语组合,也是 bootstrap 属于 search 的实质理由);peer 启用时 peer:user ≈ 8:2continuitysession_id;现役机制经 manifest 表达)listinginstructionsrecall/read/remember使用时机;随服务端版本化,content_hash客户端缓存要点:偏好正文不进启动注入(170 条领域特定偏好里启动期任何预选都是任意抽样——正文交给每轮条件化召回与 MCP read,启动期只暴露文件名 affordance);最大杠杆在写侧治理(profile 无界累积、preferences owner 分裂是上游数据纪律问题,bootstrap 定位为受策展工件的薄运输层,不做聪明内容挑选);坚决不做 LLM 重写、不做语义检索(启动期无 query)。副产品:bootstrap 若同时以 MCP 工具暴露,纯 MCP 宿主(无 hook 能力的 harness)一次调用即可自助获取完整启动注入,新 harness 接入门槛从「手写 hook 管线」降为「教模型调一个工具」。
处置:fast-follow(D12)——首 PR 只需保证组装内核的候选来源可插拔(检索命中 / 静态 manifest 两种 provider)。
3.7 超时与延迟契约
retrieval.recall_intent_timeout_s = 10:查询扩展熔断(实测 ~2s);超时退化原查询。retrieval.recall_rewrite_timeout_s = 60:digest 重写熔断。实测 6 bullets(300–500 token)7–11s,按 20 bullets 线性外推 25–45s,60s 覆盖契约上限全部输出规模。语义是「一个回合最多愿意为 digest 卡多久」:正常路径 7–11s 返回,不受默认值抬高影响;抬高只为病态情况(模型 hang、集群过载)换取慢成功而非误杀——digest 成功率优先于尾延迟。超时则digest=""、rendered照常。rendered一起丢。开 rewrite 的客户端请求超时抬至 65s(总上限 ~80s,仍在 CC hook 外层 120s 保险丝内);两数值在配置文档成对高亮。统一行为准则:任何 LLM 环节失败都优雅降级、绝不整体失败。
4. 端点兼容与迁移
/recall(新默认即 auto-recall 预设)recall-core逐步改发新参数;对旧服务端保留 400/422 剥参降级(原型已验证);/find回落链保留兜底Deprecationheader 提示,别名拆除(下一 minor)前完成升级recall工具ovCLI/api/v1/search/recall处(另有商业化侧代理服务需同步参数面演进与别名拆除时间表,属外部协调项。)
单 PR 边界:服务端(
routers/search.py重排 +type_quota_recall.py泛化为组装内核、建议更名context_assembler.py+ 有界扩展/recall_rewrite拆件 + 删无界 IntentAnalyzer 路径 +default_search_mode接线 + purpose 配置/超时配置)+ 插件参数面升级 + 中英文档与测试迁移,预估 ~2–3k 行 diff。建议 PR 内按提交分层:组装内核泛化 → search 扩参 → 端点路由处置 → 插件适配 → 文档。规模超限时的安全切出子集:① D4(resources/skills 桶 + purpose)延后 fast-follow;② L3 rewrite 延后(纯增量参数,不影响端点统一落地)。组装内核的实现边界(与 #3330 的
plan_retention()同习语):严格分离「候选产生」(检索命中 / quotas 分桶 / bootstrap manifest 三种 provider 可插拔)与「组装流水线」(去重 → 档位规划 → 读取执行 → 渲染 → stats);组装侧再拆「纯策略规划(无 I/O、无 LLM,凭 payload abstract 长度与 stat 预判制定装配计划)+ I/O 执行」两步——规划器可独立单测,且结构性保证读取纪律(只读大概率装得进预算的候选)。5. 备选方案与否决理由
/context,search/recall 双退役mode="context"保留未来平移可能6. 风险
extra="forbid"。query_expansion默认 auto 的语义微变:带 session 的旧调用从「无界分析」变为「有界 ≤3 + 熔断」——延迟与稳健性改善、扩展广度略减,写入 CHANGELOG。score_threshold守门 + resources/skills 默认封顶 abstract 档;强记忆流客户端可显式 quotas 锁回 memory-only;遥测证实分数系统性偏高再引入来源降权。/find;窗口已从「合入即发生」推迟到拆除节点,期间有Deprecationheader + warning 日志过渡。rewrite=true的服务端推理成本随对话轮数线性增长(单次 prefill ~2k token + decode 300–500 token,见 §3.2 成本账)。缓解:默认关闭、纯 opt-in;query_planner 建议配置小模型;stats.rewrite_usage透传用量供部署方监控核算;必要时客户端按轮次门控触发频率。7. 决策点清单(征求意见)
以下为报告评审阶段已形成倾向的决策点,发出来一并征求社区意见——尤其欢迎对 D8(预算单位)、D11(服务端记账去重)、D13(auto 默认档)的不同视角:
mode:"context"(组合歧义最少)detail auto|abstract|overview|full,对齐 ContextLevel;消灭 bool 与 summary 混称retrieval.purpose_profiles配置覆写recall工具target_urifilter/tags/时间窗先透传(免费红利),target 求交待需求max_tokens(默认 1600);/recall 别名折算;估算器保持启发式(±15%),不引入 tokenizer 依赖rendered输出格式total_message_count轮距;opencode/pi 等零实现受益)mode:"bootstrap",不新增端点detail默认值auto(实测分数窄带、排序区分度弱,广度优先更鲁棒;要贪深显式传full)附录:本提案依据的关键代码事实(main, v0.4.9)
FindRequest/SearchRequest同构性、RecallRequestv1 字段全集:openviking/server/routers/search.py/search退化条件与 IntentAnalyzer 触发分支(无保险丝):openviking/storage/viking_fs.pyopenviking/session/session.pydefault_search_mode未接线:openviking/retrieve/hierarchical_retriever.pyContextLevelL0/L1/L2:openviking/core/context.py;目录 sidecar:storage/queuefs/semantic_sidecar.py;单文件 abstract 仅存索引 payload(~447B):utils/embedding_utils.py/recall档位降级与 summary 双重来源:openviking/retrieve/type_quota_recall.pyopenviking/server/mcp_endpoint.pysdk/python、sdk/go、sdk/typescript、crates/ov_clibot/vikingbot/agent/memory.py_recall_query_plan、retrieve/recall_rewrite.py、compact 渲染器、并行读、超时配置及测试):feat/auto-recall-v2All reactions