bot/docs/vikingbot-openviking-context-plan.md
该方案可行,但不能直接沿用当前 VikingBot 的 OpenViking 接入方式。
当前 OpenViking 服务端已经具备以下关键能力:
pending_tokens 累积commit(keep_recent_count=...)get_session_context() 返回 latest_archive_overview 与 live messages真正需要改造的是 VikingBot 侧的写路径、读路径、配置与旧压缩链路的退场策略。
把 VikingBot 的长对话压缩链路改为:
pending_tokens 达到阈值时触发 commit。第一版不做以下事情:
ov_server.py 不是稳定 session 模式当前 bot/vikingbot/openviking_mount/ov_server.py 的 commit(...) 会在每次提交时重新创建 session,再把整段消息写入并立刻 commit。
这与目标方案冲突,因为它会导致:
pending_tokens 无法持续累积因此,这个文件必须从“一次性提交器”改造成“稳定 session 访问层”。
loop.py 仍以本地 history 为主当前 bot/vikingbot/agent/loop.py 仍然使用:
session.get_history(...) 作为模型 historylen(session.messages) > self.memory_window 作为本地自动压缩触发条件这意味着现有主链路仍是“本地 session 驱动”,而不是“OpenViking session 驱动”。
context.py 只会拼本地 history当前 bot/vikingbot/agent/context.py 的 prompt 组装仍是:
如果要接入 OpenViking 压缩上下文,必须显式扩展 prompt assembly。
当前 bot/vikingbot/hooks/builtins/openviking_hooks.py 里仍有旧的 message.compact 逻辑:
因此新链路启用后,旧 compact hook 必须被禁用、绕过或显式降级为非主路径。
第一版不建议让 OpenViking 直接替代本地 session 的短期落盘能力。
推荐职责划分:
达到 token/window 阈值并成功 commit 后,本地 session JSONL 会被清空,下一轮通过 OpenViking context 回放已压缩历史。
一个 SessionKey.safe_name() 对应一个稳定的 ov_session_id。
第一版建议直接使用:
ov_session_id = SessionKey.safe_name()不再在每次 commit 时重新 create_session()。
OpenViking 的 get_session_context() 返回的不是纯摘要,而是:
latest_archive_overview因此,VikingBot 读路径不能再把本地 history 整段拼进去。
正确规则应为:
否则会产生重复上下文。
群聊场景中要区分两层身份:
第一版建议:
role_id 记录不要把“当前 sender_id”直接等同于每次请求的 OpenViking user 身份,否则会和现有权限/命名空间语义冲突。
在本地 session metadata 中维护:
{
"openviking": {
"enabled": true,
"session_id": "<SessionKey.safe_name()>",
"last_synced_local_index": 0,
"last_commit_at": null,
"last_pending_tokens": 0,
"last_context_read_at": null,
"last_sync_status": "idle"
}
}
字段说明:
session_id: 稳定的 OpenViking session idlast_synced_local_index: 已成功同步到 OpenViking 的本地消息下标上界last_commit_at: 最近一次 commit 时间last_pending_tokens: 最近一次观测到的 pending_tokenslast_context_read_at: 最近一次读 context 时间last_sync_status: idle / syncing / error其中最关键的是 last_synced_local_index,它决定增量同步与去重是否正确。
写路径触发点位于 bot/vikingbot/agent/loop.py 当前一轮完成、本地 session.add_message(...) + save(...) 之后。
第一版只同步核心对话消息:
第一版不建议写入:
reasoning_content原因:
1. 读取 session.metadata.openviking
2. 若不存在 session_id,则创建/确保稳定 session
3. 根据 last_synced_local_index 找出新增消息
4. 将新增消息转换为 OV message parts
5. 调用 append_messages / batch_add_messages 写入 OV
6. 读取 session meta,获取 pending_tokens
7. 若 pending_tokens >= commit_token_threshold 或消息数达到 memory_window,则触发 commit_session(keep_recent_count=N)
8. 更新本地 metadata 中的同步游标与快照
9. 如果本次实际执行了 commit,则清空本地 session JSONL,并重置本地同步游标但保留稳定 session_id
last_synced_local_index读路径触发点位于 bot/vikingbot/agent/loop.py 当前构建 messages = await message_context.build_messages(...) 之前。
1. 读取本地 metadata.openviking
2. 若未启用或尚未建立稳定 session,则退化为本地 history 模式
3. 调用 get_session_context(session_id, token_budget)
4. 取回 latest_archive_overview + messages
5. 根据 last_synced_local_index,仅补本地未同步 delta
6. 将组装后的 history 传给 ContextBuilder.build_messages(...)
latest_archive_overviewmessages这是第一版最关键的边界条件:
session.get_history(...) 全量拼到 OpenViking context 后面否则很容易出现重复轮次,导致模型重复理解、工具误触发或 token 浪费。
session_context_token_budget 不能被视为最终 prompt 的硬上限。
第一版应采用两层预算:
用于控制调用 get_session_context(session_id, token_budget=...) 的预算目标。
在组装出:
之后,仍需在 VikingBot 侧做一次最终裁剪,确保不会超过 provider 的上下文限制。
否则在大群聊或消息内容较长时,仍可能超出模型窗口。
群聊推荐语义如下:
ov_session_idrole_id建议映射:
role="user", role_id=<真实 sender_id>role="assistant", role_id=<bot/agent id 或默认 assistant 标识>这样可以同时满足:
bot/vikingbot/openviking_mount/ov_server.py把现有一次性 commit(...) 改造成稳定 session 访问层。
建议新增或重构为以下接口:
ensure_session(session_id: str) -> dictappend_messages(session_id: str, messages: list[dict], role_id_resolver=...) -> dictget_session(session_id: str) -> dictget_session_context(session_id: str, token_budget: int) -> dictcommit_session(session_id: str, keep_recent_count: int = 0) -> dict要求:
pending_tokenskeep_recent_countbot/vikingbot/agent/loop.py需要新增两段逻辑:
同时需要关闭或门控当前本地自动 compact 主链路:
session_context_enabled=true 时,不再使用 len(session.messages) > self.memory_window 触发旧压缩主链路/compact 可保留为显式命令,但行为要重新定义,避免与新链路重复bot/vikingbot/agent/context.py需要让 build_messages(...) 支持接收“外部已组装好的 history”。
推荐方式:
loop.py 先准备好 historycontext.py 只负责拼:system prompt、memory、当前 user message这样可以避免把 OpenViking 逻辑硬塞进 ContextBuilder 内部。
bot/vikingbot/session/manager.py当前 metadata merge 已支持嵌套字典,可直接用于持久化:
metadata["openviking"][...]这里只需要补充新字段的读写约定,不需要重新设计存储格式。
bot/vikingbot/config/schema.py在 AgentsConfig 中增加配置项:
session_context_enabled: bool = Falsesession_context_token_budget: int = 12000commit_token_threshold: int = 6000commit_keep_recent_count: int = 10其中:
session_context_enabled:总开关session_context_token_budget:OV context 读取预算commit_token_threshold:触发 commit 的阈值commit_keep_recent_count:commit 后保留的 recent live messages 数量虽然 OpenViking 服务端已支持 keep_recent_count,但当前 Python client wrapper 还没有把这个参数完整透出给 VikingBot。
需要修改:
openviking/async_client.pyopenviking/client/session.py建议补齐以下调用能力:
commit_session(session_id, keep_recent_count=0, telemetry=False)Session.commit(keep_recent_count=0, telemetry=False)Session.commit_async(keep_recent_count=0, telemetry=False)否则 VikingBot 在 AgentsConfig 配置了 commit_keep_recent_count 也无法真正生效。
以下旧链路会与新方案冲突:
bot/vikingbot/hooks/builtins/openviking_hooks.pybot/vikingbot/agent/loop.py 中基于 memory_window 的自动 compact当 session_context_enabled=true 时:
message.compact OpenViking hook 主路径len(session.messages) > self.memory_window 的旧自动压缩/compact 作为显式运维命令,但它应调用新的 stable-session commit 逻辑,而不是旧 fanout 逻辑目标:不动 provider 行为的前提下,让 OpenViking 真正接管长对话压缩。
实施项:
ov_server.py 为稳定 session 访问层loop.py 中接入 after-turn 增量同步loop.py 中接入 before-call OV context 读取openviking 同步状态验收标准:
ov_session_idpending_tokens 可持续增长latest_archive_overview 和 live messages实施项:
commit_keep_recent_count 配置生效验收标准:
实施项:
message.compact hookmemory_window 自动 compact/compact 与新方案的关系验收标准:
如果本地 delta 计算不准确,最容易出现消息重复拼接。
这是第一版必须优先规避的问题。
当前本地 session 会保留部分 provider 特有字段,例如 reasoning_content。
因此第一版推荐保留“本地原始日志 + OV 压缩层”的双层职责,而不是直接完全切换到 OV history。
如果直接把 sender_id 当作每次 OV 请求 user 身份,容易与当前 account/user/agent 权限语义冲突。
应优先通过 role_id 保留真实说话人。
commit 之后的 memory extraction 是后台过程。
下一轮对话可依赖 session context,但不要把“新 memory 必然已可检索”当成同步保证。
把 VikingBot 改成“本地原始日志 + OpenViking 长上下文压缩层”的双层结构:每轮增量写入稳定 OV session,达到 pending_tokens 阈值就 commit,下一轮优先读取 latest_archive_overview + live messages,本地只补尚未同步的 delta,并在启用新链路后退场旧的 compact/fanout 逻辑。