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

3115 lines
111 KiB
Markdown
Raw Permalink Blame History

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