Files
memind/docs/h5-session-architecture-20260706.md
T
john 14a00774d9 feat(h5): web 联网能力、实时查询路由与 session Finish 对齐
- 新增 web 能力并挂载 platform/web(web_search/fetch_url)
- 实时查询强制 web skill,router fallback 与 await session Finish
- Session Broker 覆盖率/指标、stream replay 与相关单测/E2E 脚本

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 16:06:26 +08:00

31 KiB
Raw Blame History

H5 Session 架构改造与开发设计方案

日期: 2026-07-06

状态: Phase 1 已完成;Phase 2 Patch 3/4a/4b/4c 已落地(分支 0706-bug干净,待全量 soak 后合并)。

旧路径 h5-session-gouzao0706.md 保留重定向至本文件。

适用范围:

  • H5 前端聊天主链路。
  • Memind Portal 内的 router、agent-run-gateway、tkmind-proxy、session ownership、SSE stream。
  • goosed 作为 agent runtime 的接入边界。

非目标:

  • 不重写 H5 UI。
  • 不重写 goosed。
  • 不引入新的大型框架或新平台。
  • 不一次性重做完整 replay/event sourcing。
  • 不改变当前生产发布规则和回归守卫。
  • 不重复实施 Memind 2.0 Streaming Agent Runtime 计划 中已落地的 worker/queue/SLO 能力。

0. 结论

本次改造的本质不是重构系统,而是把当前 H5 + Portal + goosed 链路从:

socket/runtime 牵引的会话执行链路

收敛为:

Portal control plane + goosed execution runtime

最重要的架构转折点:

session 不再 socket 化。

Session 必须由 Portal 侧的 store/broker 管理,WebSocket/SSE/HTTP 都只能是 transport,不能成为 session state 的事实源。

当前系统已经具备较好的基础:

  • H5 已经主要通过 POST /agent/runs 提交聊天/agent 请求。
  • 旧的 POST /sessions/:id/reply 已经被拦截为 410 AGENT_RUNS_REQUIRED
  • h5_user_sessions 已经保存 session ownership、origin、goosed_nodegoosed_target
  • h5_agent_runsh5_agent_run_events 已经承担 run lifecycle 和基础事件记录。
  • H5 session event stream 已经走 SSE,并且前端已有 Last-Event-ID 传递。

因此第一阶段不需要“建一个新系统”,而是抽一层很薄的 Session Broker,把已有能力从 user-auth.mjstkmind-proxy.mjsserver.mjsagent-run-gateway.mjs 及若干旁路入口的散落调用中收口。

0.1 与相关文档的关系

文档 关系
Memind 2.0 Streaming Agent Runtime 计划 已落地的 streaming/runtime、worker queue、SLO、tool gateway 等;本文在其之上做 session 控制面收口,不重复 P6–P8 内容
Memind / MindSpace / Goose 解耦推进方案 更广的 Control/Execution + MindSpace 服务化;本文聚焦 H5 session ownership,不涉及 MindSpace 独立部署
MindSpace 发布与聊天 Finish 回归守卫 Patch 4 及 stream finish 相关改动必须遵守
103 Runtime Topology 生产 goosed target 拓扑参考;broker 的 goosed_target pinning 与之对齐

1. 当前结构与主要问题

1.1 当前实际链路

flowchart LR
  H5["H5 useTKMindChat"] --> API["src/api/client.ts"]
  API --> RUN["POST /agent/runs"]
  RUN --> GW["agent-run-gateway.mjs"]
  GW --> ROUTER["chat-intent-router.mjs"]
  GW --> DIRECT["direct-chat-service.mjs"]
  GW --> PROXY["tkmind-proxy.mjs"]
  PROXY --> GOOSE["goosed runtime"]
  H5 --> RUNSSE["GET /agent/runs/:runId/events"]
  H5 --> SESSSE["GET /sessions/:sessionId/events"]
  RUNSSE --> GW
  SESSSE --> PROXY
  PROXY --> GOOSE

这个链路比早期状态更健康: H5 已经不是直接把普通聊天发到 goosed reply path。

但仍有三个核心问题:

  1. session lifecycle、ownership、target mapping 仍散落在多个模块里(见 §5.6 调用点清单)。
  2. gateway 同时处理 run lifecycle、router、direct chat、tool gateway、goosed session 创建,边界偏厚。
  3. SSE 已经存在,但事件语义还没有统一成稳定协议,尤其是 run stream 与 session stream 的终态/恢复语义不完全一致(见 §1.3)。

1.2 当前已有资产

当前不应重复建设的能力:

  • user-auth.mjs
    • registerAgentSession(userId, agentSessionId, goosedTarget)
    • getSessionTarget(agentSessionId)
    • ownsSession(userId, agentSessionId)
    • unregisterAgentSession(userId, agentSessionId)
  • db.mjs
    • 自动补齐 h5_user_sessions.goosed_node
    • 自动补齐 h5_user_sessions.goosed_target
    • 自动补齐 h5_user_sessions.origin
    • 注意: schema.sqlh5_user_sessions 基表可能滞后于 runtime migrationdb.mjs 启动迁移为准Patch 1 实施时建议同步更新 schema.sql 注释或列定义。
  • tkmind-proxy.mjs
    • goosed target 选择与 session target 解析。
    • /sessions/:id/events SSE proxy。
    • stream started/ended metrics。
    • reconcileAgentSession 调用(resume 路径)。
  • session-snapshot 相关(sessionSnapshotService
    • direct chat / portal snapshot 缓存。
    • GET /sessions/:id 在 hint 匹配时的 DB snapshot 快路径。
    • gateway 执行前 transcript persist。
    • 不是 ownership 事实源;与 broker 并列存在(见 §5.6)。
  • agent-run-gateway.mjs
    • h5_agent_runs row。
    • h5_agent_run_events 基础事件。
    • queued/running/retryable/succeeded/failed 状态。
    • worker heartbeat 和 stale run recovery。

这些能力应该被收口和命名,不应被复制。

1.3 H5 双 SSE 通道现状

H5useTKMindChat.ts + src/api/client.ts)在 agent 路径上同时订阅两条 SSE/事件通道:

通道 端点 职责 终态信号
Run stream GET /agent/runs/:runId/events run 排队/执行状态、sessionId 回填、direct chat 触发 snapshot 轮询 h5_agent_runs.statussucceeded / failed
Session stream GET /sessions/:sessionId/events assistant token、tool call、finish 等 goosed 运行时事件 goosed finish / error 等 terminal event

典型时序:

POST /agent/runs
  → subscribeAgentRunEvents(runId)     # 等待 run 终态、拿 sessionId
  → subscribeSessionEvents(sessionId)  # 接收流式内容(非 direct chat
  → finish 后 syncSessionMessages       # merge,禁止盲覆盖

这解释了 §1.1 第三点: run 终态与 session 终态不是同一事件direct chat 甚至主要依赖 run 成功 + snapshot poll,而非 session SSE。

Patch 4 必须明确:

  • run stream: 保持现有 DB 驱动 polling/SSE,第一阶段不做 replay。
  • session stream: 固化 delta/control/terminal 语义、Last-Event-ID、finish 后 idle/sync。
  • 禁止 在 Patch 4 中合并两条通道或改变 H5 订阅顺序。

2. 目标架构

flowchart LR
  H5["H5"] --> PORTAL["Portal Control Plane"]
  PORTAL --> ROUTER["Router\n decision only"]
  PORTAL --> BROKER["Session Broker\n ownership + mapping"]
  PORTAL --> GATEWAY["Agent Gateway\n run lifecycle"]
  PORTAL --> STREAM["SSE Stream Module\n logical, in tkmind-proxy"]
  PORTAL --> SNAP["Session Snapshot\n conversation cache"]
  GATEWAY --> PROXY["goosed Proxy Adapter"]
  STREAM --> PROXY
  PROXY --> GOOSE["goosed\n execution runtime"]
  BROKER --> DB["MySQL\n h5_user_sessions"]
  GATEWAY --> RUNDB["MySQL\n h5_agent_runs/events"]
  SNAP --> SNAPSTORE["Redis / in-memory\n not ownership"]

说明:

  • SSE Stream Module 是逻辑模块,当前与 tkmind-proxy.mjsserver.mjs 路由同进程部署,不是独立服务。
  • Session Snapshot 缓存 conversation,与 Broker 分工不同,不可被 broker 吸收。

边界原则:

可以做 禁止做
Router 判断 chat/agentreuse/newsse/ws 执行工具、写 memory、创建 session
Session Broker ownership、target mapping、origin/timestamps message history、tool state、execution state、memory
Session Snapshot conversation 缓存、merge 输入、direct chat snapshot ownership、goosed target、计费
Agent Gateway run lifecycle、排队、heartbeat、调用 broker/proxy 直接持有 session mapping、绕过 proxy 打 goosed
Proxy Adapter goosed HTTP/SSE transport、headers、abort、backpressure、reconcileAgentSession 用户业务决策、router 决策
goosed runtime session、tool execution、event emission H5 用户归属、计费、memory policy、业务路由

3. 协议分层

3.1 主链路

H5 -> Portal
  POST /agent/runs

H5 <- Portal
  GET /agent/runs/:runId/events
  GET /sessions/:sessionId/events

Portal -> goosed
  POST /agent/start
  POST /sessions/:sessionId/reply
  GET  /sessions/:sessionId/events
  GET  /sessions/:sessionId

说明:

  • H5 不直接调用 goosed。
  • H5 普通聊天不走 WebSocket。
  • WebSocket 只允许用于真正双向交互场景,例如 terminal、远程 shell、实时协作,不作为 H5 chat/session state 的事实源。
  • SSE 是 H5 chat/agent stream 的主通道。

3.2 SSE 事件分类

第一阶段只做协议语义固化,不做完整 event sourcing。

统一把事件归为三类:

类别 含义 例子
delta 内容增量 assistant token、message chunk
control 状态变化 active request、tool call、tool confirmation、balance、heartbeat
terminal 结束态 finish、failed、canceled、error

注意:

  • h5_agent_runs.status 第一阶段不改枚举,仍保持 queued/running/retryable/succeeded/failed
  • streaming/tool_call/completed/canceled 先作为 event 语义,不直接写入 DB status。
  • Last-Event-ID 在 session stream 上继续保留;run stream 的 replay 后续再做,不在第一刀完成。
  • v0 策略: taxonomy 先在 Portal 侧文档化 + 单测断言映射关系,不改变 goosed 原始 event name 的 wire formatH5 仍按现有 event type 解析,仅在注释/测试层标注 taxonomy。

3.3 SSE event type → taxonomy 映射(v0 参考)

以下为 Patch 4 测试与文档用的语义映射,不要求 goosed 改协议:

来源 典型 event / 信号 taxonomy 备注
session SSE message / content chunk delta assistant token 流
session SSE tool call / confirmation / balance control 不改变 active request 终态
session SSE finish terminal 必须触发前端 idle + syncSessionMessages merge
session SSE error terminal headers sent 后走 SSE frame,不走 JSON
run SSE status: running control DB 驱动
run SSE status: succeeded / failed terminal 关闭 run 订阅;agent 路径再依赖 session finish

4. Router 改造设计

4.1 目标

Router 从单纯 classifier 收敛为轻量 decision output,但不升级成复杂 decision engine。

4.2 输出结构

新增统一输出字段:

{
  "route": "chat",
  "mode": "sse",
  "session_hint": "reuse",
  "flags": []
}

字段定义:

字段 允许值 含义
route chat / agent 走直接聊天还是 agent 执行
mode sse / ws 事件传输协议建议;H5 chat 固定消费 sse
session_hint reuse / new 倾向复用当前 session 还是新建
flags string array 受控扩展位,只放风险/能力标签,不放执行计划

兼容要求:

  • 旧值 direct_chat 等价于 chat
  • 旧值 agent_orchestration 等价于 agent
  • 旧字段 suggestedSkillagentBrief 第一阶段可以继续保留给现有逻辑使用,但不能扩展成多步 plan。

Router 可以读取上下文和 memory summary 做判断,但禁止:

  • 创建 session。
  • 写 memory。
  • 执行 tool。
  • 选择具体 goosed target。
  • 输出多步执行计划。

4.3 字段消费方与语义

字段 消费者 第一阶段行为
route agent-run-gateway.mjs 与现有 direct_chat / agent_orchestration 等价映射
mode 暂无 H5 消费 H5 chat 始终 SSEws 仅预留给 terminal/shell 等非 chat 场景,router 对 H5 chat 默认输出 sse
session_hint agent-run-gateway.mjs 建议性 hint,不 override 硬规则(见下)
flags gateway / 日志 能力标签,不触发执行

session_hint 与 gateway 硬规则(hint 不能 override:

session_id 为空           → 必须新建 session(无论 hint
session_id 无效/无 ownership → 403 或新建(沿用现有 gateway 行为)
direct chat 升级 deep reasoning → 清空 sessionId,强制 goosed starthint reuse 失效)
force_deep_reasoning flag     → 同上
tool gateway / code run       → 可能无 goosed sessionagent_session_id NULL),broker 不适用
isDirectChatSessionId         → 不走 goosed session stream

第一阶段 H5 不读取 router normalized 字段;仅 gateway 内部使用,便于后续扩展。

4.4 flags 约束

允许的 flags 示例:

memory_recall
selected_skill
force_deep_reasoning
code_task
long_running

不允许的 flags:

run_tool_x
write_file_y
use_worker_2
bill_as_x
inject_memory_y

5. Session Broker 设计

5.1 目标

Session Broker 是一个薄 facade,不是第二套 session store。

它只负责:

  • session ownership。
  • session origin。
  • session 与 goosed target 的 mapping。
  • session lifecycle timestamp。
  • session 删除/解绑。

它不负责:

  • message history。
  • tool state。
  • execution state。
  • memory。
  • SSE replay。
  • billing。
  • provider selection。

5.2 与 Session Snapshot、session-reconcile 的边界

模块 职责 与 Broker 关系
Session Broker h5_user_sessions ownership + target 事实源
Session Snapshot conversation 缓存、direct chat snapshot broker 不管内容;snapshot 不得替代 ownership 校验
session-reconcile.mjs goosed session resume 时 working_dir 等字段对齐 proxy adapter 在 transport 层调用;broker 不封装 reconcile 逻辑

回归约束: GET /sessions/:id snapshot 缓存仅在 hint_mc hint_ua 均匹配时命中(见 regression guard)。

5.3 建议文件

session-broker.mjs
session-broker.test.mjs

5.4 API v0

export function createSessionBroker({ userAuth, tkmindProxy = null }) {
  return {
    async validateOwnership(userId, sessionId) {},
    async registerSession({ userId, sessionId, target, origin = 'h5' }) {},
    async unregisterSession({ userId, sessionId }) {},
    async resolveSession(sessionId) {},
    async resolveSessionTarget(sessionId) {},
  };
}

第一阶段不建议让 broker 直接承接所有 goosed start 行为。更稳妥的顺序是:

  1. v0 只包现有 userAuth session 函数。
  2. tkmind-proxy.mjs 内部改为通过 broker 记录/读取 target。
  3. agent-run-gateway.mjs 再通过 broker 做 ownership/session resolution。
  4. 旁路入口(§5.6)逐步改走 broker。

5.5 direct chat 兼容

当前 direct chat session 使用 h5direct_ 前缀,并通过:

registerAgentSession(userId, activeSessionId, 'h5-direct')

写入 session 表。

Broker 必须把 h5-direct 当成合法 target/origin 信息处理,不能强行要求所有 session 都对应 goosed URL。

5.6 WeChat 兼容

h5_user_sessions.origin 已有 h5/wechat 语义。Broker v0 必须保留 origin,不要把 WeChat dedicated session 当成普通 H5 session 清理或旋转。

5.7 Session ownership 调用点清单(Patch 2 必须覆盖)

以下模块当前直接调用 userAuth.registerAgentSession / ownsSession / getSessionTargetPatch 2 需全部纳入 broker 收口计划:

模块 典型调用 Patch 批次
tkmind-proxy.mjs register / getSessionTarget / ownsSession 2a
agent-run-routes.mjs ownsSessionPOST /agent/runs 2a
agent-run-gateway.mjs 间接 via tkmindProxy start 2b
direct-chat-service.mjs register h5-direct 2b
server.mjs 多处 ownsSessionsessions CRUD、snapshot、delete 等) 2c
wechat-mp.mjs registerAgentSession 2c
mindspace-page-edit-session.mjs ownsSession / registerAgentSession 2c
mindspace-agent-runner.mjs registerAgentSession 2c
mindspace-conversation-package-routes.mjs ownsSession 2c

Patch 2 完成定义(量化):

# 除 session-broker.mjs 外,不应再直接调用 userAuth.registerAgentSession
rg 'userAuth\.registerAgentSession' --glob '*.mjs' | rg -v 'session-broker\.mjs|user-auth\.mjs|\.test\.mjs'

# ownsSession 同理(测试 mock 除外)
rg 'userAuth\.ownsSession' --glob '*.mjs' | rg -v 'session-broker\.mjs|user-auth\.mjs|\.test\.mjs'

broker 未覆盖 tool gateway / code run 的 无 session 路径: 这类 run 的 agent_session_id 可为 NULL,不经过 broker。

6. Agent Gateway 改造设计

6.1 当前边界

当前 agent-run-gateway.mjs 实际承担:

  • run 创建。
  • request idempotency。
  • router 调用。
  • direct chat 分支。
  • tool gateway 分支。
  • goosed session 创建。
  • submit reply。
  • heartbeat。
  • stale recovery。

因此第一阶段不能按理想终态一次性“去 orchestration 化”,否则改动会明显变大。

6.2 第一阶段只抽 session 相关逻辑

第一阶段改造目标:

  • 保留 run queue、heartbeat、stale recovery。
  • 保留 direct chat 和 tool gateway 分支。
  • 把 session ownership、session target mapping、session register/unregister 的调用收口到 broker。

建议变化:

agent-run-gateway
  before: direct userAuth/tkmindProxy session calls
  after:  sessionBroker + tkmindProxy adapter

不要在第一阶段做:

  • 删除 direct chat 分支。
  • 重写 retry policy。
  • 重写 stale recovery。
  • 重写 run status enum。
  • 重写 SSE run event handler。

6.3 Gateway 最终边界

长期目标:

Gateway = run lifecycle
Broker  = session lifecycle
Proxy   = runtime transport
Router  = decision only

但实施必须分阶段完成。

7. Proxy / goosed 边界设计

7.1 硬边界(H5 chat 主链路)

H5 chat / agent 主链路上,Portal 到 goosed 的调用必须通过 tkmind-proxy.mjs 或其拆分后的 proxy adapter。

禁止新增:

server.mjs -> direct fetch goosed          # H5 chat 路径
agent-run-gateway.mjs -> direct fetch goosed
chat-intent-router.mjs -> direct fetch goosed
H5 -> direct goosed

7.2 已知例外(MindSpace 专用路径)

以下模块当前通过 apiTarget 直连 goosed不在 H5 chat 主链路内。Patch 5 静态检查须显式 allowlist,避免误报:

模块 用途 Patch 5 策略
mindspace-agent-runner.mjs MindSpace agent 任务 保留独立 adapter;长期可迁入 proxy,第一阶段 document + allowlist
mindspace-page-edit-session.mjs 页面编辑 session 同上
scripts/* 运维脚本 本地调试/fix 排除在 prod boundary check 外

原则: 新增 goosed 调用必须走 proxy;存量 MindSpace 路径记录在案,不阻塞 Patch 1–4。

7.3 goosed 保留能力

goosed 继续负责:

  • /agent/start
  • /sessions/:sessionId/reply
  • /sessions/:sessionId/events
  • /sessions/:sessionId
  • tool execution
  • runtime event emission

goosed 不新增负责:

  • H5 user ownership。
  • 余额/计费。
  • router 结果。
  • memory policy。
  • session broker 事实源。

7.4 target mapping

当前 goosed_target 已经用于 session pinning。Broker 不应替代这个字段,而应封装它。

目标行为:

new session:
  proxy pick target
  goosed start returns session id
  broker records session_id -> target

existing session:
  broker resolves session_id -> target
  proxy sends reply/events to same target

8. SSE 设计

8.1 已有能力

已有能力:

  • H5 session events 使用 fetch stream。
  • 前端维护 lastEventId 并传 Last-Event-ID
  • Portal proxy 透传 Last-Event-ID 到 goosed。
  • Portal proxy 已处理 keepalive、abort、backpressure、billing transform、finish 后 snapshot refresh。

这些不能被第一阶段改造破坏。

8.2 第一阶段目标

只做语义固化:

  • 明确事件分类: delta/control/terminal(见 §3.3 映射表)。
  • 明确 session stream terminal event 必须关闭当前 active request,并走 syncSessionMessages mergeuseTKMindChat.ts)。
  • 明确 run stream terminalsucceeded/failed)关闭 run 订阅,不替代 session finish 同步。
  • 错误统一为 SSE event: error,不要在 header sent 后再走 JSON error。
  • GET /agent/runs/:runId/events 保持现有兼容,不强行改成完整 replay。

8.3 后续目标

第二阶段再考虑:

  • run event id。
  • run event replay。
  • session-level replay cursor。
  • reconnect 后从 DB 补 terminal state。

9. Run State Model

9.1 现状

DB status 当前保持:

queued
running
retryable
succeeded
failed

这些状态已经被 gateway、worker、status API、测试和前端使用。

9.2 目标语义

逻辑状态可以扩展为:

created
queued
running
streaming
tool_call
completed
failed
canceled

但第一阶段不改 DB enum,只做映射:

逻辑状态 第一阶段承载方式
created run row insert 前后
queued h5_agent_runs.status = queued
running h5_agent_runs.status = running
streaming session SSE control event
tool_call session SSE control event
completed succeeded + terminal event
failed failed + terminal/error event
canceled 后续新增,不在第一阶段强推

9.3 Tool gateway / code run 说明

tool gateway 与 code run 路径可能:

  • 不创建 goosed sessionh5_agent_runs.agent_session_id 为 NULL)。
  • 由 external worker / Aider / OpenHands 执行。

这类 run 不经过 Session Broker 的 target mappinggateway 仍负责 run lifecycle。避免在 broker 设计中假设“每个 run 必有 agent session”。

10. 5 个 Patch 的实施版本

Patch 1: Session Broker facade

目标:

  • 新增 session-broker.mjs
  • 单测覆盖 ownership、target mapping、direct chat target、legacy node fallback。
  • 不改变行为。

改动文件:

session-broker.mjs
session-broker.test.mjs

验收:

  • 所有现有 session 读写仍通过同一张 h5_user_sessions
  • 不新增 session cache。
  • 不新增 message/tool/memory 字段。
  • schema.sqldb.mjs 列定义对齐(或文档注明以 migration 为准)。

Patch 2: Proxy、Gateway 与旁路入口接入 Broker

目标:

  • tkmind-proxy.mjs 中 session register/resolve 通过 broker facade。
  • agent-run-routes.mjs 中 ownership 校验通过 broker facade。
  • agent-run-gateway.mjs 只在 session 创建/复用处接 broker,不动 direct/tool/retry。
  • direct-chat-service.mjsserver.mjswechat-mp.mjs 等 §5.7 清单模块改走 broker。

改动文件:

# 2a
tkmind-proxy.mjs
agent-run-routes.mjs

# 2b
agent-run-gateway.mjs
direct-chat-service.mjs

# 2c
server.mjs
wechat-mp.mjs
mindspace-page-edit-session.mjs
mindspace-agent-runner.mjs
mindspace-conversation-package-routes.mjs

验收:

  • POST /agent/runs 行为不变。
  • GET /sessions/:sessionId/events 行为不变。
  • direct chat session 仍能创建和读取 snapshot。
  • goosed target pinning 仍正确。
  • §5.7 量化 grep 检查通过(测试 mock 除外)。

Feature flag(建议):

MEMIND_SESSION_BROKER_ENABLED=1   # 默认 off → onoff 时 fallback 到 userAuth 直调

Patch 3: Router 输出收敛

目标:

  • 在旧 router classification 上新增 normalized decision。
  • 输出 route/mode/session_hint/flags
  • 旧字段继续兼容。

改动文件:

chat-intent-router.mjs
chat-intent-router.test.mjs
agent-run-gateway.mjs

验收:

  • 旧 direct chat / agent orchestration 判断不变。
  • Router 不执行 tool。
  • Router 不创建 session。
  • Router 不写 memory。
  • H5 chat 场景 mode 默认 ssesession_hint 不 override gateway 硬规则(§4.3)。

Feature flag(建议):

MEMIND_ROUTER_NORMALIZED_DECISION=1  # shadow: 只打日志不写行为;on: gateway 读取新字段

Patch 4: SSE event contract v0

目标:

  • 文档化并测试 delta/control/terminal 分类(§3.3)。
  • 保持现有 stream wire format 兼容。
  • 修正 header-sent 后的错误输出,继续走 SSE error frame。

改动文件:

agent-run-routes.mjs
tkmind-proxy.mjs
src/api/client.ts          # 仅注释/常量;尽量不改解析逻辑
src/hooks/useTKMindChat.ts # 若动 finish/sync 逻辑需跑 regression guard
相关测试

验收:

  • session stream 断开重连不退化。
  • Last-Event-ID 继续透传。
  • session terminal event 后前端能稳定 idle/syncmerge,非盲覆盖)。
  • run stream terminal 与 session finish 职责分离(§1.3)。
  • 不要求第一阶段完成完整 replay。

Patch 5: goosed 入口硬边界

目标:

  • 明确 H5 chat 主链路上 Portal → goosed 的唯一入口是 proxy adapter。
  • 增加测试或静态检查(scripts/check-goosed-proxy-boundary.mjs 或 eslint),防止新增绕过 proxy 的 goosed fetch。
  • 对 §7.2 例外模块做 allowlist。
  • 保留 goosed API,不改 goosed core。

改动文件:

tkmind-proxy.mjs
server.mjs
scripts/check-goosed-proxy-boundary.mjs   # 新增
tests

验收:

  • H5 不能直接访问 goosed。
  • gateway/router 不直接 fetch goosed。
  • 旧 reply path 继续返回 410 AGENT_RUNS_REQUIRED
  • MindSpace 例外模块在 allowlist 内,检查脚本通过。

11. 上线顺序

Phase 1: 安全收口

  1. Patch 1: Broker facade,无行为变化。
  2. Patch 2a: proxy + agent-run-routes 改走 brokerMEMIND_SESSION_BROKER_ENABLED 可回退。
  3. Patch 2b: gateway + direct-chat-service 接 broker。
  4. Patch 2c: server / wechat / mindspace 旁路入口接 broker。

Phase 2: 协议与 router 收敛

  1. Patch 3: router 输出新增字段,旧字段兼容;可先 shadow 日志。
  2. Patch 4a: SSE event taxonomy 和 terminal 行为测试。

Phase 3: 边界加固

  1. Patch 5: goosed proxy 唯一入口约束 + allowlist。
  2. Patch 4b: run stream replay / event id 评估,不在第一阶段强行上线。

12. 风险与控制

风险 等级 控制方式
Broker 变成第二套 session store 只包 h5_user_sessions,禁止 cache/message/tool/memory
Gateway 一次性重构过大 第一阶段只抽 session,保留 direct/tool/retry
Patch 2 漏改旁路入口 §5.7 清单 + grep 量化验收
Direct Chat 被误认为 goosed session 保留 h5direct_h5-direct target
WeChat dedicated session 被误清理 Broker 保留 origin,不统一 rotate
SSE 固化变成 event-sourcing 重构 v0 只做 taxonomy/terminal,不改 wire format
Run/session 双通道语义混淆 §1.3 明确分工;Patch 4 禁止合并通道
goosed target list 扩缩导致 session 路由错乱 继续以 goosed_target 为优先,legacy node fallback
MindSpace Finish 同步被移动错层 Finish 后 public HTML/materialize 逻辑仍留在 stream finish 处理附近
旧接口兼容破坏 保持 /agent/runs 返回结构、旧 reply path 410
Snapshot 缓存误命中导致对话清空 保留 hint_mc + hint_ua 双 hint 规则

13. 验收清单

13.1 单测

建议新增/补充:

session-broker.test.mjs
chat-intent-router.test.mjs
agent-run-routes.test.mjs
tkmind-proxy.test.mjs
direct-chat-service.test.mjs

重点断言:

  • ownership 校验失败返回 403。
  • direct chat target h5-direct 可解析且不会走 goosed URL。
  • goosed_target 优先于 legacy goosed_node
  • router 新字段默认值稳定;session_hint 不 override 空 session_id。
  • /sessions/:id/reply 仍返回 410。
  • session event error 在 headers sent 后走 SSE frame。
  • run terminal 与 session finish 职责分离(§1.3)。

13.2 回归守卫

如果修改触及以下路径,必须执行对应 guard:

npm run verify:mindspace-publish-guards
npm run verify:mindspace-publish-guards:full
npm run verify:mindspace-page-sync-guards
npm run verify:chat-finish-sync          # Patch 4 触及 useTKMindChat / client.ts 时必跑

受保护路径包括:

  • src/hooks/useTKMindChat.ts
  • chat-finish-sync.mjs
  • mindspace-public-finish-sync.mjs
  • conversation-display.mjs
  • server.mjs
  • tkmind-proxy.mjs

13.3 发布前验证

发布前必须:

bash scripts/check-release-ready.sh

并按当前仓库发布规则执行完整 main 打包发布,不允许从脏工作区或单修复散包发布。

13.4 可观测性(建议,非阻塞第一阶段)

broker/proxy 接入后可补充 metrics 或 structured log:

session_broker.resolve_target.miss     # goosed_target 空且 node fallback
session_broker.ownership.denied        # 403 计数
session_broker.register.duplicate      # 重复 register
goosed_proxy.stream.started / ended    # 已有,保持

用于验证「session 不再 socket 化」后排障是否按 broker → proxy → goosed 分层定位。

14. 回滚策略

Patch 1 回滚

删除 broker facade 和测试即可,无数据迁移。

Patch 2 回滚

设置 MEMIND_SESSION_BROKER_ENABLED=0,调用切回 userAuth 原函数。底层仍是同一张表,不需要 DB 回滚。

Patch 3 回滚

设置 MEMIND_ROUTER_NORMALIZED_DECISION=0,继续使用旧 direct_chat/agent_orchestration

Patch 4 回滚

保留现有 SSE wire format;如果新 event taxonomy 影响前端,先关闭新解析逻辑,仍按旧 event type 处理。

Patch 5 回滚

恢复旧 proxy 调用路径或禁用 boundary check script,但必须保留旧 reply path 410,避免重新打开已修复的直接 reply 风险。

15. 实施禁区

第一阶段明确禁止:

  • 新建第二套 session 表。
  • 把 message history 放进 broker。
  • 把 tool state 放进 broker。
  • 把 memory 选择逻辑放进 broker。
  • 把 session snapshot 合并进 broker。
  • 把 router 改成多步 planner。
  • 把 gateway 的 direct chat/tool gateway/retry 一次性拆掉。
  • 合并 run stream 与 session stream 为单通道。
  • 修改 goosed core。
  • 修改 H5 UIPatch 4 尽量避免改 useTKMindChat.ts 行为)。
  • 修改生产拓扑或 goosed 容器部署方式。

16. 预期解决的问题

执行后能解决:

  • session 不再依赖某条 socket/stream 连接。
  • session ownership 和 runtime target 有统一入口。
  • 多 goosed target 下 session pinning 更清晰。
  • H5 / Portal / goosed 协议边界更稳定。
  • router 不再向执行规划膨胀。
  • gateway 的 run lifecycle 与 session lifecycle 逐步分离。
  • 生产排障能按 router、broker、gateway、proxy、goosed、SSE 分层定位。

不会直接解决:

  • 模型回答质量。
  • 单次推理速度。
  • 工具本身执行失败。
  • 已污染老 session 的自动修复。
  • 完整 run/session replay。
  • memory 内容质量。

这些属于后续 P2/P3 稳定性增强,不应塞进第一阶段。

17. 最终判断标准

改造是否成功,看三点:

  1. session 不再依赖 WebSocket/SSE 连接状态。
  2. goosed 不再承担 H5 用户、计费、router、memory 的业务决策。
  3. router 不输出执行步骤,只输出受控 decision。

补充量化验收:

  1. §5.7 调用点清单全部经 broker,grep 检查通过。
  2. H5 双 SSE 通道行为与 Patch 前一致(含 direct chat、finish merge、Last-Event-ID)。

只要以上成立,系统就从 socket-driven agent system 进入 event-driven control-plane system 的正确轨道。