20 KiB
20 KiB
Memind 2.0 Streaming Agent Runtime 改造计划
更新时间: 2026-07-02
执行状态:
- P0 StreamController v1: 已完成代码落地、语法检查、Portal kickstart 和基础健康验证。
- P1 Gateway SSE 配置: 已完成,
mm.tkmind.cn接入 Portal,SSE route 已禁缓冲。 - P2 Redis Runtime State + Router v1: 已完成可回退版本并启用 Redis。
- P3 Observability: 已完成第一版,只读 runtime 状态接口已上线。
- P4 Tool Gateway v1: 已完成第一步,普通用户默认不再暴露 Aider/OpenHands,显式用户白名单保留。
- P5 Worker Pool 运维化: 已完成第一步,Redis Router 支持 worker drain。
- 生产同步分支: 已从远程
origin/main新建干净副本和分支memind-streaming-runtime-20260702,用于远程开发机后续直接拉取。 - P3.5/P5.5 生产化补强: 进行中,先完成生产数据库和
MindSpace备份,再增加 runtime metrics、健康检查脚本和 drain 运维脚本。
目标
把当前 Memind H5 + multi-goosed 架构升级为 streaming-first agent runtime:
- 易扩展: goosed worker pool 横向扩展,调度层按压力分配新会话。
- 低延迟: SSE 首 token 优先,代理层禁缓冲、及时 flush、处理 backpressure。
- 高稳定: 流式链路、工具执行、worker 状态、持久化状态分层隔离。
当前基线
- Memind Portal:
/Users/john/Project/Memind/server.mjs,本机:8081。 - H5 public base:
https://mm.tkmind.cn;m.tkmind.cn暂时不再作为 H5 public base。 - goosed worker pool: Docker/Colima 内
goosed-prod-1..4,宿主机18006..18009,当前健康。 - H5 上游配置:
TKMIND_API_TARGETS=https://127.0.0.1:18006,...,18009。 - 当前已有 session affinity: start 时分配 worker,后续 reply/events 回到同一 worker。
- imgproxy: 原生
127.0.0.1:20082,兼容代理10.10.0.2:20081。 - 风险点: SSE 代理尚未完整处理
flushHeaders、X-Accel-Buffering、客户端断开 abort、写入 backpressure 和统一 pipeline 收尾。
目标架构
flowchart LR
U["H5 / WeChat / Web"] --> E["Edge Gateway (Nginx / Envoy)"]
E --> S["Stream Controller (Memind Portal v2)"]
S --> R["Router / Scheduler"]
R <--> Redis["Redis Runtime State"]
S <--> Redis
R --> G1["goosed-prod-1"]
R --> G2["goosed-prod-2"]
R --> G3["goosed-prod-3"]
R --> G4["goosed-prod-4"]
G1 --> T["Tool Gateway"]
G2 --> T
G3 --> T
G4 --> T
T --> A["Aider Service"]
T --> O["OpenHands Service"]
S --> PG["PostgreSQL / MySQL Persistence"]
分层原则
- Control Plane: Redis + Router,负责 worker 心跳、active stream、错误率、首 token 延迟、session worker pointer。
- Stream Data Plane: Memind Portal StreamController + goosed worker,负责低延迟流式传输。
- Persistence: 现有数据库只保存最终状态、会话历史、计费、发布记录,不保存高频 runtime state。
- Worker Affinity: goosed 尽量业务无状态,但 session 运行期必须 worker-affine;已存在 session 优先回原 worker。
- Tool Isolation: Aider/OpenHands 从默认全开逐步改为按任务启用、队列化、限流、超时和熔断。
阶段计划
P0: StreamController v1 加固
目标: 不引入新基础设施,先把现有 H5 SSE 链路做稳。
任务:
- 为
/api/sessions/:sessionId/events增加 SSE 专用响应头:Content-Type: text/event-stream; charset=utf-8Cache-Control: no-cache, no-transformConnection: keep-aliveX-Accel-Buffering: no
- 在响应开始后调用
flushHeaders()。 - 使用
AbortController将客户端断开传递到 goosed upstream fetch。 - 用 backpressure-aware writable sink 替代裸
res.write()。 - 用
pipeline()统一处理 source、sanitizer、billing、client sink 的关闭和错误。 - 保留 keepalive 和 billing balance event。
验收:
node --check /Users/john/Project/Memind/server.mjs通过。- 本机
http://127.0.0.1:8081/api/status仍返回ok。 - 四个
https://127.0.0.1:18006..18009/status仍返回ok。
P1: Gateway SSE 配置
目标: mm.tkmind.cn 新入口和本机入口不缓冲 SSE;后续 H5 不再走 105 nginx 反向隧道转发。
任务:
- 对
/api/sessions/*/events、/api/agent/runs/*/events配置:proxy_buffering offproxy_cache offgzip offproxy_read_timeout 3600proxy_send_timeout 3600add_header X-Accel-Buffering no always
- 确认
mm.tkmind.cn/api/status返回真实 H5 API,而不是维护页 HTML。 - 将
m.tkmind.cn -> 105 nginx -> 127.0.0.1:19081 -> reverse SSH tunnel -> Portal :8081标记为 legacy/rollback-only。
验收:
mm.tkmind.cn/api/status返回后端ok或明确 JSON health,而不是维护页 HTML。- SSE 响应头包含
X-Accel-Buffering: no。
P2: Redis Runtime State + Router v1
目标: 从 round-robin 升级为 pressure-aware routing。
Redis key:
worker:{id}:heartbeat
worker:{id}:active_streams
worker:{id}:active_sessions
worker:{id}:ewma_first_token_ms
worker:{id}:error_rate
worker:{id}:memory_pressure
worker:{id}:drain
session:{session_id}:worker
stream:{stream_id}:status
stream:{stream_id}:started_at
调度分数:
score =
active_streams * 3
+ active_sessions * 1
+ ewma_first_token_ms * 0.01
+ error_rate * 5
+ memory_pressure * 2
原则:
- 已存在 session 优先使用
session:{id}:worker。 - 新 session 选择最低 score 的健康 worker。
- heartbeat 过期 worker 不接新 session。
- 不做无损中途迁移,除非 goose 支持完整 session restore。
P3: Observability
目标: 调度不靠感觉。
指标:
- stream open/close/abort count
- active_streams gauge
- first_token_latency_ms
- stream_duration_ms
- worker fetch error rate
- billing finish count/error count
先写日志和内存聚合,后续接 Prometheus 或 Redis。
P4: Tool Gateway v1
目标: Aider/OpenHands 工具隔离。
任务:
- 普通聊天默认不暴露 Aider/OpenHands。
- 代码任务按策略启用 Aider 或 OpenHands。
- Tool Gateway 提供 queue、timeout、retry、并发上限、失败熔断。
- stream 中输出 tool progress event,避免用户界面无反馈。
第一阶段实际落地:
- 普通用户默认不暴露 Aider/OpenHands。
- 保留显式 user capability override,作为白名单。
- 后续再将 Aider/OpenHands 从 goosed platform extension 进一步拆成 queue-based Tool Gateway。
P5: Worker Pool 运维化
目标: 多 goosed 的发布、回滚、扩缩容可控。
任务:
- 固化
goosed-prod-1..Ncompose/启动配置。 - 每个 worker 独立日志、健康检查、资源上限。
- drain 模式: 不接新 session,等 active stream 清零后升级。
- 保留一键回滚镜像 tag。
Drain 操作:
docker exec memind-runtime-redis redis-cli SET memind:runtime:worker:goosed-3:drain 1
curl -sk https://mm.tkmind.cn/api/runtime/status
docker exec memind-runtime-redis redis-cli DEL memind:runtime:worker:goosed-3:drain
说明:
drain=1后 Router 不再给该 worker 分配新 session。- 已存在 session 仍保持 worker affinity,不做中途迁移。
- 等
activeStreams=0后再重启或升级该 worker。
执行顺序
- P0 立即执行,低风险高收益。
- P1 基于
mm.tkmind.cn新入口执行;除非明确回滚,不再改造 105 H5 转发链路。 - P2/P3 一起推进,先观测再调度。
- P4/P5 在流式链路稳定后推进。
执行记录
2026-07-02 P0 StreamController v1
变更文件:
/Users/john/Project/Memind/server.mjs
备份:
/Users/john/Project/Memind/server.mjs.bak-streamcontroller-20260702-0628
已完成:
proxySessionEvents增加 upstreamAbortController,客户端断开时主动 abort goosed SSE fetch。- SSE 响应头升级为
text/event-stream; charset=utf-8、no-cache, no-transform、X-Accel-Buffering: no。 - 响应开始后调用
flushHeaders()。 - 用
Writablesink 处理res.write()backpressure,并保留 billing balance event 注入。 - 用
pipeline(source, sanitizer, billing, sink)统一管理 SSE 管道收尾。 - 通过
launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal让变更生效。
验证:
/opt/homebrew/opt/node@24/bin/node --check /Users/john/Project/Memind/server.mjs通过。- Portal 新 PID:
89465,监听*:8081。 http://127.0.0.1:8081/api/status返回ok。https://127.0.0.1:18006/status返回ok。https://127.0.0.1:18007/status返回ok。https://127.0.0.1:18008/status返回ok。https://127.0.0.1:18009/status返回ok。
2026-07-02 H5 public base 临时切换
决策:
- H5 public base 从
https://m.tkmind.cn临时切换为https://mm.tkmind.cn。 - 后续 H5 公网访问不再走 105 转发链路。
- 旧链路
m.tkmind.cn -> 105 nginx -> 127.0.0.1:19081 -> reverse SSH tunnel -> Portal :8081仅保留为 legacy/rollback-only 说明,不作为 Memind 2.0 改造目标。
变更文件:
/Users/john/Project/Memind/.env/Users/john/Project/Memind/scripts/run-memind-portal-prod.sh/Users/john/Project/Memind/RUNBOOK.txt/Users/john/Project/memind_architecture/memind-2-streaming-agent-runtime-plan.md
已执行:
- 将
H5_PUBLIC_BASE_URL改为https://mm.tkmind.cn。 - 将
VITE_MINDSPACE_BASE改为https://mm.tkmind.cn。 - 将 Portal 启动脚本默认
H5_PUBLIC_BASE_URL改为https://mm.tkmind.cn。 - 更新
RUNBOOK.txt,明确 105 转发链路不再作为后续 H5 公网路径。 - 通过
launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal重启 Portal。
验证:
- Portal 新 PID:
94324,监听*:8081。 - 运行环境确认:
H5_PUBLIC_BASE_URL=https://mm.tkmind.cn,VITE_MINDSPACE_BASE=https://mm.tkmind.cn。 - 本机
http://127.0.0.1:8081/api/status返回ok。 - 四个 goosed worker
18006..18009均返回ok。 - 公网
https://mm.tkmind.cn/api/status当前返回 nginx502 Bad Gateway,说明域名侧入口尚未接到当前 Portal;该问题纳入 P1 新入口修复,不再回到 105 转发链路处理。
2026-07-02 P1 Gateway SSE 配置
变更文件:
/opt/homebrew/etc/nginx/servers/mm.tkmind.cn.conf/Users/john/Project/Memind/scripts/wechat-mp-menu.mjs/Users/john/Project/Memind/server.mjs/Users/john/Project/Memind/dist/dev/wechat-share-demo.html/Users/john/Project/Memind/public/dev/wechat-share-demo.html
备份:
/opt/homebrew/etc/nginx/backups/mm.tkmind.cn.conf.bak-streaming-20260702-0635
已完成:
mm.tkmind.cn通过本机 nginx 反向代理到 Portal127.0.0.1:8081。- 为
/api/sessions/<id>/events和/api/agent/runs/<id>/events增加 SSE 专用 nginx location:proxy_buffering offproxy_cache offgzip offproxy_read_timeout 3600sproxy_send_timeout 3600sadd_header X-Accel-Buffering no always
- 通用
location /增加proxy_read_timeout 300s和proxy_send_timeout 300s。 - 微信菜单脚本默认入口改为
https://mm.tkmind.cn和https://mm.tkmind.cn/space。 server.mjs内H5_PUBLIC_BASE_URL缺省值改为https://mm.tkmind.cn。
验证:
nginx -t通过。nginx -s reload已执行。https://mm.tkmind.cn/api/status返回ok。- 未登录访问
https://mm.tkmind.cn/api/sessions/test-session/events返回401,且响应头包含x-accel-buffering: no。 - 未登录访问
https://mm.tkmind.cn/api/agent/runs/test-run/events返回401,且响应头包含x-accel-buffering: no。
2026-07-02 P2 Redis Runtime State + Router v1
变更文件:
/Users/john/Project/Memind/server.mjs/Users/john/Project/Memind/.env
新增运行组件:
- Docker 容器:
memind-runtime-redis - 镜像:
redis:7-alpine - 绑定:
127.0.0.1:6379->6379 - 持久化卷:
memind-runtime-redis-data - 启动参数:
redis-server --appendonly yes - 重启策略:
unless-stopped
已完成:
- 新增可选
createRuntimeRouter。 MEMIND_RUNTIME_REDIS_URL未配置时自动回退到原 round-robin。MEMIND_RUNTIME_REDIS_URL配置后启用 Redis scheduler。- 新 session 选择最低 score 的健康 worker;同分时仍按轮转顺序打散,避免全部压到第一个 worker。
- session pointer 写入 Redis:
memind:runtime:session:<id>:workermemind:runtime:session:<id>:target
- SSE 打开/关闭维护 Redis active stream:
memind:runtime:worker:<id>:active_streamsmemind:runtime:worker:<id>:heartbeatmemind:runtime:stream:<id>:status
验证:
docker exec memind-runtime-redis redis-cli PING返回PONG。- Redis AOF 已开启。
- Portal 运行环境包含
MEMIND_RUNTIME_REDIS_URL=redis://127.0.0.1:6379/0。 - Portal 日志出现
[RuntimeRouter] Redis scheduler enabled。 - Redis 当前可见 key 示例:
memind:runtime:worker:goosed-3:active_streams=1memind:runtime:worker:goosed-3:heartbeat=<timestamp>memind:runtime:stream:20260701_24:status=active
https://mm.tkmind.cn/api/status返回ok。- 四个 goosed worker
18006..18009均返回ok。
2026-07-02 P3 Observability v1
变更文件:
/Users/john/Project/Memind/server.mjs
已完成:
- 新增只读接口
GET /api/runtime/status。 - 接口返回:
publicBaseUrl- Redis Router 是否启用
- Redis namespace
- 每个 worker 的
target、activeStreams、activeSessions、ewmaFirstTokenMs、errorRate、memoryPressure、heartbeat - 每个
TKMIND_API_TARGETS上游的/status健康状态
- 该接口与
/api/status一样作为运维健康读接口,不要求登录;不返回任何密钥。
验证:
/opt/homebrew/opt/node@24/bin/node --check /Users/john/Project/Memind/server.mjs通过。https://mm.tkmind.cn/api/runtime/status返回ok: true。- 返回中
router.enabled=true,namespace 为memind:runtime。 - 返回中四个目标
18006..18009均healthy: true。 https://mm.tkmind.cn/api/status返回ok。- 未登录访问
https://mm.tkmind.cn/api/sessions/test-session/events返回401,且响应头包含x-accel-buffering: no。
2026-07-02 P4 Tool Gateway v1 第一阶段
已完成:
- 只读验证发现数据库中
role=user曾将aider、openhands覆盖为allowed=1。 - 已将
h5_capability_grants中subject_type='role' AND subject_id='user'的aider、openhands改为allowed=0。 - 保留两个显式 user override:
a6fb1e97-2b0f-447b-b138-4561d8e5c53ea70ff537-8908-486e-9b6c-042e07cc25db
效果:
- 普通用户新建/恢复 session 时,Portal policy 不再把 Aider/OpenHands 放入
extension_overrides。 - 已授权用户仍可继续使用。
- goosed 全局 config 仍保留 Aider/OpenHands enabled,避免破坏显式授权用户和既有会话;后续 Tool Gateway 阶段再拆 queue/timeout/retry。
2026-07-02 P5 Worker Pool Drain v1
变更文件:
/Users/john/Project/Memind/server.mjs
已完成:
- Redis Router scoring 支持读取
memind:runtime:worker:<id>:drain。 - 当 drain 值为
1/true/yes时,该 worker 对新 session 的 score 为不可选。 /api/runtime/status返回每个 worker 的drain字段。
验证:
/opt/homebrew/opt/node@24/bin/node --check /Users/john/Project/Memind/server.mjs通过。https://mm.tkmind.cn/api/runtime/status返回四个 workerhealthy=true。https://mm.tkmind.cn/api/runtime/status返回四个 workerdrain=false。https://mm.tkmind.cn/api/status返回ok。- 未登录访问
https://mm.tkmind.cn/api/sessions/test-session/events返回401,且响应头包含x-accel-buffering: no。
2026-07-02 生产同步分支
决策:
- 当前是生产环境,改造过程中不随意删除数据、不清理持久化目录、不覆盖旧本地脏改动。
/Users/john/Project/Memind是生产运行产物目录,不是 git 工作树。- 生产 release manifest 指向
git_head=4fc59729ee222734628dca001e172c127c84488f。 - 远端
https://git.tkmind.cn/tkmind/memind.git的origin/main当前正是该提交。
已执行:
- 新建干净源码目录
/Users/john/Project/memind-clean-main-20260702。 - 从
origin/main创建分支memind-streaming-runtime-20260702。 - 迁入本次已在生产验证的 StreamController、Redis Router、runtime status、
mm.tkmind.cn和 nginx SSE 配置样例。 - 新增远程开发机同步说明
docs/architecture/remote-dev-sync.md。
限制:
- 不提交生产
.env、数据库、MindSpace/、data/、users/、.tailscale/、logs/、证书和密钥。 - 只提交代码、配置模板、部署样例和架构/运维文档。
2026-07-02 P3.5/P5.5 生产数据保护与运维补强
生产数据保护:
- 允许重启生产服务,但重启前必须保护好数据库和
/Users/john/Project/Memind/MindSpace。 - 备份目录:
/Users/john/Project/memind_backups/20260702-065813-pre-p35-p55。 MindSpace使用rsync -a全量副本,文件数2137,大小约120M。- 数据库为 Aliyun RDS MySQL
goose,已导出:mysql-schema.sqlmysql-manifest.jsonmysql-jsonl/*.jsonl- 表数
85,行数28296
改造内容:
- Redis Router worker 状态增加:
stream_open_countstream_abort_countstream_error_countlast_stream_started_atlast_stream_ended_atscore
- 新增只读检查脚本
scripts/check-stream-runtime.mjs。 - 新增 drain 运维脚本
scripts/runtime-worker-drain.mjs。 - runtime 构建模板同步上述脚本,并将 RUNBOOK 中主路径更新为
mm.tkmind.cn -> local nginx -> Portal :8081。 - 本地和远端分支提交
4420cca chore: add runtime observability ops。 - 修正检查脚本为
HEAD探测 SSE 入口,避免健康检查打开真实上游 SSE;提交c9b7252 fix: avoid opening sse in runtime check。
2026-07-02 P4.5 Tool Gateway 过渡层第一阶段
目标:
- 不拆 goosed 内部 Aider/OpenHands,不引入队列系统,先把普通聊天和代码工具任务隔离。
- 普通聊天默认不注入 Aider/OpenHands,即使用户有显式白名单。
- 只有显式
toolMode='code'的代码任务 policy 才注入 Aider/OpenHands。
已完成:
buildAgentExtensionPolicy()新增toolMode,默认chat。toolMode='chat'时不注入aider/openhands。toolMode='code'时才注入白名单用户的aider/openhands。- Aider/OpenHands extension 增加:
timeout_msmetadata.runtime_scope='code_tool_task'
userAuth.getAgentSessionPolicy(userId)默认返回 chat policy。- 新增
userAuth.getCodeAgentSessionPolicy(userId)作为后续代码任务入口。 /api/runtime/status增加toolRuntime摘要:defaultModecodeToolModechatInjectsCodeToolsaiderTimeoutMsopenhandsTimeoutMs
- 新增生产只读检查脚本
scripts/check-tool-runtime.mjs。
生产验证:
scripts/check-tool-runtime.mjs返回ok=true。role=user的aider/openhands均为false。- 显式白名单用户数为
2。 - 白名单用户 chat mode 不含 Aider/OpenHands,code mode 具备 Aider/OpenHands。
scripts/check-stream-runtime.mjs返回ok=true,四个 worker healthy。scripts/runtime-worker-drain.mjs status显示四个 workeractiveStreams=0。
生产部署:
- 允许重启生产 Portal,已使用
launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal生效。 - 本阶段不写数据库、不删除数据、不修改
/Users/john/Project/Memind/MindSpace。 - 生产代码备份:
/Users/john/Project/memind_backups/20260702-071020-pre-p45-tool-guard。
回滚策略
- P0: 修改前保留
server.mjs备份;如启动失败,恢复备份并launchctl kickstartPortal。 - P1: 修改 nginx 前备份 conf;
nginx -t成功后再 reload。 - P2: Redis Router 默认可通过 env 开关退回当前
pickTarget()round-robin。 - P4: Tool Gateway 默认关闭,通过用户策略逐步放量。