510 lines
20 KiB
Markdown
510 lines
20 KiB
Markdown
# 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 收尾。
|
||
|
||
## 目标架构
|
||
|
||
```mermaid
|
||
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"]
|
||
```
|
||
|
||
## 分层原则
|
||
|
||
1. Control Plane: Redis + Router,负责 worker 心跳、active stream、错误率、首 token 延迟、session worker pointer。
|
||
2. Stream Data Plane: Memind Portal StreamController + goosed worker,负责低延迟流式传输。
|
||
3. Persistence: 现有数据库只保存最终状态、会话历史、计费、发布记录,不保存高频 runtime state。
|
||
4. Worker Affinity: goosed 尽量业务无状态,但 session 运行期必须 worker-affine;已存在 session 优先回原 worker。
|
||
5. Tool Isolation: Aider/OpenHands 从默认全开逐步改为按任务启用、队列化、限流、超时和熔断。
|
||
|
||
## 阶段计划
|
||
|
||
### P0: StreamController v1 加固
|
||
|
||
目标: 不引入新基础设施,先把现有 H5 SSE 链路做稳。
|
||
|
||
任务:
|
||
|
||
- 为 `/api/sessions/:sessionId/events` 增加 SSE 专用响应头:
|
||
- `Content-Type: text/event-stream; charset=utf-8`
|
||
- `Cache-Control: no-cache, no-transform`
|
||
- `Connection: keep-alive`
|
||
- `X-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 off`
|
||
- `proxy_cache off`
|
||
- `gzip off`
|
||
- `proxy_read_timeout 3600`
|
||
- `proxy_send_timeout 3600`
|
||
- `add_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:
|
||
|
||
```text
|
||
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
|
||
```
|
||
|
||
调度分数:
|
||
|
||
```text
|
||
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..N` compose/启动配置。
|
||
- 每个 worker 独立日志、健康检查、资源上限。
|
||
- drain 模式: 不接新 session,等 active stream 清零后升级。
|
||
- 保留一键回滚镜像 tag。
|
||
|
||
Drain 操作:
|
||
|
||
```bash
|
||
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。
|
||
|
||
## 执行顺序
|
||
|
||
1. P0 立即执行,低风险高收益。
|
||
2. P1 基于 `mm.tkmind.cn` 新入口执行;除非明确回滚,不再改造 105 H5 转发链路。
|
||
3. P2/P3 一起推进,先观测再调度。
|
||
4. 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` 增加 upstream `AbortController`,客户端断开时主动 abort goosed SSE fetch。
|
||
- SSE 响应头升级为 `text/event-stream; charset=utf-8`、`no-cache, no-transform`、`X-Accel-Buffering: no`。
|
||
- 响应开始后调用 `flushHeaders()`。
|
||
- 用 `Writable` sink 处理 `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` 当前返回 nginx `502 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 反向代理到 Portal `127.0.0.1:8081`。
|
||
- 为 `/api/sessions/<id>/events` 和 `/api/agent/runs/<id>/events` 增加 SSE 专用 nginx location:
|
||
- `proxy_buffering off`
|
||
- `proxy_cache off`
|
||
- `gzip off`
|
||
- `proxy_read_timeout 3600s`
|
||
- `proxy_send_timeout 3600s`
|
||
- `add_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>:worker`
|
||
- `memind:runtime:session:<id>:target`
|
||
- SSE 打开/关闭维护 Redis active stream:
|
||
- `memind:runtime:worker:<id>:active_streams`
|
||
- `memind:runtime:worker:<id>:heartbeat`
|
||
- `memind: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=1`
|
||
- `memind: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-4561d8e5c53e`
|
||
- `a70ff537-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` 返回四个 worker `healthy=true`。
|
||
- `https://mm.tkmind.cn/api/runtime/status` 返回四个 worker `drain=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.sql`
|
||
- `mysql-manifest.json`
|
||
- `mysql-jsonl/*.jsonl`
|
||
- 表数 `85`,行数 `28296`
|
||
|
||
改造内容:
|
||
|
||
- Redis Router worker 状态增加:
|
||
- `stream_open_count`
|
||
- `stream_abort_count`
|
||
- `stream_error_count`
|
||
- `last_stream_started_at`
|
||
- `last_stream_ended_at`
|
||
- `score`
|
||
- 新增只读检查脚本 `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_ms`
|
||
- `metadata.runtime_scope='code_tool_task'`
|
||
- `userAuth.getAgentSessionPolicy(userId)` 默认返回 chat policy。
|
||
- 新增 `userAuth.getCodeAgentSessionPolicy(userId)` 作为后续代码任务入口。
|
||
- `/api/runtime/status` 增加 `toolRuntime` 摘要:
|
||
- `defaultMode`
|
||
- `codeToolMode`
|
||
- `chatInjectsCodeTools`
|
||
- `aiderTimeoutMs`
|
||
- `openhandsTimeoutMs`
|
||
- 新增生产只读检查脚本 `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` 显示四个 worker `activeStreams=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 kickstart` Portal。
|
||
- P1: 修改 nginx 前备份 conf;`nginx -t` 成功后再 reload。
|
||
- P2: Redis Router 默认可通过 env 开关退回当前 `pickTarget()` round-robin。
|
||
- P4: Tool Gateway 默认关闭,通过用户策略逐步放量。
|