Files
memind/docs/architecture/memind-2-streaming-agent-runtime-plan.md
T

3115 lines
111 KiB
Markdown
Raw Normal View History

2026-07-02 06:55:26 +08:00
# Memind 2.0 Streaming Agent Runtime 改造计划
> 历史快照说明:本文记录 2026-07-02 streaming/runtime 改造过程,其中出现的 `/Users/john/Project/Memind/MindSpace` 是当时的存量数据保护/统计口径。103 当前生产 MindSpace Service 根目录以 [103 runtime topology](../103-runtime-topology.md) 为准,即 `/Users/john/MindSpace`;不要把本文历史路径当作当前服务根目录。
2026-07-02 06:55:26 +08:00
更新时间: 2026-07-02
执行状态:
- P0 StreamController v1: 已完成代码落地、语法检查、Portal kickstart 和基础健康验证。
2026-07-07 10:01:32 +08:00
- P1 Gateway SSE 配置: 已完成,`m.tkmind.cn` 接入 Portal,SSE route 已禁缓冲。
2026-07-02 06:55:26 +08:00
- P2 Redis Runtime State + Router v1: 已完成可回退版本并启用 Redis。
- P3 Observability: 已完成第一版,只读 runtime 状态接口已上线。
- P4 Tool Gateway v1: 已完成第一步,普通用户默认不再暴露 Aider/OpenHands,显式用户白名单保留。
2026-07-02 11:36:46 +08:00
- P6.0 Tool Gateway Queue v0: 已完成最小队列化运行时,`/agent/runs` 支持全局并发限制、运行超时和 runtime/SLO 可观测状态。
- P6.1 Tool Worker v1 骨架: 已完成外部 worker 接管入口,生产仍保持 Portal `autoDispatch=true`,未放量真实外置执行。
- P6.2 Tool Worker Canary: 已完成短窗口真实接管 canary,外部 worker 成功处理一条 chat-mode queued run,生产已恢复 Portal `autoDispatch=true`。
- P6.3 Tool Gateway 协议化: 已完成,已新增 `agent-run-v1` 协议模块,code run 可在显式开启后绕开 goosed session extension 交给 Aider/OpenHands 外部执行器。
- P6.4 Tool Gateway Real Canary: 已完成 Aider 真实执行 canary,修复 DeepSeek/Aider 模型名前缀问题后,专用空仓库产物生成成功。
- P6.5 OpenHands Real Canary: 已完成,OpenHands headless/json 在专用空仓库中生成目标产物,生产仍保持默认关闭。
- P6.6 External Worker Code-run Canary: 已完成,Portal autoDispatch 短窗口关闭,external `agent-run-worker --run-id` 精确接管一条 code run 并生成 canary 产物,生产已恢复默认关闭。
- P6.7 Tool Gateway Production Guardrails: 已完成第一步,Tool Gateway 支持 expected file/content 产物校验、stdout/stderr tail 审计和 validation failed 非重试失败语义,已发布生产且默认关闭。
- P6.8 Tool Gateway Controlled Rollout 基础: 已完成,external agent-run-worker LaunchAgent installer 已进入 runtime,生产已安装为 disabled/not running,后续可显式开启灰度。
- P6.9 Validated External Worker Canary: 已完成,短窗口启动 external worker,先验证 exit 0 但内容不符会 failed,再验证 expected content 命中会 succeeded;生产已恢复默认关闭,worker 已恢复 disabled/not running。
- P6.10 External Worker Observability: 已完成,只读 `check-agent-run-worker.mjs` 已进入 runtime,可检查 worker LaunchAgent、queue lag、最近失败 run;生产验证 `ok=true`。
- P6.11 Single-user Rollout Policy: 已完成默认关闭的 code-run 放量策略门禁,支持 user allowlist、task type allowlist、required validation,并在 runtime/status 暴露当前策略。
- P6.12 All-user Long-running Gray: 已完成,生产已默认对所有用户开启 H5 code-run 灰度,external worker running,普通测试用户真实 code-run 已成功生成 MindSpace 产物。
- P6.13 Agent Run Auto-pause Guard: 已完成,新增 `agent-run-guard.mjs` 和 LaunchAgent,按失败数/队列积压/running 超时自动关闭 code-run gate 并停 worker。
- P6.15 H5 Code-run Validation Metadata: 已完成,H5 code-run 自动声明 `.memind/agent-runs/<requestId>.json` receipt 校验,生产已恢复 `MEMIND_AGENT_CODE_RUNS_REQUIRE_VALIDATION=1` 并通过普通测试用户真实路径。
2026-07-02 12:08:49 +08:00
- P6.17 Controlled Worker Concurrency Canary: 已完成,external worker 短窗口提升到并发 2,两条真实 code-run 同时 running 并全部通过 receipt validation。
- P6.18 Concurrency Rollout Policy: 已完成,生产 worker 和 Portal runtime/status 已对齐到并发 2,worker KeepAlive 保持开启,保留回滚备份。
- P6.19 Task-level Artifact Validation: 已完成第一步,H5 code-run 会在 receipt 之外追加可推断的 `public/*.html` 目标文件校验,页面编辑 code-run 会追加 page-edit task receipt,生产真实 canary 三项 expectedFiles 全部通过。
2026-07-02 12:12:41 +08:00
- P8.1 Queue Lease / Stuck Run Recovery: 已完成第一步,worker dispatch 前自动回收超时 running run,新增 `--recover-stale` dry-run/apply 运维入口,生产 synthetic stale run 验证通过。
- P8.2 Portal DB/Auth Transient Error Hardening: 已完成第一步,session attach、`/auth/status` 和 API auth middleware 已捕获 DB/auth 瞬时错误,生产部署后 live health、auth/status、runtime/status、guard 和 SLO 验证通过。
2026-07-02 12:38:34 +08:00
- P8.3 Worker Lease Heartbeat: 已完成第一步,running run 执行期间写入 `worker_heartbeat` event,runtime/status、worker check、guard 和 stale recovery 均优先使用 heartbeat age,生产 synthetic 与真实 worker canary 验证通过。
2026-07-02 12:45:55 +08:00
- P8.4 Long-running Gray Soak and Heartbeat SLO: 已完成第二步,runtime SLO JSON/Markdown 报告加入 agent-run heartbeat 摘要和 failure 条件,并安装独立小时级 soak 快照;继续保持并发 2 soak,不扩大灰度。
2026-07-02 11:36:46 +08:00
- P5.15 Active Stream TTL Reconcile: 已按用户要求跳过,暂不做报表/定时 reconcile。
2026-07-02 06:55:26 +08:00
- P5 Worker Pool 运维化: 已完成第一步,Redis Router 支持 worker drain。
2026-07-02 11:36:46 +08:00
- P5.9 First-token Latency EWMA: 已完成,StreamController 会把首个 SSE chunk 延迟写入 Redis,SLO 报告已展示。
- P5.10 First-token Latency 趋势报表: 已完成 Redis rolling samples,runtime/SLO 已展示最近 5 分钟和 1 小时 p50/p95。
- P5.11 SLO 日报: 已完成 `runtime-slo-report.mjs --write-report`,可输出 JSON/Markdown 运维报告。
- P5.12 SLO 日报定时器: 已完成 LaunchAgent,每日 23:55 自动生成 runtime SLO JSON/Markdown 报告。
2026-07-02 06:55:26 +08:00
- 生产同步分支: 已从远程 `origin/main` 新建干净副本和分支 `memind-streaming-runtime-20260702`,用于远程开发机后续直接拉取。
2026-07-02 11:36:46 +08:00
- P3.5/P5.5 生产化补强: 已完成 runtime metrics、健康检查脚本、drain 运维脚本、自动采样 LaunchAgent 和 SLO 只读报告。
2026-07-02 06:55:26 +08:00
## 目标
把当前 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`。
2026-07-07 10:01:32 +08:00
- H5 public base: `https://m.tkmind.cn`。
- goosed worker pool: Docker/Colima 内 `goosed-prod-1..9`,宿主机 `18006..18014`(2026-07-07 确认 9 个 healthy)。
- H5 上游配置: `TKMIND_API_TARGETS=https://127.0.0.1:18006,...,18014`(须与 compose 容器数一致)。
2026-07-02 06:55:26 +08:00
- 当前已有 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..18014/status` 仍返回 `ok`。
2026-07-02 06:55:26 +08:00
### P1: Gateway SSE 配置
2026-07-07 10:01:32 +08:00
目标: `m.tkmind.cn` 新入口和本机入口不缓冲 SSE;后续 H5 不再走 105 nginx 反向隧道转发。
2026-07-02 06:55:26 +08:00
任务:
- 对 `/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`
2026-07-07 10:01:32 +08:00
- 确认 `m.tkmind.cn/api/status` 返回真实 H5 API,而不是维护页 HTML。
2026-07-02 06:55:26 +08:00
- 将 `m.tkmind.cn -> 105 nginx -> 127.0.0.1:19081 -> reverse SSH tunnel -> Portal :8081` 标记为 legacy/rollback-only。
验收:
2026-07-07 10:01:32 +08:00
- `m.tkmind.cn/api/status` 返回后端 `ok` 或明确 JSON health,而不是维护页 HTML。
2026-07-02 06:55:26 +08:00
- 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
2026-07-07 10:01:32 +08:00
curl -sk https://m.tkmind.cn/api/runtime/status
2026-07-02 06:55:26 +08:00
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 立即执行,低风险高收益。
2026-07-07 10:01:32 +08:00
2. P1 基于 `m.tkmind.cn` 新入口执行;除非明确回滚,不再改造 105 H5 转发链路。
2026-07-02 06:55:26 +08:00
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 临时切换
决策:
2026-07-07 10:01:32 +08:00
- H5 public base 从 `https://m.tkmind.cn` 临时切换为 `https://m.tkmind.cn`。
2026-07-02 06:55:26 +08:00
- 后续 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`
已执行:
2026-07-07 10:01:32 +08:00
- 将 `H5_PUBLIC_BASE_URL` 改为 `https://m.tkmind.cn`。
- 将 `VITE_MINDSPACE_BASE` 改为 `https://m.tkmind.cn`。
- 将 Portal 启动脚本默认 `H5_PUBLIC_BASE_URL` 改为 `https://m.tkmind.cn`。
2026-07-02 06:55:26 +08:00
- 更新 `RUNBOOK.txt`,明确 105 转发链路不再作为后续 H5 公网路径。
- 通过 `launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal` 重启 Portal。
验证:
- Portal 新 PID: `94324`,监听 `*:8081`。
2026-07-07 10:01:32 +08:00
- 运行环境确认: `H5_PUBLIC_BASE_URL=https://m.tkmind.cn`,`VITE_MINDSPACE_BASE=https://m.tkmind.cn`。
2026-07-02 06:55:26 +08:00
- 本机 `http://127.0.0.1:8081/api/status` 返回 `ok`。
- 九个 goosed worker `18006..18014` 均返回 `ok`。
2026-07-07 10:01:32 +08:00
- 公网 `https://m.tkmind.cn/api/status` 当前返回 nginx `502 Bad Gateway`,说明域名侧入口尚未接到当前 Portal;该问题纳入 P1 新入口修复,不再回到 105 转发链路处理。
2026-07-02 06:55:26 +08:00
### 2026-07-02 P1 Gateway SSE 配置
变更文件:
2026-07-07 10:01:32 +08:00
- `/opt/homebrew/etc/nginx/servers/` 下对应 `m.tkmind.cn` 的生产入口配置
2026-07-02 06:55:26 +08:00
- `/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`
备份:
2026-07-07 10:01:32 +08:00
- `/opt/homebrew/etc/nginx/backups/` 下对应 `m.tkmind.cn` 的 streaming 备份配置
2026-07-02 06:55:26 +08:00
已完成:
2026-07-07 10:01:32 +08:00
- `m.tkmind.cn` 通过本机 nginx 反向代理到 Portal `127.0.0.1:8081`。
2026-07-02 06:55:26 +08:00
- 为 `/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`。
2026-07-07 10:01:32 +08:00
- 微信菜单脚本默认入口改为 `https://m.tkmind.cn` 和 `https://m.tkmind.cn/space`。
- `server.mjs` 内 `H5_PUBLIC_BASE_URL` 缺省值改为 `https://m.tkmind.cn`。
2026-07-02 06:55:26 +08:00
验证:
- `nginx -t` 通过。
- `nginx -s reload` 已执行。
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status` 返回 `ok`。
- 未登录访问 `https://m.tkmind.cn/api/sessions/test-session/events` 返回 `401`,且响应头包含 `x-accel-buffering: no`。
- 未登录访问 `https://m.tkmind.cn/api/agent/runs/test-run/events` 返回 `401`,且响应头包含 `x-accel-buffering: no`。
2026-07-02 06:55:26 +08:00
### 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`
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status` 返回 `ok`。
- 九个 goosed worker `18006..18014` 均返回 `ok`。
2026-07-02 06:55:26 +08:00
### 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` 通过。
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/runtime/status` 返回 `ok: true`。
2026-07-02 06:55:26 +08:00
- 返回中 `router.enabled=true`,namespace 为 `memind:runtime`。
- 返回中九个目标 `18006..18014` 均 `healthy: true`。
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status` 返回 `ok`。
- 未登录访问 `https://m.tkmind.cn/api/sessions/test-session/events` 返回 `401`,且响应头包含 `x-accel-buffering: no`。
2026-07-02 06:55:26 +08:00
### 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` 通过。
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/runtime/status` 返回四个 worker `healthy=true`。
- `https://m.tkmind.cn/api/runtime/status` 返回四个 worker `drain=false`。
- `https://m.tkmind.cn/api/status` 返回 `ok`。
- 未登录访问 `https://m.tkmind.cn/api/sessions/test-session/events` 返回 `401`,且响应头包含 `x-accel-buffering: no`。
2026-07-02 06:55:26 +08:00
### 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`。
2026-07-07 10:01:32 +08:00
- 迁入本次已在生产验证的 StreamController、Redis Router、runtime status、`m.tkmind.cn` 和 nginx SSE 配置样例。
2026-07-02 06:55:26 +08:00
- 新增远程开发机同步说明 `docs/architecture/remote-dev-sync.md`。
限制:
- 不提交生产 `.env`、数据库、`MindSpace/`、`data/`、`users/`、`.tailscale/`、`logs/`、证书和密钥。
- 只提交代码、配置模板、部署样例和架构/运维文档。
2026-07-02 07:04:50 +08:00
### 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`。
2026-07-07 10:01:32 +08:00
- runtime 构建模板同步上述脚本,并将 RUNBOOK 中主路径更新为 `m.tkmind.cn -> local nginx -> Portal :8081`。
2026-07-02 07:05:07 +08:00
- 本地和远端分支提交 `4420cca chore: add runtime observability ops`。
2026-07-02 07:12:18 +08:00
- 修正检查脚本为 `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`。
2026-07-02 07:04:50 +08:00
2026-07-02 11:36:46 +08:00
### 2026-07-02 P4.6 Code Task 入口接线
目标:
- 在不引入新表、不改生产数据的前提下,让显式代码任务可以进入 `toolMode='code'`。
- 普通 H5 聊天继续默认 `toolMode='chat'`,不注入 Aider/OpenHands。
- 对已有 session 的 code run 也先 reconcile 到 code policy,再提交用户消息。
已完成:
- `POST /api/agent/runs` 支持 `tool_mode` / `toolMode` 与 `task_type` / `taskType`。
- 支持的 code mode alias:
- `code`
- `code-task`
- `code_task`
- `code-tool`
- `code_tool`
- `code_tool_task`
- 未知 `tool_mode` 返回 400,避免误开高权限工具。
- run 创建时将运行模式写入既有 `user_message_json.metadata.memindRun`:
- `toolMode`
- `taskType`
- 新建 code run session 时使用 `userAuth.getCodeAgentSessionPolicy(userId)`。
- 提交 code run 到已有 session 前,`submitSessionReplyForUser(..., { toolMode: 'code' })` 会按 code policy reconcile。
安全边界:
- 本阶段不写数据库 schema。
- 本阶段不修改或删除 `/Users/john/Project/Memind/MindSpace`。
- Aider/OpenHands 仍只对白名单用户在 code mode 下按 policy 注入。
- 普通 chat mode 仍保持无 Aider/OpenHands。
本地验证:
- `node --check agent-run-gateway.mjs agent-run-routes.mjs tkmind-proxy.mjs` 通过。
- `node --test agent-run-gateway.test.mjs agent-run-routes.test.mjs capabilities.test.mjs policies.test.mjs user-memory-profile.test.mjs tkmind-proxy.test.mjs user-publish.test.mjs message-stream.test.mjs` 通过,80 tests pass。
生产部署:
- 生产代码备份: `/Users/john/Project/memind_backups/20260702-071604-pre-p46-code-task-entry`。
- 已将 `.runtime/portal/server.mjs` 部署为 `/Users/john/Project/Memind/server.mjs`。
- 已执行 `launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal`。
- 未修改 PG 数据,未修改或删除 `/Users/john/Project/Memind/MindSpace`。
生产验证:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status` 返回 `ok`。
- `https://m.tkmind.cn/api/runtime/status` 返回 router enabled,全部 goosed target healthy。
2026-07-02 11:36:46 +08:00
- `scripts/check-stream-runtime.mjs` 返回 `ok=true`。
- `scripts/check-tool-runtime.mjs` 返回 `ok=true`,普通 chat 不注入 Aider/OpenHands,code mode 对白名单用户可注入。
- `scripts/runtime-worker-drain.mjs status` 返回 `ok=true`,四个 worker drain=false。
- 观察项: `goosed-1 activeStreams=2`,无 error;未强制重置,避免误伤生产中的真实 stream。
### 2026-07-02 P4.7 H5 Code Run 灰度入口
目标:
- 将 H5 侧 agent run 创建入口接到 P4.6 的 `tool_mode`。
- 默认关闭灰度,避免普通生产聊天自动扩大 Aider/OpenHands 暴露面。
- 远程开发机拉取分支后,可通过构建时 env 开关灰度验证。
已完成:
- `createAgentRun()` 支持可选 `toolMode` / `taskType`,请求体映射为:
- `tool_mode`
- `task_type`
- 新增前端 helper `src/utils/agentRunMode.ts`。
- 新增构建时开关:
- `VITE_AGENT_CODE_RUNS_ENABLED`
- `VITE_AGENT_CODE_RUNS_AUTODETECT`
- 开关默认关闭。
- 页面编辑子聊天在 `VITE_AGENT_CODE_RUNS_ENABLED=1` 时传 `tool_mode=code`。
- 普通 H5 聊天仅在 `VITE_AGENT_CODE_RUNS_ENABLED=1` 且 `VITE_AGENT_CODE_RUNS_AUTODETECT=1` 时,按明确代码/仓库/测试/重构类文本命中后传 `tool_mode=code`。
本地验证:
- `npm run build` 通过。
- 当前构建未设置灰度 env,因此生产默认行为仍为 chat mode。
安全边界:
- 本阶段不改 PG,不改 schema。
- 本阶段不修改或删除 `/Users/john/Project/Memind/MindSpace`。
- `tool_mode=code` 到达后端后仍受 P4.6/P4.5 policy 约束;非白名单用户不会获得 Aider/OpenHands。
### 2026-07-02 P4.8 Code Run 灰度前只读检查
目标:
- 固化灰度前检查步骤,避免靠人工记忆判断 H5 code-run 链路是否安全。
- 检查过程不创建 agent run,不写数据库,不修改 MindSpace。
已完成:
- 新增只读脚本 `scripts/check-agent-code-run-entry.mjs`。
- 检查内容:
- 当前 H5 `dist` bundle 是否包含 `/agent/runs` 和 `tool_mode` 入口。
- `/api/runtime/status.toolRuntime` 是否保持:
- `defaultMode='chat'`
- `codeToolMode='code'`
- `chatInjectsCodeTools=false`
- `role=user` 的 `aider` / `openhands` 默认是否仍为 false。
- 白名单用户 code mode 是否具备代码工具能力。
- 明确输出 `writes.database=false`、`writes.mindSpace=false`、`createsAgentRun=false`。
- `scripts/build-portal-runtime.mjs` 已将该脚本复制到 runtime artifact,并在 RUNBOOK 中列出。
验证:
- `node --check scripts/check-agent-code-run-entry.mjs scripts/build-portal-runtime.mjs` 通过。
- 使用生产 env 验证:
- `MEMIND_ENV_FILE=/Users/john/Project/Memind/.env node scripts/check-agent-code-run-entry.mjs`
- 返回 `ok=true`。
### 2026-07-02 P4.9 Code Run 后端灰度门禁
目标:
- 在 H5 构建开关之外,再增加 Portal 后端开关,防止客户端直接提交 `tool_mode=code` 绕过前端灰度。
- 默认关闭,不影响普通 chat run。
已完成:
- 新增后端 env:
- `MEMIND_AGENT_CODE_RUNS_ENABLED=1`
- `POST /api/agent/runs` 收到 `tool_mode=code` 且后端开关未开启时返回 403:
- `代码任务灰度未开启`
- 普通 `tool_mode=chat` 不受该开关影响。
- `/api/runtime/status.toolRuntime` 增加 `codeRunsEnabled`。
- `scripts/check-agent-code-run-entry.mjs` 增加后端开关一致性检查。
- `.env.example` 增加后端灰度开关说明。
验证:
- `node --check agent-run-routes.mjs tkmind-proxy.mjs scripts/check-agent-code-run-entry.mjs` 通过。
- `node --test agent-run-routes.test.mjs agent-run-gateway.test.mjs` 通过,14 tests pass。
- 相关回归 `capabilities/policies/user-memory/tkmind-proxy/message-stream` 通过,61 tests pass。
生产部署:
- 生产代码备份: `/Users/john/Project/memind_backups/20260702-072452-pre-p49-code-run-server-gate`。
- 已覆盖 `/Users/john/Project/Memind/server.mjs` 和 `scripts/check-agent-code-run-entry.mjs`。
- 已执行 `launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal`。
- 未修改 PG 数据,未修改或删除 `/Users/john/Project/Memind/MindSpace`。
生产验证:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status` 返回 `ok`。
2026-07-02 11:36:46 +08:00
- `/api/runtime/status.toolRuntime.codeRunsEnabled=false`。
- 全部 goosed target healthy。
2026-07-02 11:36:46 +08:00
- `scripts/check-agent-code-run-entry.mjs` 返回 `ok=true`。
- `scripts/check-stream-runtime.mjs` 返回 `ok=true`。
- `scripts/runtime-worker-drain.mjs status` 返回 `ok=true`,四个 worker drain=false。
- 观察项: worker `activeStreams` 有非零计数且无 error;未强制重置,避免误伤生产中的真实 stream。
### 2026-07-02 P5.6 Worker activeStreams 收敛工具
目标:
- 处理 Redis 中 `active_streams` 因异常断流或进程重启可能残留的问题。
- 默认只读,不自动清生产状态。
- 只有显式 `--apply` 且 stream 明显超过 TTL 时才做收敛。
已完成:
- `scripts/runtime-worker-drain.mjs` 新增动作:
- `reconcile`
- `reconcile --apply`
- `--stale-ms=<ms>`
- 默认 TTL:
- `MEMIND_RUNTIME_STALE_STREAM_MS`
- 未设置时为 `900000ms`。
- `reconcile` 默认 dry-run,只报告:
- `stale`
- `staleAgeMs`
- `lastStreamReconciledAt`
- `streamReconcileCount`
- `reconciled`
- 只有 `activeStreams > 0` 且 `last_stream_started_at` 超过 TTL,并且没有更新的 `last_stream_ended_at` 时,才被视为 stale。
- `MEMIND_ENV_FILE` 已被 `runtime-worker-drain.mjs` 支持,便于在干净分支读取生产 env 做只读检查。
- runtime RUNBOOK 模板增加:
- `node scripts/runtime-worker-drain.mjs reconcile`
- `node scripts/runtime-worker-drain.mjs reconcile --apply`
验证:
- `node --check scripts/runtime-worker-drain.mjs scripts/build-portal-runtime.mjs` 通过。
- 使用生产 env dry-run:
- `MEMIND_ENV_FILE=/Users/john/Project/Memind/.env node scripts/runtime-worker-drain.mjs reconcile`
- 返回 `ok=true`。
- 四个 worker 均列出。
- 当前无 stale worker,未执行 apply。
生产部署:
- 生产脚本备份: `/Users/john/Project/memind_backups/20260702-072835-pre-p56-worker-reconcile`。
- 已覆盖 `/Users/john/Project/Memind/scripts/runtime-worker-drain.mjs` 和 `RUNBOOK.txt`。
- 未重启 Portal。
- 未修改 PG 数据,未修改或删除 `/Users/john/Project/Memind/MindSpace`。
生产验证:
- `node scripts/runtime-worker-drain.mjs reconcile` 返回 `ok=true`。
- `dryRun=true`。
- 四个 worker 均列出。
- 当前无 stale worker,未执行 apply。
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status` 返回 `ok`。
2026-07-02 11:36:46 +08:00
### 2026-07-02 P5.7 Worker Metrics 采样接入
目标:
- 将 goosed worker 的进程/容器指标写入 Redis runtime state。
- 让 Router score 不只依赖 `active_streams`,同时纳入 CPU / memory / fd pressure。
- 保持 PG 和 MindSpace 零写入。
已完成:
- 新增 `scripts/runtime-worker-metrics.mjs`。
- 支持:
- `status`:采样但不写 Redis。
- `sample`:采样并写入 Redis。
- `--dry-run`:强制只读。
- 对 Docker worker `goosed-prod-1..9` 采集:
2026-07-02 11:36:46 +08:00
- `cpuLoad`
- `memoryPressure`
- `fdPressure`
- `fdCount`
- `containerPids`
- `containerHealth`
- `containerHostPid`
- `metricsSampledAt`
- `tkmind-proxy` Router score 增加:
- `cpu_load * 2`
- `fd_pressure * 2`
- `/api/runtime/status.router.workers[]` 增加 metrics 字段。
- runtime artifact 和 RUNBOOK 增加:
- `node scripts/runtime-worker-metrics.mjs status`
- `node scripts/runtime-worker-metrics.mjs sample`
验证:
- `node --check scripts/runtime-worker-metrics.mjs tkmind-proxy.mjs scripts/build-portal-runtime.mjs` 通过。
- `MEMIND_ENV_FILE=/Users/john/Project/Memind/.env node scripts/runtime-worker-metrics.mjs status` 返回 `ok=true`。
- `MEMIND_ENV_FILE=/Users/john/Project/Memind/.env node scripts/runtime-worker-metrics.mjs sample` 返回 `ok=true`,并写入 Redis runtime state。
- 相关回归测试 58 tests pass。
生产部署:
- 生产代码备份: `/Users/john/Project/memind_backups/20260702-073424-pre-p57-worker-metrics-final`。
- 已覆盖:
- `/Users/john/Project/Memind/server.mjs`
- `/Users/john/Project/Memind/RUNBOOK.txt`
- `/Users/john/Project/Memind/scripts/runtime-worker-drain.mjs`
- `/Users/john/Project/Memind/scripts/runtime-worker-metrics.mjs`
- 已执行 `launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal`。
- 未修改 PG 数据,未修改或删除 `/Users/john/Project/Memind/MindSpace`。
生产验证:
- `node scripts/runtime-worker-metrics.mjs sample` 返回 `ok=true`,全部 `goosed-prod-*` 均 `healthy`。
2026-07-02 11:36:46 +08:00
- `/api/runtime/status.router.workers[]` 已显示:
- `cpuLoad`
- `memoryPressure`
- `fdPressure`
- `fdCount`
- `containerPids`
- `containerHealth`
- `metricsSampledAt`
- 新 `score`
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status` 返回 `ok`。
2026-07-02 11:36:46 +08:00
- `scripts/check-stream-runtime.mjs` 返回 `ok=true`。
- `scripts/check-agent-code-run-entry.mjs` 返回 `ok=true`。
- `scripts/runtime-worker-drain.mjs reconcile` 返回 `ok=true`,`dryRun=true`。
### 2026-07-02 P5.8 自动采样与 SLO 只读报告
目标:
- 将 P5.7 的手动 worker metrics 采样改为 LaunchAgent 定时采样。
- 增加一份生产 SLO 快照脚本,作为每次灰度/发布前后的统一只读检查。
- PG 和 MindSpace 允许只读统计,不删除、不修改用户数据。
已完成:
- 新增 `scripts/install-runtime-metrics-agent.sh`。
- 安装 LaunchAgent: `cn.tkmind.memind-runtime-metrics`。
- 默认每 60 秒执行 `node scripts/runtime-worker-metrics.mjs sample`。
- 日志: `~/Library/Logs/memind-runtime-metrics.log`。
- 新增 `scripts/runtime-slo-report.mjs`。
- 读取 `/api/runtime/status`。
- 只读统计 PG 表行数。
- 只读统计 `/Users/john/Project/Memind/MindSpace` 和 `data/mindspace` 文件/目录/字节数。
- 读取 Redis runtime key 摘要。
- 检查 worker target health、container health、metrics freshness、stream errors、chat/code tool gate。
- 输出 `writes.database=false`、`writes.mindSpace=false`、`writes.redis=false`。
- runtime artifact 和 RUNBOOK 增加:
- `bash scripts/install-runtime-metrics-agent.sh`
- `node scripts/runtime-slo-report.mjs`
验证:
- `node --check scripts/runtime-slo-report.mjs scripts/runtime-worker-metrics.mjs scripts/build-portal-runtime.mjs` 通过。
- `bash -n scripts/install-runtime-metrics-agent.sh` 通过。
- 使用生产 env 只读验证:
- `MEMIND_ENV_FILE=/Users/john/Project/Memind/.env node scripts/runtime-slo-report.mjs`
- 返回 `ok=true`。
- 正确统计 `/Users/john/Project/Memind/MindSpace`。
生产部署:
- 生产脚本备份: `/Users/john/Project/memind_backups/20260702-073927-pre-p58-runtime-slo-agent`。
- 已覆盖:
- `/Users/john/Project/Memind/RUNBOOK.txt`
- `/Users/john/Project/Memind/scripts/runtime-worker-metrics.mjs`
- `/Users/john/Project/Memind/scripts/install-runtime-metrics-agent.sh`
- `/Users/john/Project/Memind/scripts/runtime-slo-report.mjs`
- 已安装 LaunchAgent:
- label: `cn.tkmind.memind-runtime-metrics`
- plist: `/Users/john/Library/LaunchAgents/cn.tkmind.memind-runtime-metrics.plist`
- interval: `60s`
- log: `/Users/john/Library/Logs/memind-runtime-metrics.log`
- LaunchAgent 执行状态:
- `launchctl print gui/$(id -u)/cn.tkmind.memind-runtime-metrics` 显示最近运行退出码为 `0`。
- 日志显示 `runtime-worker-metrics.mjs sample` 成功。
- 生产 SLO 只读报告:
- `ok=true`
- `failures=[]`
- 四个 worker `metricsFresh=true`
- `/Users/john/Project/Memind/MindSpace`: `files=2143`, `dirs=1595`, `bytes=122160654`
- `/Users/john/Project/Memind/data/mindspace`: `files=5281`, `dirs=7651`, `bytes=270214686`
- `writes.database=false`
- `writes.mindSpace=false`
- `writes.redis=false`
- 未修改 PG 用户数据,未删除或修改 `/Users/john/Project/Memind/MindSpace` 用户数据。
### 2026-07-02 P5.9 First-token Latency EWMA
目标:
- 将 H5 SSE 请求从进入 StreamController 到收到首个 upstream SSE body chunk 的耗时写入 Redis。
- 让 Router score、`/api/runtime/status` 和 SLO 报告都能看到 worker 级 first-token 指标。
- 不写 PG,不修改或删除 MindSpace 用户数据。
已完成:
- `tkmind-proxy.mjs` 增加 first-token probe。
- 首个 non-empty upstream chunk 到达时异步记录,不阻塞 token 下发。
- 写入 Redis:
- `worker:{id}:ewma_first_token_ms`
- `worker:{id}:last_first_token_ms`
- `worker:{id}:last_first_token_at`
- `worker:{id}:first_token_count`
- EWMA 计算: `next = previous * 0.8 + sample * 0.2`,首次样本直接使用 sample。
- `/api/runtime/status.router.workers[]` 增加:
- `ewmaFirstTokenMs`
- `lastFirstTokenMs`
- `lastFirstTokenAt`
- `firstTokenCount`
- `scripts/runtime-slo-report.mjs` 增加 first-token 指标展示和基础一致性检查。
验证:
- `node --check tkmind-proxy.mjs scripts/runtime-slo-report.mjs scripts/build-portal-runtime.mjs` 通过。
- `node --test tkmind-proxy.test.mjs message-stream.test.mjs` 通过,12 tests pass。
- `node scripts/build-portal-runtime.mjs` 通过。
生产部署:
- 生产代码备份: `/Users/john/Project/memind_backups/20260702-074436-pre-p59-first-token-ewma`。
- 已覆盖:
- `/Users/john/Project/Memind/server.mjs`
- `/Users/john/Project/Memind/scripts/runtime-slo-report.mjs`
- 已执行 `launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal`。
2026-07-07 10:01:32 +08:00
- 重启窗口内 `m.tkmind.cn` 曾短暂返回 nginx `502`;Portal 随后正常监听 `:8081`,公网 `https://m.tkmind.cn/api/status` 返回 `ok`。
2026-07-02 11:36:46 +08:00
生产验证:
- `/api/runtime/status` 返回 `ok=true`。
- 已观测到真实首 token 样本:
- `goosed-4.ewmaFirstTokenMs=125`
- `goosed-4.lastFirstTokenMs=125`
- `goosed-4.firstTokenCount=1`
- `scripts/runtime-slo-report.mjs` 返回:
- `ok=true`
- `failures=[]`
- `writes.database=false`
- `writes.mindSpace=false`
- `writes.redis=false`
- PG 仅由 SLO 脚本执行只读计数;未修改 PG 用户数据。
- MindSpace 仅由 SLO 脚本执行只读统计;未删除或修改 `/Users/john/Project/Memind/MindSpace` 用户数据。
### 2026-07-02 P6.0 Tool Gateway Queue v0
目标:
- 将 `/agent/runs` 的后台执行变成受控队列,作为 Aider/OpenHands 后续拆分的最小 Tool Gateway。
- 默认限制并发,避免 code task 放量后压垮 goosed 或本机工具进程。
- 增加运行超时和 runtime/SLO 可观测状态。
- 不做 schema migration,不修改 PG 用户数据,不删除或修改 MindSpace 用户数据。
已完成:
- `agent-run-gateway.mjs` 增加本机队列调度器:
- `maxConcurrentRuns`
- `runTimeoutMs`
- `inFlight`
- `pendingDispatches`
- `statusCounts`
- 默认配置:
- `MEMIND_AGENT_RUN_QUEUE_CONCURRENCY` 未设置时为 `1`
- `MEMIND_AGENT_RUN_TIMEOUT_MS` 未设置时为 `900000ms`
- 执行超时时:
- run 标记为 `failed`
- 写入 `timeout` event
- 不重试超时任务
- `/api/runtime/status.toolRuntime.queue` 增加队列状态。
- `scripts/runtime-slo-report.mjs` 增加队列状态展示和基础一致性检查。
- 普通聊天仍不注入 Aider/OpenHands。
- `MEMIND_AGENT_CODE_RUNS_ENABLED` 仍为 false,生产 code run 未放量。
验证:
- `node --check agent-run-gateway.mjs agent-run-routes.mjs server.mjs scripts/runtime-slo-report.mjs` 通过。
- `node --test agent-run-gateway.test.mjs agent-run-routes.test.mjs` 通过,17 tests pass。
- `node scripts/build-portal-runtime.mjs` 通过。
生产部署:
- 生产代码备份: `/Users/john/Project/memind_backups/20260702-074955-pre-p60-tool-queue-v0`。
- 已覆盖:
- `/Users/john/Project/Memind/server.mjs`
- `/Users/john/Project/Memind/scripts/runtime-slo-report.mjs`
- 已执行 `launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal`。
2026-07-07 10:01:32 +08:00
- 重启窗口内 `m.tkmind.cn` 曾短暂返回 nginx `502`;Portal 随后正常监听 `:8081`,公网 `https://m.tkmind.cn/api/status` 返回 `ok`。
2026-07-02 11:36:46 +08:00
生产验证:
- `/api/runtime/status.toolRuntime.queue` 返回:
- `maxConcurrentRuns=1`
- `runTimeoutMs=900000`
- `inFlight=0`
- `pendingDispatches=0`
- `statusCounts={}`
- `/api/runtime/status.toolRuntime.codeRunsEnabled=false`。
- `scripts/runtime-slo-report.mjs` 返回:
- `ok=true`
- `failures=[]`
- `writes.database=false`
- `writes.mindSpace=false`
- `writes.redis=false`
- PG 仅由 SLO 脚本执行只读计数;未修改 PG 用户数据。
- MindSpace 仅由 SLO 脚本执行只读统计;未删除或修改 `/Users/john/Project/Memind/MindSpace` 用户数据。
### 2026-07-02 P6.1 Tool Worker v1 骨架
目标:
- 为 `/agent/runs` 增加外部 worker 接管能力。
- Portal 默认仍保持内部 `autoDispatch=true`,避免生产行为突然切换。
- 外部 worker 默认只在显式启动时运行,不安装 LaunchAgent,不自动放量。
- 不做 schema migration,不修改 PG 用户数据,不删除或修改 MindSpace 用户数据。
已完成:
- `agent-run-gateway.mjs` 增加:
- `autoDispatch` 显式配置。
- `dispatchQueuedRuns({ limit })`,扫描现有 `queued/retryable` run 并走同一队列控制。
- 队列满载时不预取 DB 任务,等待下一轮 poll。
- `server.mjs` 增加 `MEMIND_AGENT_RUN_AUTODISPATCH` 配置:
- 默认 `1`,保持 Portal 内部 dispatch。
- 后续外部 worker canary 时可设为 `0`。
- 新增 `scripts/agent-run-worker.mjs`:
- `--status`: 只读查看队列状态。
- `--once`: 扫描并调度一批 queued/retryable run。
- 常驻模式: 按 `MEMIND_AGENT_RUN_WORKER_POLL_MS` 轮询。
- `scripts/build-portal-runtime.mjs` 将 worker 打包成单文件 runtime:
- `.runtime/portal/scripts/agent-run-worker.mjs`
- `.env.example` 增加:
- `MEMIND_AGENT_RUN_AUTODISPATCH`
- `MEMIND_AGENT_RUN_WORKER_POLL_MS`
- `MEMIND_AGENT_RUN_WORKER_BATCH_SIZE`
- RUNBOOK 增加:
- `node scripts/agent-run-worker.mjs --status`
- `node scripts/agent-run-worker.mjs --once`
验证:
- `node --check agent-run-gateway.mjs server.mjs scripts/agent-run-worker.mjs scripts/build-portal-runtime.mjs` 通过。
- `node --test agent-run-gateway.test.mjs agent-run-routes.test.mjs` 通过,19 tests pass。
- `node scripts/build-portal-runtime.mjs` 通过,worker 被打成单文件 runtime。
生产部署:
- 生产代码备份: `/Users/john/Project/memind_backups/20260702-075904-pre-p61-agent-run-worker-v1`。
- 已覆盖:
- `/Users/john/Project/Memind/server.mjs`
- `/Users/john/Project/Memind/scripts/agent-run-worker.mjs`
- `/Users/john/Project/Memind/RUNBOOK.txt`
- 已执行 `launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal`。
2026-07-07 10:01:32 +08:00
- 重启窗口内 `m.tkmind.cn` 曾短暂返回 nginx `502`;Portal 随后正常监听 `:8081`,公网 runtime status 返回 `ok=true`。
2026-07-02 11:36:46 +08:00
生产验证:
- `/api/runtime/status.toolRuntime.queue.autoDispatch=true`。
- `/api/runtime/status.toolRuntime.codeRunsEnabled=false`。
- `node scripts/agent-run-worker.mjs --status` 返回 `ok=true`,且 worker 自身 `autoDispatch=false`。
- `scripts/runtime-slo-report.mjs` 返回:
- `ok=true`
- `failures=[]`
- `writes.database=false`
- `writes.mindSpace=false`
- `writes.redis=false`
- PG 仅由 status/SLO 执行只读检查;未修改 PG 用户数据。
- MindSpace 仅由 SLO 脚本执行只读统计;未删除或修改 `/Users/john/Project/Memind/MindSpace` 用户数据。
### 2026-07-02 P5.10 First-token Latency 趋势报表
目标:
- 将 P5.9 的 worker-level EWMA 扩展成短窗口趋势指标。
- 在 Redis runtime state 中保留最近 1 小时 first-token 样本。
- `/api/runtime/status` 和 SLO 报告展示最近 5 分钟/1 小时 p50、p95。
- 不写 PG,不修改或删除 MindSpace 用户数据。
已完成:
- `tkmind-proxy.mjs` 在首个 upstream SSE chunk 到达时写入 Redis sorted set:
- `worker:{id}:first_token_samples`
- sorted set score 为时间戳,member 带 latency sample。
- 自动清理 1 小时前样本。
- key TTL 为 2 小时。
- `/api/runtime/status.router.workers[]` 增加:
- `firstToken5m.count`
- `firstToken5m.p50Ms`
- `firstToken5m.p95Ms`
- `firstToken1h.count`
- `firstToken1h.p50Ms`
- `firstToken1h.p95Ms`
- `scripts/runtime-slo-report.mjs` 增加 first-token 窗口指标输出。
验证:
- `node --check tkmind-proxy.mjs scripts/runtime-slo-report.mjs scripts/build-portal-runtime.mjs` 通过。
- `node --test tkmind-proxy.test.mjs message-stream.test.mjs agent-run-gateway.test.mjs agent-run-routes.test.mjs` 通过,31 tests pass。
- `node scripts/build-portal-runtime.mjs` 通过。
生产部署:
- 生产代码备份: `/Users/john/Project/memind_backups/20260702-080546-pre-p510-first-token-trends`。
- 已覆盖:
- `/Users/john/Project/Memind/server.mjs`
- `/Users/john/Project/Memind/scripts/runtime-slo-report.mjs`
- `/Users/john/Project/Memind/scripts/agent-run-worker.mjs`
- 已执行 `launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal`。
2026-07-07 10:01:32 +08:00
- 重启窗口内 `m.tkmind.cn` 曾短暂返回 nginx `502`;Portal 随后正常监听 `:8081`,公网 `https://m.tkmind.cn/api/status` 返回 `ok`。
2026-07-02 11:36:46 +08:00
生产验证:
- `/api/runtime/status` 返回 `ok=true`。
- 已观测到真实窗口样本:
- `goosed-4.ewmaFirstTokenMs=101`
- `goosed-4.firstToken5m.count=1`
- `goosed-4.firstToken5m.p50Ms=188`
- `goosed-4.firstToken5m.p95Ms=188`
- `goosed-4.firstToken1h.count=1`
- `goosed-4.firstToken1h.p50Ms=188`
- `goosed-4.firstToken1h.p95Ms=188`
- `scripts/runtime-slo-report.mjs` 返回:
- `ok=true`
- `failures=[]`
- `writes.database=false`
- `writes.mindSpace=false`
- `writes.redis=false`
- PG 仅由 SLO 脚本执行只读计数;未修改 PG 用户数据。
- MindSpace 仅由 SLO 脚本执行只读统计;未删除或修改 `/Users/john/Project/Memind/MindSpace` 用户数据。
### 2026-07-02 P5.11 SLO 日报
目标:
- 将即时 SLO 快照落盘为运维报告,方便跨天追踪。
- 默认仍只读;只有显式 `--write-report` 时写报告目录。
- 不写 PG,不修改或删除 MindSpace 用户数据。
已完成:
- `scripts/runtime-slo-report.mjs` 增加:
- `--write-report`
- `--report-dir <dir>`
- Markdown 报告
- JSON 报告
- 默认报告目录:
- `/Users/john/Project/Memind/reports/runtime-slo`
- 报告内容包含:
- runtime ok/failures
- worker health
- active streams
- stream errors
- first-token 5m/1h p50/p95
- tool queue state
- writes 声明
验证:
- `node --check scripts/runtime-slo-report.mjs` 通过。
- `node scripts/runtime-slo-report.mjs --help` 正常输出。
- 使用生产 env 执行 `--write-report` 返回 `ok=true`。
生产部署:
- 生产脚本备份: `/Users/john/Project/memind_backups/20260702-080820-pre-p511-slo-daily-report`。
- 已覆盖:
- `/Users/john/Project/Memind/scripts/runtime-slo-report.mjs`
- 未重启 Portal。
生产验证:
- `node scripts/runtime-slo-report.mjs --write-report` 返回:
- `ok=true`
- `failures=[]`
- `reportFiles.json=/Users/john/Project/Memind/reports/runtime-slo/2026-07-02T00-08-26-316Z.json`
- `reportFiles.markdown=/Users/john/Project/Memind/reports/runtime-slo/2026-07-02T00-08-26-316Z.md`
- `writes.database=false`
- `writes.mindSpace=false`
- `writes.redis=false`
- `writes.report=true`
- PG 仅由 SLO 脚本执行只读计数;未修改 PG 用户数据。
- MindSpace 仅由 SLO 脚本执行只读统计;未删除或修改 `/Users/john/Project/Memind/MindSpace` 用户数据。
### 2026-07-02 P5.12 SLO 日报定时器
目标:
- 将 P5.11 的手动 SLO 日报落盘变成每日自动执行。
- 只写运维报告目录,不写 PG,不修改或删除 MindSpace 用户数据。
已完成:
- 新增 `scripts/install-runtime-slo-report-agent.sh`。
- 安装 LaunchAgent:
- label: `cn.tkmind.memind-runtime-slo-report`
- plist: `/Users/john/Library/LaunchAgents/cn.tkmind.memind-runtime-slo-report.plist`
- schedule: 每日 `23:55`
- command: `node scripts/runtime-slo-report.mjs --write-report --report-dir /Users/john/Project/Memind/reports/runtime-slo`
- log: `/Users/john/Library/Logs/memind-runtime-slo-report.log`
- `.env.example` 增加:
- `MEMIND_RUNTIME_REPORT_DIR`
- `MEMIND_RUNTIME_SLO_HOUR`
- `MEMIND_RUNTIME_SLO_MINUTE`
- runtime artifact 和 RUNBOOK 增加:
- `bash scripts/install-runtime-slo-report-agent.sh`
验证:
- `bash -n scripts/install-runtime-slo-report-agent.sh` 通过。
- `node --check scripts/build-portal-runtime.mjs scripts/runtime-slo-report.mjs` 通过。
- `node scripts/build-portal-runtime.mjs` 通过。
生产部署:
- 生产脚本备份: `/Users/john/Project/memind_backups/20260702-081100-pre-p512-slo-report-agent`。
- 已覆盖:
- `/Users/john/Project/Memind/RUNBOOK.txt`
- `/Users/john/Project/Memind/scripts/install-runtime-slo-report-agent.sh`
- 已执行:
- `bash scripts/install-runtime-slo-report-agent.sh`
- 未重启 Portal。
生产验证:
- `launchctl print gui/$(id -u)/cn.tkmind.memind-runtime-slo-report`:
- `last exit code = 0`
- `runs = 1`
- `Hour = 23`
- `Minute = 55`
- 已生成日报:
- `/Users/john/Project/Memind/reports/runtime-slo/2026-07-02T00-11-06-214Z.json`
- `/Users/john/Project/Memind/reports/runtime-slo/2026-07-02T00-11-06-214Z.md`
- 日志中 SLO:
- `failures=[]`
- `writes.database=false`
- `writes.mindSpace=false`
- `writes.redis=false`
- `writes.report=true`
- PG 仅由 SLO 脚本执行只读计数;未修改 PG 用户数据。
- MindSpace 仅由 SLO 脚本执行只读统计;未删除或修改 `/Users/john/Project/Memind/MindSpace` 用户数据。
### 2026-07-02 P6.2 Tool Worker Canary
目标:
- 短窗口验证外部 `agent-run-worker.mjs --once` 能接管真实 `/agent/runs` queued run。
- 不打开 code run,不启用 Aider/OpenHands。
- 不删除 PG 数据,不修改或删除 MindSpace 用户数据。
执行:
- 生产 `.env` 临时增加:
- `MEMIND_AGENT_RUN_AUTODISPATCH=0`
- 已执行 `launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal`。
- runtime status 确认:
- `toolRuntime.queue.autoDispatch=false`
- `toolRuntime.codeRunsEnabled=false`
- 使用 `testuser1` 创建 chat-mode canary run。
- 执行:
- `node scripts/agent-run-worker.mjs --once --limit 1`
过程中发现并修复:
- `--once` 原先调度后会立即关闭 DB pool,导致异步 run 标记状态时报 `Pool is closed`。
- 已修复为等待 `inFlight=0` 后再退出。
- 外部 worker 模式下 retry 不应使用进程内 `setTimeout`。
- 已修复为 `autoDispatch=false` 时保留 `retryable`,由下一轮 worker poll 接管。
- `--once` 完成后仍有长连接句柄导致 CLI 不退出。
- 已修复为 `--status/--once` 完成后显式退出。
- 手工 canary payload 需要完整 goosed message 字段:
- `metadata.userVisible=true`
- `metadata.agentVisible=true`
- `created=<timestamp>`
- `request_id=<uuid>`
验证:
- `node --test agent-run-gateway.test.mjs agent-run-routes.test.mjs` 通过,19 tests pass。
- 成功 canary:
- run id: `8e64ff4b-c043-475e-b60b-ae7466111da9`
- session id: `20260702_2`
- final status: `succeeded`
- attempts: `3`
- 失败的早期 canary 保留为审计记录:
- run id: `9f0c713a-0aff-4e67-bf86-0ea2952e9326`
- final status: `failed`
- 原因: 手工 payload 缺字段
恢复:
- 已移除生产 `.env` 中的 `MEMIND_AGENT_RUN_AUTODISPATCH=0`。
- 已重启 Portal。
- runtime status 确认:
- `toolRuntime.queue.autoDispatch=true`
- `toolRuntime.queue.inFlight=0`
- `toolRuntime.queue.pendingDispatches=0`
- `toolRuntime.codeRunsEnabled=false`
- PG 仅新增 canary run/session/event 审计记录;未删除 PG 用户数据。
- 未修改或删除 `/Users/john/Project/Memind/MindSpace` 用户数据。
### 2026-07-02 P4.10 Code Run Canary
目标:
- 短窗口验证 code-mode agent run 可以被外部 worker 接管并完成。
- 只对白名单用户打开后端 code-run 开关。
- 不打开普通聊天 autodetect,不让普通聊天注入 Aider/OpenHands 工具。
- 验证后恢复默认关闭。
保护边界:
- 生产 `.env` 修改前备份:
- `/Users/john/Project/memind_backups/20260702-083225-pre-p410-code-run-canary`
- PG 只新增 canary run/session/event 审计记录。
- 未删除或修改 PG 用户数据。
- 未修改或删除 `/Users/john/Project/Memind/MindSpace` 用户数据。
执行:
- 生产 `.env` 临时增加:
- `MEMIND_AGENT_CODE_RUNS_ENABLED=1`
- 已执行 `launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal`。
- runtime status 确认:
- `toolRuntime.codeRunsEnabled=true`
- `toolRuntime.chatInjectsCodeTools=false`
- `toolRuntime.queue.autoDispatch=true`
- `toolRuntime.queue.inFlight=0`
- 使用 john 白名单用户创建 code-mode canary run。
- 执行:
- `MEMIND_AGENT_RUN_WORKER_DRAIN_WAIT_MS=180000 node scripts/agent-run-worker.mjs --once --limit 1`
验证:
- 成功 canary:
- user id: `a6fb1e97-2b0f-447b-b138-4561d8e5c53e`
- run id: `805e7422-175e-4eb7-8fb2-df0c29f79c0c`
- request id: `b7ef57f3-725c-4a3a-b214-997b29afb86f`
- session id: `20260702_3`
- final status: `succeeded`
- attempts: `1`
- run events 已记录:
- `queued`
- `running`
- `session_started`
- `succeeded`
恢复:
- 已移除生产 `.env` 中的 `MEMIND_AGENT_CODE_RUNS_ENABLED=1`。
- 已重启 Portal。
- runtime status 确认:
- `toolRuntime.codeRunsEnabled=false`
- `toolRuntime.chatInjectsCodeTools=false`
- `toolRuntime.queue.autoDispatch=true`
- `toolRuntime.queue.inFlight=0`
- `toolRuntime.queue.pendingDispatches=0`
- `toolRuntime.queue.statusCounts={}`
2026-07-07 10:01:32 +08:00
- `m.tkmind.cn` runtime health 仍为 `ok=true`。
2026-07-02 11:36:46 +08:00
### 2026-07-02 P5.13 Runtime SLO Report Retention
目标:
- 给 `reports/runtime-slo` 日报目录增加保留周期。
- 只清理运维报告目录下的过期 `.json/.md`。
- 不触碰 PG,不触碰 MindSpace。
执行:
- 源码分支新增提交:
- `8a53dff chore: prune runtime slo reports`
- `scripts/runtime-slo-report.mjs` 新增:
- `--prune`
- `--retention-days <days>`
- `MEMIND_RUNTIME_SLO_RETENTION_DAYS`,默认 `30`
- `scripts/install-runtime-slo-report-agent.sh` 默认安装参数:
- `--write-report`
- `--prune`
- `--retention-days 30`
- 生产运行目录脚本已同步。
- 生产脚本修改前备份:
- `/Users/john/Project/memind_backups/20260702-083917-pre-p513-slo-prune`
验证:
- LaunchAgent:
- `cn.tkmind.memind-runtime-slo-report`
- daily `23:55`
- arguments 已包含 `--prune --retention-days 30`
- last exit code: `0`
- 最新报告:
- `/Users/john/Project/Memind/reports/runtime-slo/2026-07-02T00-39-23-109Z.json`
- `ok=true`
- `writes.database=false`
- `writes.mindSpace=false`
- `writes.redis=false`
- `writes.report=true`
- `writes.reportPrune=true`
- `reportPrune.deleted=[]`
- `reportPrune.errors=[]`
边界:
- 清理范围只限 report dir 顶层 `.json/.md`。
- 不递归清理。
- 不删除非报告文件。
- 未删除 PG 数据。
- 未修改或删除 `/Users/john/Project/Memind/MindSpace` 用户数据。
### 2026-07-02 P4.11 Code Run User Whitelist Gate
目标:
- 为 H5 页面编辑 UI 灰度补齐用户级白名单。
- 防止只开前端 `VITE_AGENT_CODE_RUNS_ENABLED=1` 后,所有页面编辑子聊天都尝试 code mode。
- 后端同步增加用户级 gate,防止客户端绕过前端直接 POST `tool_mode=code`。
实现:
- 源码分支新增提交:
- `c7d8140 feat: gate code runs by user whitelist`
- 前端新增:
- `VITE_AGENT_CODE_RUNS_USER_IDS`
- `agentCodeRunsEnabledForUser(userId)`
- 普通 H5 chat 和页面编辑子聊天调用 `resolveAgentRunOptions()` 时传入当前 user id
- 后端新增:
- `MEMIND_AGENT_CODE_RUNS_USER_IDS`
- `POST /agent/runs` 在 `tool_mode=code` 时校验用户白名单
- 白名单为空时保持原有全局开关语义
验证:
- `node --test agent-run-routes.test.mjs agent-run-gateway.test.mjs`:
- 21 tests pass
- `npm run build`:
- 通过
- `scripts/check-agent-code-run-entry.mjs`:
- `ok=true`
- `MEMIND_AGENT_CODE_RUNS_ENABLED` 未设置
- `VITE_AGENT_CODE_RUNS_ENABLED` 未设置
- `MEMIND_AGENT_CODE_RUNS_USER_IDS` 未设置
- `VITE_AGENT_CODE_RUNS_USER_IDS` 未设置
- `runtimePolicy.codeRunsEnabled=false`
- `runtimePolicy.chatInjectsCodeTools=false`
生产发布:
- 已发布默认关闭 runtime:
- release id: `20260702-085001-a0af049`
- git head: `a0af049fa7d5a5a9cf88a09449ab4d3bd43f5cb3`
- 发布脚本修复:
- `a0af049 fix: pass mindspace link bypass to prod release`
- `ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES=1` 现在会传到远端 post-check
- 已按用户要求忽略既有 MindSpace 公开页 docx 附件缺失问题,不做修复
- 发布脚本已完成:
- runtime artifact 校验
- live 全目录备份
- 持久目录单独备份
- Portal restart
- health check
- 105 tunnel health check
- 备份:
- `/Users/john/Project/backups/memind/memind-full-20260702-085001-a0af049-before.tar.gz`
- `/Users/john/Project/backups/memind/memind-persisted-20260702-085001-a0af049-before.tar.gz`
- `/Users/john/Project/archives/Memind-source-before-20260702-085001-a0af049`
发布后状态:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status`: `ok`
2026-07-02 11:36:46 +08:00
- `toolRuntime.codeRunsEnabled=false`
- `toolRuntime.chatInjectsCodeTools=false`
- `toolRuntime.queue.autoDispatch=true`
- `toolRuntime.queue.inFlight=0`
- `toolRuntime.queue.pendingDispatches=0`
- SLO report LaunchAgent 仍包含:
- `--write-report --prune --retention-days 30`
注意:
- 既有生产 MindSpace 公开页缺失 4 个 docx 附件链接,按用户要求忽略,不修复。
- 当前生产已确认运行 release `20260702-085001-a0af049`。
- 未删除 PG 数据。
- 未修改或删除 `/Users/john/Project/Memind/MindSpace` 用户数据。
- 下一步如做真正 H5 UI canary,需要短窗口同时打开:
- `VITE_AGENT_CODE_RUNS_ENABLED=1`
- `VITE_AGENT_CODE_RUNS_USER_IDS=<john user id>`
- `MEMIND_AGENT_CODE_RUNS_ENABLED=1`
- `MEMIND_AGENT_CODE_RUNS_USER_IDS=<john user id>`
- 继续保持 `VITE_AGENT_CODE_RUNS_AUTODETECT` 关闭
### 2026-07-02 P4.12 H5 Page Edit UI Canary
目标:
- 短窗口打开 H5 页面编辑 code-mode UI 灰度。
- 只对白名单 john 用户生效。
- 普通聊天 autodetect 继续关闭。
- 验证后恢复默认关闭。
保护边界:
- 生产 `.env` 修改前备份:
- `/Users/john/Project/memind_backups/20260702-085302-pre-p412-h5-ui-canary`
- PG 仅新增 canary run/session/event 审计记录。
- 未删除或修改 PG 用户数据。
- 未修改或删除 `/Users/john/Project/Memind/MindSpace` 用户数据。
canary 打开:
- 生产 `.env` 短窗口增加:
- `MEMIND_AGENT_CODE_RUNS_ENABLED=1`
- `MEMIND_AGENT_CODE_RUNS_USER_IDS=a6fb1e97-2b0f-447b-b138-4561d8e5c53e`
- 前端 canary bundle 构建环境:
- `VITE_AGENT_CODE_RUNS_ENABLED=1`
- `VITE_AGENT_CODE_RUNS_USER_IDS=a6fb1e97-2b0f-447b-b138-4561d8e5c53e`
- 未设置 `VITE_AGENT_CODE_RUNS_AUTODETECT`
- canary release:
- `20260702-085319-a0af049`
- canary 发布备份:
- `/Users/john/Project/backups/memind/memind-full-20260702-085319-a0af049-before.tar.gz`
- `/Users/john/Project/backups/memind/memind-persisted-20260702-085319-a0af049-before.tar.gz`
- `/Users/john/Project/archives/Memind-source-before-20260702-085319-a0af049`
canary 验证:
- frontend bundle 已固化:
- `VITE_AGENT_CODE_RUNS_ENABLED=true`
- john user id whitelist
- `page_edit_code_task`
- `h5_chat_code_task`
- runtime status:
- `toolRuntime.codeRunsEnabled=true`
- `toolRuntime.chatInjectsCodeTools=false`
- `toolRuntime.queue.inFlight=0`
- `toolRuntime.queue.pendingDispatches=0`
- canary run:
- run id: `825720c9-330d-447d-a6ed-afeabbf6e14c`
- request id: `03691942-52c1-4612-9588-68840f77b1a6`
- user id: `a6fb1e97-2b0f-447b-b138-4561d8e5c53e`
- session id: `20260702_4`
- status: `succeeded`
- attempts: `1`
- run events:
- `queued`
- `running`
- `session_started`
- `succeeded`
恢复默认关闭:
- 已移除生产 `.env` 中:
- `MEMIND_AGENT_CODE_RUNS_ENABLED`
- `MEMIND_AGENT_CODE_RUNS_USER_IDS`
- 已发布默认关闭 bundle:
- release id: `20260702-085706-a0af049`
- 恢复发布备份:
- `/Users/john/Project/backups/memind/memind-full-20260702-085706-a0af049-before.tar.gz`
- `/Users/john/Project/backups/memind/memind-persisted-20260702-085706-a0af049-before.tar.gz`
- `/Users/john/Project/archives/Memind-source-before-20260702-085706-a0af049`
- 恢复后验证:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status`: `ok`
2026-07-02 11:36:46 +08:00
- `toolRuntime.codeRunsEnabled=false`
- `toolRuntime.chatInjectsCodeTools=false`
- `toolRuntime.queue.autoDispatch=true`
- `toolRuntime.queue.inFlight=0`
- `toolRuntime.queue.pendingDispatches=0`
- `.env` 无 `MEMIND_AGENT_CODE_RUNS_*`
- frontend bundle 不再包含 john code-run whitelist
### 2026-07-02 P5.14 Worker Self-heartbeat Sidecar
目标:
- 将 worker freshness 从单纯外部 60 秒 metrics 采样,升级为常驻 sidecar 主动 heartbeat。
- Router 和 SLO 能直接看到 `heartbeatSource=sidecar`、`heartbeatFresh`、`heartbeatOk`、`heartbeatLatencyMs`。
- 不修改 PG,不修改 MindSpace。
实现:
- 源码分支新增提交:
- `e24e6ca feat: add runtime worker heartbeat sidecar`
- 新增脚本:
- `scripts/runtime-worker-heartbeat.mjs`
- `scripts/install-runtime-heartbeat-agent.sh`
- LaunchAgent:
- `cn.tkmind.memind-runtime-heartbeat`
- mode: `serve`
- interval: `15000ms`
- timeout: `5000ms`
- ttl: `45000ms`
- log: `/Users/john/Library/Logs/memind-runtime-heartbeat.log`
- Redis keys:
- `worker:{id}:heartbeat`
- `worker:{id}:heartbeat_at`
- `worker:{id}:heartbeat_source`
- `worker:{id}:heartbeat_ok`
- `worker:{id}:heartbeat_status_code`
- `worker:{id}:heartbeat_latency_ms`
- `worker:{id}:heartbeat_error`
- Router `/api/runtime/status` 已展示 heartbeat 字段。
- SLO 报告已检查:
- `heartbeatFresh`
- `heartbeatOk`
生产发布:
- release id: `20260702-090343-e24e6ca`
- git head: `e24e6ca4a72b4cc16a2074d205727f6d131cff0d`
- 发布备份:
- `/Users/john/Project/backups/memind/memind-full-20260702-090343-e24e6ca-before.tar.gz`
- `/Users/john/Project/backups/memind/memind-persisted-20260702-090343-e24e6ca-before.tar.gz`
- `/Users/john/Project/archives/Memind-source-before-20260702-090343-e24e6ca`
验证:
- LaunchAgent:
- `state = running`
- pid: `51413`
- program: `/opt/homebrew/opt/node@24/bin/node`
- args: `/Users/john/Project/Memind/scripts/runtime-worker-heartbeat.mjs serve`
- heartbeat log:
- `goosed-1`: `ok=true`, `statusCode=200`, `latencyMs=62`
- `goosed-2`: `ok=true`, `statusCode=200`, `latencyMs=46`
- `goosed-3`: `ok=true`, `statusCode=200`, `latencyMs=46`
- `goosed-4`: `ok=true`, `statusCode=200`, `latencyMs=53`
- `/api/runtime/status`:
- all workers `heartbeatSource=sidecar`
- all workers `heartbeatOk=true`
- all workers `heartbeatStatusCode=200`
- `scripts/runtime-slo-report.mjs`:
- `ok=true`
- `failures=[]`
- all workers `heartbeatFresh=true`
- all workers `metricsFresh=true`
边界:
- 未删除 PG 数据。
- 未修改或删除 `/Users/john/Project/Memind/MindSpace` 用户数据。
- 原 60 秒 metrics agent 保留,用于 CPU/memory/fd 指标。
- 新 sidecar 只负责更高频 freshness 和 `/status` 可用性。
### 2026-07-02 P6.3 Tool Gateway 协议化
目标:
- 将 Aider/OpenHands 从 goosed session extension 继续外移为独立 Tool Gateway 执行协议。
- 复用现有 `h5_agent_runs` 和 `h5_agent_run_events`,不新增生产 schema migration。
- 默认关闭,只有 `MEMIND_TOOL_GATEWAY_ENABLED=1` 且 `toolMode=code` 时才走外部执行器。
- 不删除、不修改 PG 用户数据,不删除、不修改 `/Users/john/Project/Memind/MindSpace` 用户文件。
当前落地:
- 新增 `tool-gateway.mjs`:
- protocol: `agent-run-v1`
- executors: `aider`, `openhands`
- default executor: `aider`
- `repo_refactor,multi_file,complex_repo` 默认走 `openhands`
- `MEMIND_TOOL_GATEWAY_DRY_RUN=1` 支持只生成启动计划不执行进程
- `agent-run-gateway.mjs`:
- `chat` 和默认关闭状态继续走 Goose 原链路
- `code` + Tool Gateway enabled 时写入事件:
- `tool_gateway_dispatch`
- `tool_gateway_result`
- 外部工具任务完成后标记 run succeeded,不再强制创建 goosed session
- `server.mjs` 和 `scripts/agent-run-worker.mjs`:
- 已接入同一个 Tool Gateway 对象
- 独立 worker 后续可接管 code run 队列
- `scripts/check-tool-runtime.mjs`:
- 已输出 Tool Gateway enable/dry-run/protocol/default executor 状态
- 测试:
- `node --test agent-run-gateway.test.mjs tool-gateway.test.mjs`
- 结果: `15 passed`
- 源码提交:
- `21e03d8 feat: add tool gateway protocol`
默认环境:
- `MEMIND_TOOL_GATEWAY_ENABLED` 未设置时为关闭。
- `MEMIND_TOOL_GATEWAY_DRY_RUN` 未设置时为关闭。
- 生产发布后若不显式设置 env,不会自动执行 Aider/OpenHands 外部进程。
生产发布:
- release id: `20260702-092457-21e03d8`
- git head: `21e03d86b5e8ec28a0bd511c484d0618476528a6`
- 发布前:
- 全目录备份已完成
- 持久目录备份已完成
- 未删除 PG 数据
- 未删除或改写 MindSpace 用户数据
- 发布验证:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status`: `ok`
- `https://m.tkmind.cn/api/runtime/status`: `ok=true`
2026-07-02 11:36:46 +08:00
- `toolRuntime.codeRunsEnabled=false`
- `toolRuntime.chatInjectsCodeTools=false`
- `toolRuntime.queue.toolGateway.enabled=false`
- `toolRuntime.queue.toolGateway.protocol=agent-run-v1`
- all workers `heartbeatSource=sidecar`
- all workers `heartbeatOk=true`
- `scripts/runtime-slo-report.mjs`: `ok=true`, `failures=[]`
- `.env` 无 `MEMIND_TOOL_GATEWAY_*`
- `.env` 无 `MEMIND_AGENT_CODE_RUNS_*`
Dry-run canary:
- canary window:
- temporarily enabled `MEMIND_AGENT_CODE_RUNS_ENABLED=1`
- temporarily enabled `MEMIND_AGENT_CODE_RUNS_USER_IDS=a6fb1e97-2b0f-447b-b138-4561d8e5c53e`
- temporarily enabled `MEMIND_TOOL_GATEWAY_ENABLED=1`
- temporarily enabled `MEMIND_TOOL_GATEWAY_DRY_RUN=1`
- `.env` backup:
- `/Users/john/Project/memind_backups/20260702-0927-p63-tool-gateway-dryrun/.env.before`
- canary run:
- run id: `15dda47e-c282-493b-99df-8a59127404d5`
- request id: `p63-dryrun-20260702093311`
- user id: `a6fb1e97-2b0f-447b-b138-4561d8e5c53e`
- status: `succeeded`
- attempts: `1`
- `agent_session_id=null`
- event proof:
- `queued`
- `running`
- `tool_gateway_dispatch`
- `protocol=agent-run-v1`
- `taskType=small_patch`
- `tool_gateway_result`
- `dryRun=true`
- `executor=aider`
- `exitCode=null`
- `succeeded`
- cleanup:
- restored `.env` from backup
- revoked temporary login session
- restarted Portal
- final `toolRuntime.codeRunsEnabled=false`
- final `toolRuntime.queue.toolGateway.enabled=false`
- final SLO: `ok=true`, `failures=[]`
边界:
- 本阶段未启用生产外部执行器。
- 本阶段未修改 PG schema。
- 本阶段未删除 PG 数据。
- 本阶段未删除或改写 MindSpace 用户数据。
- P5.15 Active Stream TTL Reconcile 已按用户要求跳过。
### 2026-07-02 P6.4 Tool Gateway Real Canary
目标:
- 在专用 canary workspace / 空 git 仓库内验证真实 Aider executor。
- 不打开普通生产 code run。
- 不使用生产用户真实页面。
- 不删除 PG 数据,不删除或改写 `/Users/john/Project/Memind/MindSpace` 用户数据。
第一次真实 canary 发现:
- canary dir:
- `/Users/john/Project/memind_canary/tool-gateway-p64-20260702-093648`
- run id:
- `ddfc2666-eb56-48aa-8c7c-a21d99b2ae7c`
- request id:
- `p64-real-aider-20260702013704`
- event proof:
- `tool_gateway_dispatch`
- `tool_gateway_result`
- `dryRun=false`
- `executor=aider`
- `exitCode=0`
- 实际问题:
- Aider 进程退出码为 0,但 `.aider.chat.history.md` 显示 LiteLLM 报错:
- `LLM Provider NOT provided`
- model was `deepseek-reasoner`
- 未生成目标 `P64_CANARY.md`,只生成 `.aider*` 和 `.gitignore`。
- 结论:
- Tool Gateway 进程协议跑通。
- Aider 模型名映射不正确,真实产物 canary 未通过。
修复:
- 源码提交:
- `0cfd2d3 fix: map deepseek models for aider`
- 修复内容:
- Aider executor 对 `custom_deepseek` 自动使用 `deepseek/<model>`。
- `deepseek-reasoner` 映射为 `deepseek/deepseek-reasoner`。
- 测试改为不依赖本机是否安装 `aider`。
- 测试:
- `node --test llm-providers.test.mjs tool-gateway.test.mjs agent-run-gateway.test.mjs`
- 结果: `43 passed`
- 生产发布:
- release id: `20260702-094216-0cfd2d3`
- git head: `0cfd2d3d2adf404d4ad7f68dcc37c8074c14e900`
- 发布脚本在 legacy `m.tkmind.cn` tunnel 检查处返回 1,但 Portal runtime 已发布并通过健康检查。
- 已手动清理占用 8081 的孤儿 Portal node 进程,让 LaunchAgent 重新接管。
- 最终监听:
- LaunchAgent `cn.tkmind.memind-portal`
- pid: `86462`
- port: `8081`
第二次真实 canary:
- canary dir:
- `/Users/john/Project/memind_canary/tool-gateway-p64-fixed-20260702-094739`
- run id:
- `7daff187-1f60-4e67-9848-a404274a0455`
- request id:
- `p64-real-aider-fixed-20260702014740`
- status:
- `succeeded`
- attempts: `1`
- `agent_session_id=null`
- event proof:
- `queued`
- `running`
- `tool_gateway_dispatch`
- `protocol=agent-run-v1`
- workingDir is the canary repo
- `tool_gateway_result`
- `dryRun=false`
- `executor=aider`
- `exitCode=0`
- `succeeded`
- file proof:
- generated file:
- `/Users/john/Project/memind_canary/tool-gateway-p64-fixed-20260702-094739/P64_CANARY.md`
- content:
- `P6.4 real Tool Gateway canary succeeded.`
恢复/最终状态:
- 生产 `.env` 无 `MEMIND_TOOL_GATEWAY_*`。
- 生产 `.env` 无 `MEMIND_AGENT_CODE_RUNS_*`。
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/runtime/status`:
2026-07-02 11:36:46 +08:00
- `ok=true`
- `toolRuntime.codeRunsEnabled=false`
- `toolRuntime.queue.toolGateway.enabled=false`
- all workers `heartbeatSource=sidecar`
- all workers `heartbeatOk=true`
- `scripts/runtime-slo-report.mjs`:
- `ok=true`
- `failures=[]`
边界:
- 未做 PG schema migration。
- 仅新增 canary run/event 审计记录。
- 未删除 PG 数据。
- 未删除或改写生产 MindSpace 用户数据。
- 真实 Aider 文件写入只发生在 `/Users/john/Project/memind_canary/...`。
### 2026-07-02 P6.5 OpenHands Real Canary
目标:
- 在专用 canary workspace / 空 git 仓库内验证真实 OpenHands executor。
- 使用 `taskType=repo_refactor` 触发 Tool Gateway 选择 `openhands`。
- 不打开普通生产 code run。
- 不使用生产用户真实页面。
- 不删除 PG 数据,不删除或改写 `/Users/john/Project/Memind/MindSpace` 用户数据。
执行:
- OpenHands CLI:
- version: `1.16.0`
- SDK: `1.21.0`
- launch plan:
- command: `openhands`
- args:
- `--headless`
- `--json`
- `--override-with-envs`
- `--task <instruction>`
- env:
- `LLM_MODEL=deepseek-reasoner`
- `LLM_BASE_URL=https://api.deepseek.com/v1`
- `LLM_API_KEY=<from provider binding>`
- canary dir:
- `/Users/john/Project/memind_canary/tool-gateway-p65-openhands-20260702-095402`
- run id:
- `0b1bb7a0-d111-44c1-91b5-4f14b2f9c01c`
- request id:
- `p65-real-openhands-20260702015402`
- duration:
- about `206s`
- status:
- `succeeded`
- attempts: `1`
- `agent_session_id=null`
event proof:
- `queued`
- `running`
- `tool_gateway_dispatch`
- `protocol=agent-run-v1`
- `taskType=repo_refactor`
- workingDir is the canary repo
- `tool_gateway_result`
- `dryRun=false`
- `executor=openhands`
- `exitCode=0`
- `succeeded`
file proof:
- generated file:
- `/Users/john/Project/memind_canary/tool-gateway-p65-openhands-20260702-095402/P65_OPENHANDS_CANARY.md`
- content:
- `P6.5 OpenHands Tool Gateway canary succeeded.`
恢复/最终状态:
- 生产 `.env` 无 `MEMIND_TOOL_GATEWAY_*`。
- 生产 `.env` 无 `MEMIND_AGENT_CODE_RUNS_*`。
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/runtime/status`:
2026-07-02 11:36:46 +08:00
- `ok=true`
- `toolRuntime.codeRunsEnabled=false`
- `toolRuntime.queue.toolGateway.enabled=false`
- all workers `heartbeatSource=sidecar`
- all workers `heartbeatOk=true`
- `scripts/runtime-slo-report.mjs`:
- `ok=true`
- `failures=[]`
边界:
- 未做 PG schema migration。
- 仅新增 canary run/event 审计记录。
- 未删除 PG 数据。
- 未删除或改写生产 MindSpace 用户数据。
- 真实 OpenHands 文件写入只发生在 `/Users/john/Project/memind_canary/...`。
### 2026-07-02 P6.6 External Worker Code-run Canary
目标:
- 验证 code-mode run 可以由 Portal 以外的 external worker 精确接管。
- 确认 Portal `autoDispatch=false` 时不会抢跑 queued run。
- 使用专用 canary workspace,不触碰生产用户页面。
- 执行后恢复生产默认关闭。
源码:
- commit:
- `a2d69a0 feat: target agent run worker dispatch`
- 关键变更:
- `scripts/agent-run-worker.mjs` 支持 `--run-id <id>` 精确 dispatch 单条 run。
- 支持 `MEMIND_AGENT_RUN_WORKDIR_OVERRIDE` 和 `MEMIND_AGENT_RUN_WORKDIR_USER_ID`,用于把 canary 文件写入专用目录。
- worker 输出包含 `runId`,便于审计。
生产 release:
- release id:
- `20260702-100212-a2d69a0`
- manifest:
- `git_head=a2d69a0c36f4300bc36a79abfe01300f2237fd07`
- `branch=memind-streaming-runtime-20260702`
执行:
- 生产 `.env` 短窗口启用:
- `MEMIND_AGENT_RUN_AUTODISPATCH=0`
- `MEMIND_AGENT_CODE_RUNS_ENABLED=1`
- `MEMIND_AGENT_CODE_RUNS_USER_IDS=a6fb1e97-2b0f-447b-b138-4561d8e5c53e`
- 外部 worker 单次执行时启用:
- `MEMIND_TOOL_GATEWAY_ENABLED=1`
- `MEMIND_TOOL_GATEWAY_DRY_RUN=0`
- `MEMIND_AGENT_RUN_WORKDIR_OVERRIDE=/Users/john/Project/memind_canary/tool-gateway-p66-worker-20260702-100500`
- `MEMIND_AGENT_RUN_WORKDIR_USER_ID=a6fb1e97-2b0f-447b-b138-4561d8e5c53e`
- code run:
- run id: `94dcca6f-cf32-4fa1-b602-8e6a5bff1e3c`
- request id: `p66-worker-aider-20260702100509`
worker proof:
- command path:
- `/Users/john/Project/Memind/scripts/agent-run-worker.mjs --run-id 94dcca6f-cf32-4fa1-b602-8e6a5bff1e3c`
- output:
- `ok=true`
- `mode=dispatch`
- `dispatched=1`
- `runId=94dcca6f-cf32-4fa1-b602-8e6a5bff1e3c`
- queue `autoDispatch=false`
- Tool Gateway `enabled=true`
- `dryRun=false`
event proof:
- `queued`
- `running`
- `tool_gateway_dispatch`
- canary workingDir
- `tool_gateway_result`
- `dryRun=false`
- `executor=aider`
- `exitCode=0`
- `succeeded`
file proof:
- generated file:
- `/Users/john/Project/memind_canary/tool-gateway-p66-worker-20260702-100500/P66_WORKER_CANARY.md`
- content:
- `P6.6 external worker Tool Gateway canary succeeded.`
恢复/最终状态:
- 已恢复 `.env`。
- 临时登录 session 已 revoke。
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/runtime/status`:
2026-07-02 11:36:46 +08:00
- `ok=true`
- `toolRuntime.codeRunsEnabled=false`
- `toolRuntime.queue.autoDispatch=true`
- `toolRuntime.queue.toolGateway.enabled=false`
- all workers `heartbeatSource=sidecar`
- all workers `heartbeatOk=true`
- `scripts/runtime-slo-report.mjs`:
- `ok=true`
- `failures=[]`
边界:
- 未做 PG schema migration。
- 仅新增 canary run/event 审计记录。
- 未删除 PG 数据。
- 未删除或改写生产 MindSpace 用户数据。
- 真实文件写入只发生在 `/Users/john/Project/memind_canary/...`。
### 2026-07-02 P6.7 Tool Gateway Production Guardrails
目标:
- 解决 executor `exitCode=0` 但目标产物未生成时仍被标记 `succeeded` 的风险。
- 为 canary 和灰度 code run 增加显式成功标准。
- 保持生产默认关闭,不改变普通聊天链路。
源码:
- commit:
- `6340b57 feat: validate tool gateway artifacts`
- branch:
- `memind-streaming-runtime-20260702`
关键变更:
- `agent-run-gateway.mjs`
- 支持读取 `metadata.memindRun.validation` 或 `metadata.toolGatewayValidation`。
- 支持校验:
- `expectedFile`
- `expectedFiles`
- `contains` / `expectedContent` / `contentIncludes`
- 校验路径限制在 Tool Gateway working directory 内。
- `tool_gateway_result` event 增加 `stdoutTail` / `stderrTail`。
- 校验通过写入 `tool_gateway_validation` event。
- 校验失败写入 `tool_gateway_validation_failed` event。
- 校验失败 `retryable=false`,避免确定性产物缺失被重复重试。
验证:
- `node --test agent-run-gateway.test.mjs tool-gateway.test.mjs`
- `17 passed`
- `node --check agent-run-gateway.mjs`
- passed
- `npm run build:portal-runtime`
- passed
生产 release:
- release id:
- `20260702-101319-6340b57`
- manifest:
- `git_head=6340b57fda61122a61b0f767eee118be81b151e6`
- `git_branch=memind-streaming-runtime-20260702`
发布说明:
- release 脚本第一次在已知历史 docx 缺失链接处停止。
- 按用户要求不修复/不删除/不改写历史用户数据,第二次发布使用 `ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES=1` 仅跳过该守卫。
- release 过程中脚本完成当前 live 全目录备份和持久目录备份。
2026-07-07 10:01:32 +08:00
- 105 legacy tunnel step 仍由脚本执行并健康,但 H5 主路径继续是 `https://m.tkmind.cn`。
2026-07-02 11:36:46 +08:00
发布后状态:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/runtime/status`:
2026-07-02 11:36:46 +08:00
- `ok=true`
- `toolRuntime.codeRunsEnabled=false`
- `toolRuntime.queue.autoDispatch=true`
- `toolRuntime.queue.toolGateway.enabled=false`
- `scripts/runtime-slo-report.mjs`:
- `ok=true`
- `failures=[]`
- `writes.database=false`
- `writes.mindSpace=false`
- `writes.redis=false`
- `writes.report=false`
- `writes.reportPrune=false`
边界:
- 未做 PG schema migration。
- 未删除 PG 数据。
- 未删除或改写生产 MindSpace 用户数据。
- 新能力默认关闭,只影响显式 Tool Gateway code run 且声明 validation 的任务。
### 2026-07-02 P6.8 Tool Gateway Controlled Rollout 基础
目标:
- 为 external agent-run-worker 准备标准 LaunchAgent 运维入口。
- 默认不启动、不接管生产队列。
- 保持 `codeRunsEnabled=false`、`toolGateway.enabled=false`。
源码:
- commit:
- `3654639 feat: add agent run worker launchagent installer`
关键变更:
- 新增:
- `scripts/install-agent-run-worker-agent.sh`
- 更新:
- `scripts/build-portal-runtime.mjs`
- runtime artifact 会带上 installer,并在 RUNBOOK 中记录入口。
installer 默认行为:
- 写入:
- `/Users/john/Library/LaunchAgents/cn.tkmind.memind-agent-run-worker.plist`
- 默认:
- `RunAtLoad=false`
- `KeepAlive=true`
2026-07-02 11:36:46 +08:00
- `launchctl disable`
- 不 `kickstart`
- 默认环境:
- `MEMIND_AGENT_RUN_AUTODISPATCH=0`
- `MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=1`
- `MEMIND_TOOL_GATEWAY_ENABLED=0`
- `MEMIND_TOOL_GATEWAY_DRY_RUN=0`
验证:
- `bash -n scripts/install-agent-run-worker-agent.sh`
- passed
- `node --check scripts/build-portal-runtime.mjs`
- passed
- `node --test agent-run-gateway.test.mjs tool-gateway.test.mjs`
- `17 passed`
- `npm run build:portal-runtime`
- passed
生产 release:
- release id:
- `20260702-101834-3654639`
- manifest:
- `git_head=3654639bb314437a7463446f47f40ab1c15aeb92`
- `git_branch=memind-streaming-runtime-20260702`
生产安装状态:
- installer:
- `/Users/john/Project/Memind/scripts/install-agent-run-worker-agent.sh`
- LaunchAgent:
- `/Users/john/Library/LaunchAgents/cn.tkmind.memind-agent-run-worker.plist`
- state:
- `installed-disabled`
- `print-disabled`: disabled
- `launchctl print`: `state = not running`
- no `agent-run-worker.mjs` process
发布后状态:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/runtime/status`:
2026-07-02 11:36:46 +08:00
- `ok=true`
- `toolRuntime.codeRunsEnabled=false`
- `toolRuntime.queue.autoDispatch=true`
- `toolRuntime.queue.toolGateway.enabled=false`
- `scripts/runtime-slo-report.mjs`:
- `ok=true`
- `failures=[]`
- writes all false
边界:
- 未做 PG schema migration。
- 未删除 PG 数据。
- 未删除或改写生产 MindSpace 用户数据。
- 未启动 external worker。
### 2026-07-02 P6.9 Validated External Worker Canary
目标:
- 使用 LaunchAgent 方式短窗口启动 external `agent-run-worker`。
- 强制 worker 写入专用 canary repo,不触碰生产 MindSpace 用户页面。
- 验证 Tool Gateway validation 的失败路径和成功路径。
- 完成后恢复默认关闭。
准备补丁:
- commit:
- `b12c1b3 feat: support worker canary workdir override`
- release:
- `20260702-102636-b12c1b3`
- 目的:
- installer 支持 `MEMIND_AGENT_RUN_WORKDIR_OVERRIDE`
- installer 支持 `MEMIND_AGENT_RUN_WORKDIR_USER_ID`
短窗口配置:
- `.env` backup:
- `/Users/john/Project/memind_backups/20260702-1029-p69-validated-worker-canary/.env.before`
- Portal:
- `MEMIND_AGENT_RUN_AUTODISPATCH=0`
- `MEMIND_AGENT_CODE_RUNS_ENABLED=1`
- `MEMIND_AGENT_CODE_RUNS_USER_IDS=a6fb1e97-2b0f-447b-b138-4561d8e5c53e`
- `MEMIND_TOOL_GATEWAY_ENABLED=0`
- external worker LaunchAgent:
- `MEMIND_TOOL_GATEWAY_ENABLED=1`
- `MEMIND_TOOL_GATEWAY_DRY_RUN=0`
- `MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=1`
- `MEMIND_AGENT_RUN_WORKDIR_OVERRIDE=/Users/john/Project/memind_canary/tool-gateway-p69-validated-worker-20260702-102942`
- `MEMIND_AGENT_RUN_WORKDIR_USER_ID=a6fb1e97-2b0f-447b-b138-4561d8e5c53e`
canary repo:
- `/Users/john/Project/memind_canary/tool-gateway-p69-validated-worker-20260702-102942`
失败路径 proof:
- run id:
- `1353d917-c03b-4fb8-9acd-739d56984fc5`
- request id:
- `p69-validated-worker-20260702022958`
- final status:
- `failed`
- attempts: `1`
- event chain:
- `queued`
- `running`
- `tool_gateway_dispatch`
- `tool_gateway_result`
- `executor=aider`
- `dryRun=false`
- `exitCode=0`
- `tool_gateway_validation_failed`
- `reason=missing_content`
- `failed`
- 说明:
- Aider exit 0 且生成文件,但内容与 expected content 不完全匹配,validation 正确阻止 succeeded。
成功路径 proof:
- run id:
- `980424de-5df5-4294-9d1d-8901ae66be6f`
- request id:
- `p69-validated-worker-pass-20260702023132`
- final status:
- `succeeded`
- attempts: `1`
- `agent_session_id=null`
- event chain:
- `queued`
- `running`
- `tool_gateway_dispatch`
- `tool_gateway_result`
- `executor=aider`
- `dryRun=false`
- `exitCode=0`
- `tool_gateway_validation`
- `expectedFiles[0].path=P69_VALIDATED_WORKER_CANARY_PASS.md`
- `contains=true`
- `sizeBytes=31`
- `succeeded`
- generated file:
- `/Users/john/Project/memind_canary/tool-gateway-p69-validated-worker-20260702-102942/P69_VALIDATED_WORKER_CANARY_PASS.md`
- content:
- `P69_VALIDATED_WORKER_CANARY_OK`
恢复/最终状态:
- 已恢复 `.env`。
- 临时 login sessions 已 revoke:
- `revokedRows=2`
- worker LaunchAgent:
- `installed-disabled`
- `state=not running`
- no `agent-run-worker.mjs` process
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/runtime/status`:
2026-07-02 11:36:46 +08:00
- `ok=true`
- `toolRuntime.codeRunsEnabled=false`
- `toolRuntime.queue.autoDispatch=true`
- `toolRuntime.queue.toolGateway.enabled=false`
- `scripts/runtime-slo-report.mjs`:
- `ok=true`
- `failures=[]`
- writes all false
边界:
- 未做 PG schema migration。
- 仅新增 canary run/event 审计记录和临时 login session 记录,临时 session 已 revoke。
- 未删除 PG 数据。
- 未删除或改写生产 MindSpace 用户数据。
- 真实文件写入只发生在 `/Users/john/Project/memind_canary/...`。
### 2026-07-02 P6.10 External Worker Observability
目标:
- 为 external worker 增加独立只读状态检查。
- 默认期望 worker disabled/not running。
- 继续保持生产 code-run/Tool Gateway 默认关闭。
源码:
- commit:
- `f1fd4cc feat: add agent run worker status check`
新增:
- `scripts/check-agent-run-worker.mjs`
能力:
- 检查 LaunchAgent:
- plist 是否存在
- `launchctl print` state
- disabled/enabled 状态
- pid / program / path
- `agent-run-worker.mjs` 进程列表
- 检查 queue:
- queued/running/retryable counts
- oldest pending age
- latest failed run
- 默认:
- 期望 disabled/not running
- 可选:
- `MEMIND_AGENT_RUN_WORKER_EXPECT_RUNNING=1` 时期望 running
验证:
- `node --check scripts/check-agent-run-worker.mjs`
- passed
- `MEMIND_ENV_FILE=/Users/john/Project/Memind/.env node scripts/check-agent-run-worker.mjs`
- `ok=true`
- `node --test agent-run-gateway.test.mjs tool-gateway.test.mjs`
- `17 passed`
- `npm run build:portal-runtime`
- passed
生产 release:
- release id:
- `20260702-103631-f1fd4cc`
- manifest:
- `git_head=f1fd4cc31666b33962e9a730cb7d925e860e6db5`
发布后验证:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/runtime/status`:
2026-07-02 11:36:46 +08:00
- `ok=true`
- `toolRuntime.codeRunsEnabled=false`
- `toolRuntime.queue.autoDispatch=true`
- `toolRuntime.queue.toolGateway.enabled=false`
- `node scripts/check-agent-run-worker.mjs`:
- `ok=true`
- `expected=disabled`
- `installed=true`
- `disabled=true`
- `running=false`
- `queue.statusCounts={}`
- `oldestPendingAgeMs=0`
- `scripts/runtime-slo-report.mjs`:
- `ok=true`
- `failures=[]`
- writes all false
边界:
- 未做 PG schema migration。
- 未删除 PG 数据。
- 未删除或改写生产 MindSpace 用户数据。
- 新脚本只读。
### 2026-07-02 P6.11 Single-user Rollout Policy
目标:
- 为后续单用户长期灰度建立策略门禁。
- 默认关闭,不改变普通用户路径。
- 在 runtime/status 中可观测当前 code-run policy。
源码:
- commit:
- `212e163 feat: add code run rollout policy gates`
新增策略:
- `MEMIND_AGENT_CODE_RUNS_ENABLED`
- code run 总开关。
- `MEMIND_AGENT_CODE_RUNS_USER_IDS`
- 用户级 allowlist。
- `MEMIND_AGENT_CODE_RUN_TASK_TYPES`
- task type allowlist;为空表示不限制 task type。
- `MEMIND_AGENT_CODE_RUNS_REQUIRE_VALIDATION`
- 开启后,code run 必须声明 `metadata.memindRun.validation` 或 `metadata.toolGatewayValidation`。
入口行为:
- 非 allowlist 用户:
- `403 当前用户未开启代码任务灰度`
- 非 allowlist task type:
- `403 当前代码任务类型未开启灰度`
- 缺少 required validation:
- `400 代码任务必须声明产物校验规则`
runtime/status:
- 新增:
- `toolRuntime.codeRunPolicy.enabled`
- `toolRuntime.codeRunPolicy.userAllowlist`
- `toolRuntime.codeRunPolicy.taskTypeAllowlist`
- `toolRuntime.codeRunPolicy.requireValidation`
验证:
- `node --test agent-run-routes.test.mjs agent-run-gateway.test.mjs tool-gateway.test.mjs`
- `31 passed`
- `node --check agent-run-routes.mjs`
- passed
- `node --check server.mjs`
- passed
- `npm run build:portal-runtime`
- passed
生产 release:
- release id:
- `20260702-104734-212e163`
- manifest:
- `git_head=212e163006ae120827923335e568845a886db775`
发布备注:
- release 脚本在 legacy `m.tkmind.cn` tunnel step 短暂返回失败;Portal runtime 已切换且健康。
- 后续检查显示:
2026-07-07 10:01:32 +08:00
- `m.tkmind.cn` 主路径正常。
2026-07-02 11:36:46 +08:00
- `cn.tkmind.memind-portal-tunnel` 已恢复 running。
- Portal node pid `35983` 正在监听 `:8081`。
2026-07-07 10:01:32 +08:00
- 该 legacy tunnel 不是当前 H5 主路径,当前 H5 主路径仍是 `https://m.tkmind.cn`。
2026-07-02 11:36:46 +08:00
发布后状态:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/runtime/status`:
2026-07-02 11:36:46 +08:00
- `ok=true`
- `toolRuntime.codeRunPolicy.enabled=false`
- `toolRuntime.codeRunPolicy.userAllowlist=[]`
- `toolRuntime.codeRunPolicy.taskTypeAllowlist=[]`
- `toolRuntime.codeRunPolicy.requireValidation=false`
- `toolRuntime.codeRunsEnabled=false`
- `toolRuntime.queue.autoDispatch=true`
- `toolRuntime.queue.toolGateway.enabled=false`
- `node scripts/check-agent-run-worker.mjs`:
- `ok=true`
- worker disabled/not running
- `scripts/runtime-slo-report.mjs`:
- `ok=true`
- `failures=[]`
- writes all false
边界:
- 未做 PG schema migration。
- 未删除 PG 数据。
- 未删除或改写生产 MindSpace 用户数据。
- 默认关闭,不开启真实用户 code run。
### 2026-07-02 P6.12 All-user Long-running Gray
目标:
- 将 code-run 灰度从单用户策略门禁推进到所有 H5 用户默认开启。
- 保持 Portal 不直接执行 Tool Gateway,统一由 external worker 接管。
- 使用真实普通用户路径验证,而不是只做后台 canary。
生产变更:
- Portal `.env`:
- `MEMIND_AGENT_CODE_RUNS_ENABLED=1`
- `MEMIND_AGENT_CODE_RUNS_USER_IDS=`,空值表示不限制用户。
- `MEMIND_AGENT_CODE_RUN_TASK_TYPES=`,空值表示不限制任务类型。
- `MEMIND_AGENT_CODE_RUNS_REQUIRE_VALIDATION=0`,H5 默认 code-run 不强制声明 expected file validation;带 validation 的任务仍走 Tool Gateway 校验。
- `MEMIND_AGENT_RUN_AUTODISPATCH=0`,Portal 不内联派发 code-run。
- `MEMIND_TOOL_GATEWAY_ENABLED=0`,Portal 侧 Tool Gateway 保持关闭。
- H5 前端重建:
- `VITE_AGENT_CODE_RUNS_ENABLED=1`
- `VITE_AGENT_CODE_RUNS_AUTODETECT=1`
- 当前公网 bundle: `/assets/index-CI7mXNQo.js`
- external worker:
- `cn.tkmind.memind-agent-run-worker` enabled/running。
- `MEMIND_TOOL_GATEWAY_ENABLED=1`
- `MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=1`
备份:
- `/Users/john/Project/memind_backups/20260702-111654-all-user-code-run-gray`
真实用户验证:
- 测试用户:
- `gray20260702112309`
- user id `b4561354-4273-4900-9ada-b6c52d2c5836`
- run:
- `aa0d1ac1-c03b-4110-baea-6cac83556567`
- request id `gray-code-20260702112309-login`
- status `succeeded`
- attempts `1`
- 事件链:
- `queued`
- `running`
- `tool_gateway_dispatch`
- `tool_gateway_result`
- `succeeded`
- executor:
- `aider`
- `dryRun=false`
- 工作目录:
- `/Users/john/Project/Memind/MindSpace/b4561354-4273-4900-9ada-b6c52d2c5836`
- 产物:
- `/Users/john/Project/Memind/MindSpace/b4561354-4273-4900-9ada-b6c52d2c5836/public/gray-code-run-20260702112309.html`
- 内容匹配 `GRAY_CODE_RUN_20260702112309`
生产观察:
- `MEMIND_AGENT_RUN_WORKER_EXPECT_RUNNING=1 node scripts/check-agent-run-worker.mjs`:
- `ok=true`
- worker `running=true`
- queue `statusCounts={}`
- `oldestPendingAgeMs=0`
- `node scripts/runtime-slo-report.mjs`:
- `ok=true`
- `failures=[]`
边界:
- 未做 PG schema migration。
- 未删除 PG 数据。
- 未删除或改写既有生产 MindSpace 用户数据。
- 真实测试产物仅写入新建测试用户自己的 MindSpace。
### 2026-07-02 P6.13 Agent Run Auto-pause Guard
目标:
- 全用户灰度开启后,增加自动暂停保护。
- 当失败或队列积压超过阈值时,自动关闭 code-run gate 并停掉 external worker,防止故障扩散。
新增源码:
- `scripts/agent-run-guard.mjs`
- `scripts/install-agent-run-guard-agent.sh`
- `docs/agent-run-worker-rollout-runbook.md`
- `scripts/build-portal-runtime.mjs` 已同步 runtime 打包清单。
生产安装:
- LaunchAgent:
- label `cn.tkmind.memind-agent-run-guard`
- interval `60s`
- log `/Users/john/Library/Logs/memind-agent-run-guard.log`
- 安装命令:
- `bash /Users/john/Project/Memind/scripts/install-agent-run-guard-agent.sh`
默认触发阈值:
- 10 分钟内 failed run 数 `>= 3`
- 最老 queued/retryable age `>= 300000ms`
- queued + retryable 数量 `>= 10`
- 最老 running age `>= 900000ms`
触发动作:
- 备份 `.env` 到 `/Users/john/Project/memind_backups/<timestamp>-agent-run-guard-pause/.env.before`
- 写入:
- `MEMIND_AGENT_CODE_RUNS_ENABLED=0`
- `MEMIND_AGENT_RUN_AUTODISPATCH=1`
- `launchctl bootout/disable cn.tkmind.memind-agent-run-worker`
- `launchctl kickstart -k cn.tkmind.memind-portal`
验证:
- `node scripts/agent-run-guard.mjs --dry-run`:
- `ok=true`
- `shouldPause=false`
- queue empty
- recent failed count `0`
- `launchctl print gui/$(id -u)/cn.tkmind.memind-agent-run-guard`:
- enabled
- `run interval = 60 seconds`
- last exit code `0`
- 安装后复查:
- worker 仍 running
- code-run all-user gate 仍 enabled
- SLO `ok=true`, `failures=[]`
备份:
- `/Users/john/Project/memind_backups/20260702-113106-agent-run-guard-install`
### 2026-07-02 P6.15 H5 Code-run Validation Metadata
目标:
- 在 P6.14 灰度稳定后,把 H5 默认 code-run 从“允许无 validation”升级为“自动带 validation”。
- 恢复后端 required validation gate,避免全用户灰度下出现无产物校验的 code-run。
- 不猜测用户任务真正产物;统一要求每次 code-run 额外写入可验证 receipt。
新增行为:
- H5 code-run 创建时自动生成:
- receipt path: `.memind/agent-runs/<requestId>.json`
- validation: `metadata.memindRun.validation.expectedFile`
- contains: 当前 `requestId`
- H5 会把简短校验指令追加到 agent-visible message:
- 要求执行器创建/更新 receipt JSON。
- receipt 必须包含当前 requestId 值。
- `createAgentRun()` 在提交 `/api/agent/runs` 前合并 validation metadata。
- 普通 chat mode 不附加 receipt validation。
生产配置:
- 已恢复:
- `MEMIND_AGENT_CODE_RUNS_REQUIRE_VALIDATION=1`
- 保持:
- `MEMIND_AGENT_CODE_RUNS_ENABLED=1`
- `MEMIND_AGENT_CODE_RUNS_USER_IDS=`
- `MEMIND_AGENT_CODE_RUN_TASK_TYPES=`
- `MEMIND_AGENT_RUN_AUTODISPATCH=0`
- external worker running
- auto-pause guard enabled
验证:
- `npm run build` 通过。
- `node --test agent-run-routes.test.mjs agent-run-gateway.test.mjs` 通过,27 tests pass。
- 生产 `/api/runtime/status`:
- `codeRunPolicy.enabled=true`
- `codeRunPolicy.userAllowlist=[]`
- `codeRunPolicy.taskTypeAllowlist=[]`
- `codeRunPolicy.requireValidation=true`
- 普通测试用户真实路径通过:
- user id `52ff79cf-21b2-4185-94d6-f24555126ceb`
- run id `5eb28014-b376-4a0c-b42a-8dd38f781f4f`
- request id `p615-validation-20260702034524`
- status `succeeded`
- attempts `1`
- 产物验证:
- receipt: `/Users/john/Project/Memind/MindSpace/52ff79cf-21b2-4185-94d6-f24555126ceb/.memind/agent-runs/p615-validation-20260702034524.json`
- product: `/Users/john/Project/Memind/MindSpace/52ff79cf-21b2-4185-94d6-f24555126ceb/public/p615-validation-20260702034524.html`
- event chain 包含 `tool_gateway_validation`。
- SLO:
- `ok=true`
- `failures=[]`
- queue empty
- guard `shouldPause=false`
备份:
- `/Users/john/Project/memind_backups/20260702-114335-p615-validation-metadata`
- worker KeepAlive 运维补强备份: `/Users/john/Project/memind_backups/20260702-115330-worker-keepalive`
2026-07-02 12:08:49 +08:00
### 2026-07-02 P6.17 Controlled Worker Concurrency Canary
目标:
- 在 required validation 和 auto-pause guard 已稳定后,短窗口验证 external worker 并发 2。
- 不扩大 code-run gate,不改用户 allowlist,不改 task type allowlist。
- 真实 canary 使用两个新测试用户,避免同一 MindSpace/git 工作目录互相锁住。
生产变更:
- 备份:
- `/Users/john/Project/memind_backups/20260702-120520-p617-concurrency-canary`
- worker LaunchAgent 临时提升:
- `MEMIND_AGENT_RUN_WORKER_BATCH_SIZE=2`
- `MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=2`
- `--limit 2`
- `KeepAlive=true`
验证:
- 两条真实 code-run 同时进入 running:
- run `d064c8c0-b061-4df7-8c9b-5dfa315271ea`
- user `916f1fe6-e9c8-44b5-9faa-2fc1bf146ab9`
- request `p617-concurrency-20260702040600-1`
- started `1782965163186`
- completed `1782965180346`
- status `succeeded`, attempts `1`
- run `64717bcc-4065-4947-a0d3-a706f3b62640`
- user `07f29934-8cf8-408a-af92-3c4276e579c8`
- request `p617-concurrency-20260702040600-2`
- started `1782965163135`
- completed `1782965186566`
- status `succeeded`, attempts `1`
- 两条 run 都写入:
- `tool_gateway_dispatch`
- `tool_gateway_result`
- `tool_gateway_validation`
- `succeeded`
- MindSpace 验证:
- 两个 receipt JSON 均包含 requestId。
- 两个 `public/p617-concurrency-*.html` 均包含目标 marker。
- worker log:
- `limit=2`
- `maxConcurrentRuns=2`
- `dispatched=2`
- P6.17 后复查:
- worker running
- queue empty
- guard `shouldPause=false`
- SLO `ok=true`, `failures=[]`
### 2026-07-02 P6.18 Concurrency Rollout Policy
目标:
- 将 P6.17 的并发 2 从短窗口 canary 固化为当前 all-user gray 的可运营策略。
- 让 worker LaunchAgent、Portal `.env` 和 runtime/status 对齐,避免状态面显示并发 1 但实际 worker 并发 2。
生产配置:
- `/Users/john/Project/Memind/.env`:
- `MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=2`
- `MEMIND_AGENT_RUN_WORKER_BATCH_SIZE=2`
- worker LaunchAgent:
- `MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=2`
- `MEMIND_AGENT_RUN_WORKER_BATCH_SIZE=2`
- `--limit 2`
- `KeepAlive=true`
验证:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status` 返回 `ok`。
2026-07-02 12:08:49 +08:00
- `/api/runtime/status`:
- `codeRunPolicy.requireValidation=true`
- `toolRuntime.queue.maxConcurrentRuns=2`
- `toolRuntime.queue.statusCounts={}`
- worker check:
- running `true`
- process args include `--limit 2`
- guard dry-run:
- `ok=true`
- `shouldPause=false`
- queue empty
- SLO:
- `ok=true`
- `failures=[]`
观察:
- Portal 在一次重启窗口中出现 `read EADDRNOTAVAIL` 并由 LaunchAgent 自动拉起,随后 `/api/status` 和 `/api/runtime/status` 恢复 `ok`。
- 后续建议优先做 running/stuck recovery 与 DB/session auth 异常不致命化,避免瞬时 DB/socket 错误扩大为进程退出。
2026-07-02 12:12:41 +08:00
### 2026-07-02 P8.1 Queue Lease / Stuck Run Recovery
目标:
- 防止 worker/进程在 run 进入 `running` 后异常退出,导致该 run 永久卡住。
- 在不新增 schema、不修改真实用户数据的前提下,先用 `started_at + runTimeoutMs` 作为最小 lease。
新增能力:
- `agent-run-gateway.mjs`:
- 新增 `recoverStaleRunningRuns()`。
- `dispatchQueuedRuns()` 每轮 dispatch 前自动回收超过 `runTimeoutMs` 的 `running` rows。
- stale row 会标记为 `failed`,写入 `stale_recovered` event。
- `scripts/agent-run-worker.mjs`:
- 新增只读入口:
- `node scripts/agent-run-worker.mjs --recover-stale --stale-ms 900000 --limit 5`
- 新增应用入口:
- `node scripts/agent-run-worker.mjs --recover-stale --apply-recovery --stale-ms 900000 --limit 5`
- `scripts/check-agent-run-worker.mjs`:
- queue 摘要增加:
- `oldestRunningStartedAt`
- `oldestRunningAgeMs`
- runtime/status 的 queue 摘要增加 running age 字段。
测试:
- `node --check agent-run-gateway.mjs scripts/agent-run-worker.mjs scripts/check-agent-run-worker.mjs` 通过。
- `node --test agent-run-gateway.test.mjs agent-run-routes.test.mjs` 通过,29 tests pass。
- 新增测试:
- dry-run reports stale running rows without mutating them。
- apply marks stale running rows failed and writes `stale_recovered` event。
生产部署:
- 备份:
- `/Users/john/Project/memind_backups/20260702-120900-p81-stale-recovery`
- 已部署:
- bundled `server.mjs`
- bundled `scripts/agent-run-worker.mjs`
- `scripts/check-agent-run-worker.mjs`
- `RUNBOOK.txt`
- 已重启:
- `cn.tkmind.memind-portal`
- `cn.tkmind.memind-agent-run-worker`
生产验证:
- `/api/status` 返回 `ok`。
- `/api/runtime/status.toolRuntime.queue`:
- `maxConcurrentRuns=2`
- `statusCounts={}`
- `oldestRunningStartedAt=null`
- `oldestRunningAgeMs=0`
- stale dry-run:
- `considered=0`
- `recovered=0`
- synthetic stale apply:
- run id `9e22ad3d-ad5d-42b5-9ce3-24fe7aace3f5`
- request id `p81-stale-recovery-1782965482040`
- recovery `considered=1`, `recovered=1`
- final status `failed`
- event chain includes `stale_recovered`
- post-check:
- worker running with `--limit 2`
- queue empty
- guard `shouldPause=false`
- SLO `ok=true`, `failures=[]`
### 2026-07-02 P8.2 Portal DB/Auth Transient Error Hardening
目标:
- 防止 session verify / auth status / API auth 中的 MySQL 或 socket 瞬时错误扩大为进程级异常。
- 将认证依赖临时不可用时的用户可见结果收敛为 503,而不是 uncaught rejection 或 Portal 退出。
- 保持 plaza public read 路径在 session attach 瞬时失败时仍可继续读取。
改动:
- `attachUserSession()`:
- 捕获 `userAuth.verify(token)` 异常。
- 写入 `req.userSessionError`,记录 `[Auth] session verify failed`。
- 不让异常穿透 Express middleware。
- `/auth/status`:
- 捕获 `getMe()`、`getUserById()`、`resolveUserCapabilities()` 异常。
- 返回 `503 { authenticated:false, mode:'unavailable' }`。
- API auth middleware:
- 对 `req.userSessionError` 返回 503。
- 对 public plaza read 继续放行。
- 捕获后续 `getMe()` 异常并返回 503。
测试:
- `node --check server.mjs` 通过。
- `node --test auth.test.mjs user-auth.test.mjs admin-guard.test.mjs` 通过,23 tests pass。
生产部署:
- 备份:
- `/Users/john/Project/memind_backups/20260702-121438-p82-auth-transient-hardening`
- 已部署:
- bundled `server.mjs`
- 已重启:
- `cn.tkmind.memind-portal`
生产验证:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status` 返回 `ok`。
- `https://m.tkmind.cn/auth/status` 未登录返回 `200 {"authenticated":false,"mode":"user"}`。
- `/api/runtime/status.toolRuntime`:
- `codeRunsEnabled=true`
- `requireValidation=true`
- `maxConcurrentRuns=2`
- `oldestRunningStartedAt=null`
- `oldestRunningAgeMs=0`
- worker LaunchAgent running:
- `--limit 2`
- `MEMIND_TOOL_GATEWAY_ENABLED=1`
- `MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=2`
- guard dry-run:
- `ok=true`
- `shouldPause=false`
- SLO:
- `ok=true`
- `failures=[]`
### 2026-07-02 P6.19 Task-level Artifact Validation
目标:
- receipt validation 继续作为所有 code-run 的兜底。
- 对能从用户指令或 MindSpace 上下文稳定推断的具体产物追加 task-level validation。
- 页面编辑类任务即使通过 API/patch 修改页面,也需要生成带 `pageId` 的 task receipt,证明执行器处理的是目标页面。
改动:
- `src/utils/agentRunMode.ts`:
- 新增 `extractAgentRunPublicHtmlPaths()`,可从用户输入、`MindSpace/.../public/*.html` 和公开链接中提取 workspace-relative `public/*.html`。
- 新增 `buildAgentRunTaskValidation()`。
- `resolveAgentRunOptions()` 现在会合并:
- 默认 `.memind/agent-runs/<requestId>.json` receipt。
- 推断出的 `public/*.html` expectedFiles。
- `page_edit_code_task` 的 `.memind/agent-runs/<requestId>-page-edit.json`,内容必须包含 `pageId`。
- `src/hooks/useTKMindChat.ts`:
- 普通 H5 code-run 调用 `resolveAgentRunOptions()` 时传入 MindSpace context。
- `src/hooks/usePageEditSubChat.ts`:
- 页面编辑子聊天传入 MindSpace context、`pageId` 和 `pageTitle`。
测试:
- `npm run build` 通过。
- `node --test agent-run-gateway.test.mjs agent-run-routes.test.mjs` 通过,29 tests pass。
- `npx tsc --noEmit` 仍被既有历史 TS 问题阻断,本次相关 `waitForAgentRun()` 返回类型已收紧;最终以 Vite build 和 agent-run route/gateway tests 为准。
生产部署:
- 使用灰度 env 重建 H5:
- `VITE_AGENT_CODE_RUNS_ENABLED=1`
- `VITE_AGENT_CODE_RUNS_AUTODETECT=1`
- `VITE_AGENT_CODE_RUNS_USER_IDS=`
- 备份:
- `/Users/john/Project/memind_backups/20260702-122737-p619-task-artifact-validation`
- 已同步:
- `/Users/john/Project/Memind/dist`
生产验证:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/` 已加载新 bundle:
- `/assets/index-jYf9W5vk.js`
- `/api/runtime/status`:
- `ok=true`
- `codeRunsEnabled=true`
- `requireValidation=true`
- `maxConcurrentRuns=2`
- `statusCounts={}`
- guard dry-run:
- `ok=true`
- `shouldPause=false`
- `failedRecentCount=0`
- real canary:
- user `916f1fe6-e9c8-44b5-9faa-2fc1bf146ab9`
- run `60067d2f-d2af-40d1-bf35-7976309bd8da`
- request `p619-task-validation-20260702042911`
- status `succeeded`
- attempts `1`
- event chain includes `tool_gateway_validation`
- validated expectedFiles:
- `.memind/agent-runs/p619-task-validation-20260702042911.json`
- `public/p619-task-validation-20260702042911.html`
- `.memind/agent-runs/p619-task-validation-20260702042911-page-edit.json`
2026-07-02 12:38:34 +08:00
### 2026-07-02 P8.3 Worker Lease Heartbeat
目标:
- 将 running run 的 lease 从单纯 `started_at + timeout` 升级为 heartbeat-aware。
- 避免长任务仍在执行时被 stale recovery 误杀。
- 让 runtime/status、worker check 和 auto-pause guard 能看到 running heartbeat age。
改动:
- `agent-run-gateway.mjs`:
- 新增 `MEMIND_AGENT_RUN_HEARTBEAT_MS`,默认 `30000`。
- run 被 claim 为 `running` 后立即写入 `worker_heartbeat` event,后续按 heartbeat interval 定时写入。
- run 进入 terminal 或失败后停止 heartbeat timer。
- `recoverStaleRunningRuns()` 使用 `COALESCE(latest worker_heartbeat, started_at)` 判断 stale。
- recovery UPDATE 也复核最新 heartbeat,避免 select/update 之间误杀刚刷新 heartbeat 的 run。
- `getQueueStatus()` 增加:
- `heartbeatMs`
- `oldestRunningHeartbeatAt`
- `oldestRunningHeartbeatAgeMs`
- `runningWithoutHeartbeatCount`
- `latestRunningRun`
- `scripts/agent-run-guard.mjs`:
- auto-pause 的 running age 判断改为 heartbeat age。
- queue 摘要增加 heartbeat 字段。
- `scripts/check-agent-run-worker.mjs`:
- queue 摘要增加 heartbeat 字段。
- `.env.example` 和 worker rollout runbook:
- 记录 `MEMIND_AGENT_RUN_HEARTBEAT_MS`。
- all-user gray 下记录 `MEMIND_AGENT_RUN_WORKER_EXPECT_RUNNING=1`。
测试:
- `node --check agent-run-gateway.mjs scripts/agent-run-worker.mjs scripts/agent-run-guard.mjs scripts/check-agent-run-worker.mjs` 通过。
- `node --test agent-run-gateway.test.mjs agent-run-routes.test.mjs` 通过,31 tests pass。
- 新增测试:
- worker dispatch 会写 `worker_heartbeat`。
- stale recovery 不回收有新 heartbeat 的旧 running run。
- queue status 展示 heartbeat age 与 missing heartbeat count。
生产部署:
- 备份:
- `/Users/john/Project/memind_backups/20260702-123510-p83-worker-heartbeat`
- 已部署:
- bundled `server.mjs`
- bundled `scripts/agent-run-worker.mjs`
- `scripts/agent-run-guard.mjs`
- `scripts/check-agent-run-worker.mjs`
- 已重启:
- `cn.tkmind.memind-portal`
- `cn.tkmind.memind-agent-run-worker`
- 生产 `.env` 追加:
- `MEMIND_AGENT_RUN_WORKER_EXPECT_RUNNING=1`
生产验证:
2026-07-07 10:01:32 +08:00
- `https://m.tkmind.cn/api/status` 返回 `ok`。
2026-07-02 12:38:34 +08:00
- `/api/runtime/status.toolRuntime.queue`:
- `maxConcurrentRuns=2`
- `heartbeatMs=30000`
- `statusCounts={}`
- `oldestRunningHeartbeatAgeMs=0`
- `runningWithoutHeartbeatCount=0`
- `check-agent-run-worker.mjs`:
- `ok=true`
- `expected=running`
- `running=true`
- guard dry-run:
- `ok=true`
- `shouldPause=false`
- synthetic recovery canary:
- no-heartbeat run `042276ae-ecac-4b7c-9c53-c02154ea85cd`
- request `p83-no-heartbeat-20260702043604`
- recovery `considered=1`, `recovered=1`
- stale event includes `heartbeatAt=null` and `heartbeatAgeMs`
- fresh-heartbeat synthetic run `97a2ac16-0234-483d-be4f-836ba95a91e7` was not recovered and then deleted to avoid queue residue.
- real worker heartbeat canary:
- run `ad2894a7-dc02-49c6-a859-da6af95cb143`
- request `p83-heartbeat-real-20260702043642`
- status `succeeded`
- event chain includes `worker_heartbeat` before `tool_gateway_dispatch`
- heartbeat data includes `pid=31341`, `attempt=1`, `heartbeatMs=30000`
- `tool_gateway_validation` passed for receipt and `public/p83-heartbeat-real-20260702043642.html`
### 2026-07-02 P8.4 Long-running Gray Soak and Heartbeat SLO
目标:
- 不扩大并发、不做 UI,先把 P8.3 heartbeat 纳入每日 SLO 报告。
- 让 23:55 定时日报和手动 `runtime-slo-report.mjs` 都能暴露 running heartbeat 风险。
2026-07-02 12:45:55 +08:00
- 为 24h all-user gray soak 提供稳定观测字段和小时级留存证据。
改动:
- `scripts/runtime-slo-report.mjs`:
- `runtime.toolQueueSlo` 新增 heartbeat 摘要:
- `heartbeatMs`
- `maxConcurrentRuns`
- `statusCounts`
- `oldestRunningStartedAt`
- `oldestRunningAgeMs`
- `oldestRunningHeartbeatAt`
- `oldestRunningHeartbeatAgeMs`
- `runningWithoutHeartbeatCount`
- `latestRunningRun`
- failure 条件新增:
- `agent_run_heartbeat_stale_<age>`: running heartbeat age 超过阈值。
- `agent_run_missing_heartbeat_<count>`: running run 长时间缺失 heartbeat。
- Markdown 报告的 Tool Queue 段落展示:
- `heartbeatMs`
- `oldestRunningHeartbeatAgeMs`
- `runningWithoutHeartbeatCount`
- `.env.example`:
- 记录 `MEMIND_AGENT_RUN_HEARTBEAT_STALE_MS=90000`。
- `docs/agent-run-worker-rollout-runbook.md`:
- 记录 heartbeat SLO 检查命令和期望字段。
2026-07-02 12:45:55 +08:00
- `scripts/install-runtime-slo-soak-agent.sh`:
- 新增独立 LaunchAgent installer。
- 默认 label `cn.tkmind.memind-runtime-slo-soak`。
- 默认 `StartInterval=3600`。
- 默认写入 `reports/runtime-slo-soak`。
- 默认保留 `7` 天。
- `scripts/build-portal-runtime.mjs`:
- runtime artifact 会同步并 chmod soak installer。
测试:
2026-07-02 12:45:55 +08:00
- `bash -n scripts/install-runtime-slo-soak-agent.sh` 通过。
- `node --check scripts/runtime-slo-report.mjs scripts/build-portal-runtime.mjs` 通过。
- 源码目录只读 SLO:
- `ok=true`
- `failures=[]`
- `toolQueueSlo.heartbeatMs=30000`
- `toolQueueSlo.runningWithoutHeartbeatCount=0`
生产部署:
- 备份:
- `/Users/john/Project/memind_backups/20260702-123947-p84-heartbeat-slo`
2026-07-02 12:45:55 +08:00
- `/Users/john/Project/memind_backups/20260702-124520-p84-hourly-soak`
- 已部署:
- `scripts/runtime-slo-report.mjs`
2026-07-02 12:45:55 +08:00
- `scripts/install-runtime-slo-soak-agent.sh`
- 未重启 Portal、worker、goosed 或 H5。
- 现有 SLO LaunchAgent 保持:
- `cn.tkmind.memind-runtime-slo-report`
- 每日 `23:55`
- `--write-report --prune --retention-days 30`
2026-07-02 12:45:55 +08:00
- 新增 soak LaunchAgent:
- `cn.tkmind.memind-runtime-slo-soak`
- `StartInterval=3600`
- `--write-report --prune --retention-days 7`
- report dir `/Users/john/Project/Memind/reports/runtime-slo-soak`
生产验证:
- 生产只读 SLO:
- `ok=true`
- `failures=[]`
- `toolQueueSlo.heartbeatMs=30000`
- `toolQueueSlo.statusCounts={}`
- `toolQueueSlo.oldestRunningHeartbeatAgeMs=0`
- `toolQueueSlo.runningWithoutHeartbeatCount=0`
- 临时 report-dir 写入验证:
- JSON 和 Markdown 均生成成功。
- 两种格式均包含 `heartbeatMs`、`oldestRunningHeartbeatAgeMs`、`runningWithoutHeartbeatCount`。
2026-07-02 12:45:55 +08:00
- soak 快照验证:
- LaunchAgent 已加载。
- 首次 kickstart 快照已写入 JSON/Markdown。
- 最新快照 `ok=true`, `failures=[]`。
- JSON/Markdown 均包含 heartbeat summary。
2026-07-02 06:55:26 +08:00
## 回滚策略
- P0: 修改前保留 `server.mjs` 备份;如启动失败,恢复备份并 `launchctl kickstart` Portal。
- P1: 修改 nginx 前备份 conf;`nginx -t` 成功后再 reload。
- P2: Redis Router 默认可通过 env 开关退回当前 `pickTarget()` round-robin。
- P4: Tool Gateway 默认关闭,通过用户策略逐步放量。