Files
mnote/design/07-ai/done/7-8-page-ai-hermes-runtime-bff-next-v1.md
T

23 KiB
Raw Blame History

7-8 [process] 页面 AI Hermes Runtime BFF 下一阶段设计 v1

更新时间:2026-05-15

当前状态:PROCESS。本文承接 7-37-47-57-67-7 已完成的 Hermes 页面内客户端主线,写入对 hermes-web-ui-0.5.18 Chat Runtime 架构的吸收结论。

上位依据:

  • /mnt/Data1T/mnote/ARCHITECTURE.md
  • /mnt/Data1T/mnote/design/01-05-current-priority-overview.md
  • /mnt/Data1T/mnote/design/03-rust-web/process/3-rust-web-long-term-architecture-v1.md
  • /mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md
  • /mnt/Data1T/mnote/design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md
  • /mnt/Data1T/mnote/design/05-editor-mainline/reference-code/hermes-web-ui-0.5.18
  • /mnt/Data1T/mnote/design/07-ai/done/7-3-page-ai-hermes-panel-and-mnote-plugin-v1.md
  • /mnt/Data1T/mnote/design/07-ai/done/7-4-page-ai-hermes-panel-execution-checklist-v1.md
  • /mnt/Data1T/mnote/design/07-ai/done/7-5-hermes-client-proxy-contract-v1.md
  • /mnt/Data1T/mnote/design/07-ai/done/7-6-mnote-hermes-plugin-tool-contract-v1.md
  • /mnt/Data1T/mnote/design/07-ai/done/7-7-page-ai-mini-hermes-control-surface-v1.md

1. 结论

hermes-web-ui-0.5.18 对 mnote 有用,但不能原样照搬。

可吸收的是:

  • Web 端应采用 Browser -> BFF -> Hermes gateway,不是浏览器直接 ACP。
  • BFF 应承担 runtime 管理:active run registry、resume、queue、abort、event normalization、gateway manager、错误规整。
  • 前端只展示 message、tool event、状态和输入,不持有 AI 编排真相。

不可吸收的是:

  • 不把 mnote 的页面 AI 聊天真相改为 Convex、SQLite 或 mnote 自建 chat DB。
  • 不把 Hermes 降级成 /v1/responses store=false 的纯模型执行器。
  • 不让 mnote-web 根据自有数据库长期组装完整 conversation_history

当前冻结方向:

mnote 学 hermes-web-ui 的 BFF/runtime 架构思想,但会话真相继续归 Hermes;mnote 只保存业务事实、audit、artifact、edge、page/body/title/options 结果。


2. 当前实现定位

当前页面 AI 接入是:

Leptos 页面 AI 面板
  -> mnote-web /api/hermes/client/*
  -> Hermes gateway /v1/runs
  -> Hermes gateway /v1/runs/{run_id}/events

同时:

  • profiles / skills / memory 等设置能力由 mnote-web 本地 BFF 通过 Hermes CLI 或 ~/.hermes 文件读写补齐。
  • mnote.* 工具由 mnote Rust runtime 承接,Hermes 只通过 skill/plugin/tool 调用。
  • 页面 AI 不再走旧 /api/ai-agent/run

这已经满足最小闭环,但仍只是“最小 runtime 接入”,缺少 hermes-web-ui 已经具备的完整 Chat Runtime 能力。


3. ACP、gateway、responses 的取舍

3.1 ACP

ACP 适合:

  • VSCode 插件
  • Tauri / Electron 桌面端
  • 本地 IDE host
  • 需要由宿主启动 hermes acp 并通过 stdio JSON-RPC 深度交互的场景

ACP 不适合作为 mnote 页面 AI 第一主链:

  • 浏览器不能直接 spawn hermes acp
  • 若 Web 使用 ACP,仍需 mnote-web 作为 ACP bridge 管理 stdio 子进程。
  • 这会引入长期进程、session、backpressure、并发、恢复和崩溃清理复杂度。
  • 当前 Hermes gateway 已提供 Web 更自然的 /v1/* 与 stream 能力。

结论:

ACP 后续只作为桌面端或高级 host bridge 候选,不作为页面 AI Web 主链。

3.2 /v1/runs

/v1/runs 适合当前 mnote 主线:

  • Hermes 持有 run / session / event 语义。
  • mnote 页面 AI 只是 Hermes client。
  • mnote 不需要长期组装 conversation_history
  • 与“AI 会话归 Hermesmnote 能力归 Rust kernel”一致。

风险:

  • mnote-web 需要补 runtime 状态管理,否则刷新、断线、队列、abort 体验弱。
  • Hermes gateway 的非 /v1/* 管理面不足,需要 BFF 补 profiles/skills/memory/gateway manager。

结论:

短期继续以 /v1/runs 为默认页面 AI 主链,并在 mnote-web 上补 Runtime BFF。

3.3 /v1/responses

hermes-web-ui 的产品化 Chat Runtime 使用 /v1/responses stream=true,典型形态是:

Browser Vue UI
  -> Socket.IO /chat-run
  -> hermes-web-ui server
  -> /v1/responses stream=true
  -> server 从本地 DB 组装 conversation_history
  -> server 把 response.* 事件转成 run/message/tool 事件
  -> server 写入 SQLite session/messages

它适合“自建 Chat Runtime”:

  • 后端自有 session/messages 数据库。
  • 后端自行压缩上下文。
  • 后端自行映射 tool event。
  • store=false 时 Hermes 不再是会话真相。

这与 mnote 当前页面 AI 方向有冲突。除非后续明确改变设计,把“AI 会话真相”从 Hermes 迁到 mnote,否则不能把 /v1/responses store=false + mnote conversation_history 写成默认主链。

允许使用 /v1/responses 的条件:

  • 只作为特定 runtime 实验或 fallback。
  • 必须证明 Hermes session 仍能作为用户可见会话真相,或者另开设计稿明确改变会话真相。
  • 不得把 mnote page aggregate / Convex 页面数据变成聊天消息数据库。

结论:

/v1/responses 先作为参考实现和可评估候选,不作为当前页面 AI 默认主链。


4. 目标架构

下一阶段目标不是改成 ACP,也不是直接改成 /v1/responses,而是在当前 gateway 主线上补一个 mnote-web Runtime BFF

Leptos 页面 AI 面板
  -> mnote-web Hermes Runtime BFF
      - active run registry
      - session/run resume
      - queue
      - abort
      - event normalization
      - gateway/profile/skill/memory BFF
      - trace/audit 串联
  -> Hermes gateway /v1/runs
  -> Hermes session storage
  -> mnote skill/plugin/tool
  -> Rust runtime / kernel

真相边界:

  • HermesAI session、message、run、tool event、usage、model、profile。
  • mnote:页面、树、正文、artifact、edge、projection、audit、权限、idempotency。
  • mnote-web Runtime BFF:运行时连接和事件编排,不保存长期聊天真相。

5. Hermes Web UI 参考索引

参考根目录:

/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/hermes-web-ui-0.5.18

只参考下表,不整站搬运:

参考主题 文件 搜索点 mnote 中吸收什么 mnote 中不采用什么
Socket runtime 主链 packages/server/src/services/hermes/chat-run-socket.ts ChatRunSocketonConnectionresumeSessionhandleRundequeueNextQueuedRunhandleAbort active run registry、resume、queue、abort、前端刷新不丢运行态 不照搬 Socket.IOmnote 可用 SSE / WebSocket / HTTP 状态轮询
/v1/responses 事件映射 packages/server/src/services/hermes/chat-run-socket.ts /v1/responsesapplyResponseStreamEventresponse.output_text.deltaresponse.output_item.doneresponse.completed 学习如何把上游事件标准化为 message.deltatool.startedtool.completedrun.completed 不把 /v1/responses store=false 写成默认主链
后端 session store packages/server/src/db/hermes/session-store.ts HermesSessionRowHermesMessageRowcreateSessionaddMessagegetSessionDetail 理解 Hermes Web UI 如何建完整 chat DB,以及字段形态 不在 mnote 建第二套 chat DB;不把 Convex 当 AI 会话真相
前端 chat store packages/client/src/stores/hermes/chat.ts resumeServerWorkingRunqueueLengthsabortStatecase 'message.delta'case 'tool.started'case 'run.completed' 前端状态机、队列提示、abort UI、tool event 归并 不复用 Vue/Pinia;不把前端 state 当真相
前端 API packages/client/src/api/hermes/chat.ts startRunViaSocketresumeSessionregisterSessionHandlersRunEvent 客户端事件合同与 runtime event 类型 不让浏览器绕过 mnote-web 直连 Hermes
VSCode ACP tool UI design/05-editor-mainline/reference-code/hermes-vscode-main/src/sessionManager.tssrc/protocol.tssrc/chatPanel.tssrc/webview/main.ts tool_calltool_call_updateparseToolCallparseToolCallUpdatecase 'toolCall'data-tool-id 学习按 toolCallId 追加工具行、再用后续 update 原地更新状态的 UI 模型 不采用 ACP 作为 Web 主链;只参考 tool event UI 状态机
gateway 管理 packages/server/src/services/hermes/gateway-manager.tspackages/server/src/services/gateway-bootstrap.ts getUpstreamstartstophealth profile 对应 gateway 的发现、启动、健康检查、错误提示 不让页面 AI 抽屉变成完整 gateway 管理台
proxy handler packages/server/src/routes/hermes/proxy-handler.ts resolveUpstreamSSE_EVENTS_PATHstreamSSE/v1/runs 路由改写、SSE 拦截、upstream 错误规整 不把全量 proxy catch-all 暴露给浏览器
skills 来源 packages/server/src/controllers/hermes/skills.ts .bundled_manifest.hub/lock.json.usage.jsonsourcemodified skill 来源与生成/安装/本地分类 不复制完整 skill 管理台

快速定位命令:

cd /mnt/Data1T/mnote/design/05-editor-mainline/reference-code/hermes-web-ui-0.5.18
rg -n "ChatRunSocket|resumeSession|handleRun|dequeueNextQueuedRun|handleAbort|/v1/responses|applyResponseStreamEvent|response\\.output_text\\.delta|response\\.output_item\\.done|response\\.completed|HermesSessionRow|startRunViaSocket|gateway-manager" packages/server/src packages/client/src

6. RuntimeEvent 合同

mnote-web BFF 对前端暴露的事件应稳定为 mnote.hermes_runtime_event.v1

最小事件集:

{ "event": "run.started", "sessionId": "mnote_doc_1_trace_1", "runId": "run_1", "traceId": "trace_1" }
{ "event": "message.delta", "sessionId": "mnote_doc_1_trace_1", "runId": "run_1", "delta": "当前页", "traceId": "trace_1" }
{ "event": "tool.started", "sessionId": "mnote_doc_1_trace_1", "runId": "run_1", "toolCallId": "call_1", "toolName": "mnote.page.get", "traceId": "trace_1" }
{ "event": "tool.completed", "sessionId": "mnote_doc_1_trace_1", "runId": "run_1", "toolCallId": "call_1", "toolName": "mnote.page.get", "summary": "读取当前页面", "auditId": "audit_1", "traceId": "trace_1" }
{ "event": "run.completed", "sessionId": "mnote_doc_1_trace_1", "runId": "run_1", "output": "总结文本", "usage": { "inputTokens": 0, "outputTokens": 0 }, "traceId": "trace_1" }
{ "event": "run.failed", "sessionId": "mnote_doc_1_trace_1", "runId": "run_1", "code": "hermes_client_upstream_error", "message": "Hermes upstream 返回错误", "traceId": "trace_1" }
{ "event": "abort.started", "sessionId": "mnote_doc_1_trace_1", "runId": "run_1", "traceId": "trace_1" }
{ "event": "abort.completed", "sessionId": "mnote_doc_1_trace_1", "runId": "run_1", "synced": true, "traceId": "trace_1" }

如果上游是 /v1/runs/{run_id}/eventsBFF 只做字段规整;如果后续试验 /v1/responsesBFF 负责把 response.* 映射为上述 RuntimeEvent。前端不直接依赖上游原始事件格式。


7. 下一阶段 checklist

A. Runtime 状态注册表

  • mnote-web 建立内存级 sessionId -> active run state registry。
  • 记录 sessionIdrunIdprofiledocumentIdtraceIdstatusstartedAtlastEventAt
  • registry 只保存运行态和最近事件摘要,不保存完整聊天历史。
  • 服务重启后允许 registry 丢失,但页面必须能从 Hermes session detail 恢复已完成消息。

验收标准:

  • 页面刷新时,若 run 仍在 registry 中,前端能看到 running/tool_calling 状态。
  • 关闭页面再打开,不会把 mnote local storage 当作聊天真相。

B. Resume

  • 增加 POST /api/hermes/client/sessions/{session_id}/resume 或等价同源接口。
  • 返回 Hermes session detail 中的 messages,并附带 registry 中当前运行态。
  • 若 session 不在 registry 中,只返回 Hermes 已持久化状态。

验收标准:

  • run 过程中刷新页面,UI 能恢复 run 状态或明确显示“正在同步 Hermes 状态”。
  • run 完成后刷新页面,消息来自 Hermes session detail / export,不来自 mnote 自建聊天表。

验证证据:

  • cargo test -p mnote-web hermes_client_ -- --nocapture:通过 9 个 Hermes client 单测,覆盖 registry 不保存 messages、tool event 摘要和 /resume 返回 runtime。
  • MNOTE_UI_BASE_URL=http://127.0.0.1:13000 node scripts/task-hermes-page-ai-smoke.js:通过。13000 是本轮临时启动的新 mnote-web,用于避免打断当前 3000;验证后已关闭。
  • 当前 3000 运行的是本轮修改前的旧 mnote-web 二进制,POST /api/hermes/client/sessions/{id}/resume 返回 404;需要重启 3000 后再用同一 smoke 复验主入口。

C. Queue

  • 同一 session 已有 active run 时,后续输入进入后端 queue。
  • queue item 只保存待发送 input、profile、context snapshot 摘要和 trace,不保存长期聊天真相。
  • 当前 run 结束后自动触发下一条。
  • 前端显示 queue length 和可取消项。

验收标准:

  • 连续发送两条消息时,第二条显示 queued,而不是并发打到同一 Hermes session。
  • 取消 queued item 后,不触发 Hermes run。

验证证据:

  • cargo test -p mnote-web hermes_client_ -- --nocapture:通过 12 个 Hermes client 单测,新增覆盖 active run 时第二条入队、queue item 不返回完整 messages/pageContext、后端取消 queued item、终态后用 mock Hermes upstream 自动启动下一条 queued run。
  • MNOTE_UI_BASE_URL=http://127.0.0.1:13000 node scripts/task-hermes-page-ai-queue-smoke.js:通过。验证连续发送两条消息时第二条走 queued 响应,UI 显示队列长度,并能从 Runtime 面板取消 queued item。
  • MNOTE_UI_BASE_URL=http://127.0.0.1:13000 node scripts/task-hermes-page-ai-smoke.js:通过。确认 resume / tool / 完成态基础链路未被 queue 改动破坏。
  • 13000 是本轮临时启动的新 mnote-web,验证后已关闭;当前 3000 仍需重启后复验。

D. Abort

  • abort 由 BFF 统一执行,调用 Hermes /v1/runs/{run_id}/stop 或中断当前 upstream request。
  • abort 前后发出 abort.startedabort.completed
  • abort 后清理 registry 状态,并处理队列后续项。

验收标准:

  • 点击停止后,UI 不再继续追加该 run 的 assistant delta。
  • 若 abort 后有队列项,后端按顺序继续处理或明确要求用户确认。

验证证据:

  • cargo test -p mnote-web hermes_client_ -- --nocapture:通过 13 个 Hermes client 单测,新增覆盖 abort 响应返回 abort.started/abort.completed,并在 abort 后继续处理 queued run。
  • 临时 mnote-webMNOTE_WEB_HERMES_UPSTREAM_URL=http://127.0.0.1:3999 启动后,执行 MNOTE_UI_BASE_URL=http://127.0.0.1:13000 node scripts/task-hermes-page-ai-runtime-controls-smoke.js:通过。验证停止 run 后 UI 状态进入 aborted,并且后续 message.delta 不再追加到聊天区。
  • MNOTE_UI_BASE_URL=http://127.0.0.1:13000 node scripts/task-hermes-page-ai-queue-smoke.jsMNOTE_UI_BASE_URL=http://127.0.0.1:13000 node scripts/task-hermes-page-ai-smoke.js:通过。确认 queue / resume 基础链路未被 abort 改动破坏。
  • 13000 是本轮临时启动的新 mnote-web,验证后已关闭;当前 3000 仍需重启后复验。

E. Event normalization

  • mnote-web 将 Hermes 上游事件规整为 mnote.hermes_runtime_event.v1
  • 前端只消费标准事件,不直接分支处理多套 Hermes 原始事件。
  • tool event 展示独立于 assistant 文本。
  • 当前 UI 已能在聊天区输出 工具 消息并显示工具名,例如 mnote.page.get;下一阶段必须升级为按 toolCallId 合并的 tool event card。
  • tool.started 创建或更新同一个工具卡,显示工具名、运行中状态、参数摘要。
  • tool.completed 原地更新同一个工具卡,显示完成状态、结果摘要、audit/trace id。
  • tool.failed 原地更新同一个工具卡,显示失败状态、错误码和可复制详情。
  • Runtime 页的最近 tool call 摘要继续保留,但不能替代聊天区内联工具卡。

验收标准:

  • message.deltatool.startedtool.completedrun.completedrun.failed 均有 smoke 覆盖。
  • tool.started -> tool.completed 不产生两条互不关联的纯文本工具消息;同一 toolCallId 在 UI 中表现为同一张工具卡状态变化。
  • 工具卡至少展示 toolNametoolCallId、状态、参数摘要、结果摘要、traceId/auditId
  • tool result 不混入正文编辑器,不写入 page body,除非 Hermes 明确调用 mnote.page.save 等写工具。

验证证据:

  • MNOTE_UI_BASE_URL=http://127.0.0.1:13000 node scripts/task-hermes-page-ai-smoke.js:通过。smoke 发送同一 toolCallId=call_smoke_page_gettool.started -> tool.completed,断言聊天区只有 1 张 completed 工具卡;同时发送 toolCallId=call_smoke_page_savetool.started -> tool.failed,断言 failed 工具卡显示 permission_denied
  • MNOTE_UI_BASE_URL=http://127.0.0.1:13000 node scripts/task-hermes-page-ai-runtime-controls-smoke.jsMNOTE_UI_BASE_URL=http://127.0.0.1:13000 node scripts/task-hermes-page-ai-queue-smoke.js:通过。确认工具卡改动未破坏 abort / queue。
  • cargo test -p mnote-web hermes_client_ -- --nocapture:通过 14 个 Hermes client 单测,新增覆盖 assistant_messagetool.failed 等上游事件被规整为 mnote.hermes_runtime_event.v1
  • mnote-web /client/events/{run_id} 输出 event: <canonical>schemaVersion: "mnote.hermes_runtime_event.v1";前端消费路径只处理 message.deltarun.completedrun.failedrun.abortedtool.* 标准事件名。

F. Gateway manager

  • BFF 能检查当前 Hermes gateway health。
  • BFF 能识别 profile 对应 gateway / model 状态。
  • 缺 model/default 或 API key 时,错误提示指向 Hermes 设置,而不是只显示 502。

验收标准:

  • hermes_client_upstream_error 场景能显示具体修复建议,例如“当前 profile 缺 model.default”。
  • profile/skill/settings API 不再错误代理到 Hermes gateway 非 /v1/* 路径。

验证证据:

  • mnote-web 新增 GET /api/hermes/client/gateway/health?profile=<name>,只做只读探测,不启动/停止 gateway;返回 gateway.configured/upstream/status/httpStatusprofile.modelDefault/provider/apiKeyConfiguredsuggestions
  • profile_detail_payload() 与 fallback profiles 会从 Hermes profile config.yaml 读取 model.defaultmodel.provider、provider api_key/key_env 摘要,但不返回 API key 原文。
  • upstream_error()hermes_client_upstream_error 增加 Hermes 设置修复建议;当上游错误正文包含 model.defaultAPI keyprofile/provider 等线索时,错误 message 会指向 Hermes 设置。
  • Runtime 面板增加 gateway/profile 状态区;页面请求失败时前端显示后端 message,不再只显示错误 code。
  • cargo test -p mnote-web hermes_client_ -- --nocapture:通过 17 个 Hermes client 单测,新增覆盖 gateway 未配置、profile 缺 model/API key、hermes_client_upstream_error 错误建议。
  • 临时 mnote-webMNOTE_WEB_HERMES_UPSTREAM_URL=http://127.0.0.1:3999 启动到 127.0.0.1:13000 后,task-hermes-page-ai-smoke.jstask-hermes-page-ai-runtime-controls-smoke.jstask-hermes-page-ai-queue-smoke.js 均通过;验证后已关闭 13000。
  • MNOTE_UI_BASE_URL=http://127.0.0.1:13000 node scripts/task-hermes-page-ai-agent-skill-smoke.js:通过。确认新增 gateway health 请求不影响 agent/profile 选择、SOUL.md 保存、skill toggle 与 run profile 透传。

G. /v1/responses 评估,不默认切换

  • 增加只读 spike 文档或测试,验证当前 Hermes gateway 的 /v1/responses stream=true 事件格式。
  • 明确 /v1/responses 是否能在不迁移会话真相的前提下复用 Hermes session。
  • 若不能保持 Hermes session 真相,则不得进入默认页面 AI 主链。

验收标准:

  • 有明确结论:继续 /v1/runs,或另开设计稿说明为什么改变会话真相。
  • 任何 /v1/responses 实验都不得把 mnote Convex / page aggregate 变成聊天消息真相。

只读 spike 结论:

  • 参考 hermes-web-ui-0.5.18/packages/server/src/services/hermes/chat-run-socket.ts,其 /v1/responses 主链在发送前从后端 DB 组装 conversation_history,随后设置 body.stream = truebody.store = false,再请求 ${upstream}/v1/responses
  • 同一文件的 applyResponseStreamEvent() 表明 responses 事件格式主要是 response.createdresponse.output_text.deltaresponse.output_item.addedresponse.output_item.doneresponse.completedresponse.failed,由上层 BFF 映射为 run.startedmessage.deltatool.startedtool.completedrun.completedrun.failed
  • response.output_item.donefunction_call 会被映射为 tool.startedfunction_call_output 会被映射为 tool.completed。这对 mnote 的 tool card 状态机有参考价值,但不改变主链。
  • 本机只读探测 http://127.0.0.1:3999/health/v1/modelsOPTIONS /v1/responses 时,当前没有 gateway 监听,无法取得 live /v1/responses stream=true 事件样本;因此本轮不把 live event capture 写成验收依据。
  • 基于参考实现,/v1/responses store=false + conversation_history 的顺滑体验依赖上层 BFF/SQLite 自建 chat session 真相。除非后续确认 Hermes gateway 支持把 /v1/responses 直接绑定到 Hermes session storage,否则 mnote 不应把它切成默认主链。

当前决策:

继续 /v1/runs 作为页面 AI 默认主链;/v1/responses 只作为事件标准化与 tool card 的参考实现。若未来要切换,必须另开设计稿证明 Hermes session 仍是 AI 会话真相,或明确批准迁移会话真相。


8. 非目标

  • 不在本阶段引入 ACP 主链。
  • 不在本阶段把页面 AI 主链切到 /v1/responses store=false
  • 不在 mnote 建 Hermes Web UI 那样的 SQLite chat DB。
  • 不把 Convex 作为 AI 会话真相。
  • 不把页面 AI 抽屉扩成完整 Hermes 管理台。
  • 不让 mnote plugin 直接写 Convex;写入仍回到 Rust runtime / kernel。

9. 最终验收口径

本稿完成时,应能同时成立:

  • 页面 AI 仍是 Hermes 页面内客户端。
  • Hermes 仍是 AI session/message/tool event/usage/model/profile 真相。
  • mnote-web 有 Runtime BFF 能力,支持 run resume、queue、abort 和事件标准化。
  • 页面刷新或断线不导致 active run UI 状态丢失;完成后的消息从 Hermes 恢复。
  • mnote 不保存完整聊天历史,只保存 mnote 业务写入结果和 audit。
  • /v1/responses 是否进入主链已有单独评估结论,且没有绕过 Hermes session 真相。

一句话:

补齐 Web Chat Runtime 的工程能力,但不改变“AI 会话归 Hermesmnote 事实归 Rust kernel”的长期边界。