3115 lines
111 KiB
Markdown
3115 lines
111 KiB
Markdown
# 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` 接入 Portal,SSE 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 已对齐到并发 2,worker 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` event,runtime/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 延迟写入 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 报告。
|
||
- 生产同步分支: 已从远程 `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/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 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/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`。
|
||
|
||
生产验证:
|
||
|
||
- `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 默认关闭,通过用户策略逐步放量。
|