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

111 KiB
Raw Permalink Blame History

Memind 2.0 Streaming Agent Runtime 改造计划

历史快照说明:本文记录 2026-07-02 streaming/runtime 改造过程,其中出现的 /Users/john/Project/Memind/MindSpace 是当时的存量数据保护/统计口径。103 当前生产 MindSpace Service 根目录以 103 runtime topology 为准,即 /Users/john/MindSpace;不要把本文历史路径当作当前服务根目录。

更新时间: 2026-07-02

执行状态:

  • P0 StreamController v1: 已完成代码落地、语法检查、Portal kickstart 和基础健康验证。
  • P1 Gateway SSE 配置: 已完成,mm.tkmind.cn 接入 PortalSSE route 已禁缓冲。
  • P2 Redis Runtime State + Router v1: 已完成可回退版本并启用 Redis。
  • P3 Observability: 已完成第一版,只读 runtime 状态接口已上线。
  • P4 Tool Gateway v1: 已完成第一步,普通用户默认不再暴露 Aider/OpenHands,显式用户白名单保留。
  • P6.0 Tool Gateway Queue v0: 已完成最小队列化运行时,/agent/runs 支持全局并发限制、运行超时和 runtime/SLO 可观测状态。
  • P6.1 Tool Worker v1 骨架: 已完成外部 worker 接管入口,生产仍保持 Portal autoDispatch=true,未放量真实外置执行。
  • P6.2 Tool Worker Canary: 已完成短窗口真实接管 canary,外部 worker 成功处理一条 chat-mode queued run,生产已恢复 Portal autoDispatch=true
  • P6.3 Tool Gateway 协议化: 已完成,已新增 agent-run-v1 协议模块,code run 可在显式开启后绕开 goosed session extension 交给 Aider/OpenHands 外部执行器。
  • P6.4 Tool Gateway Real Canary: 已完成 Aider 真实执行 canary,修复 DeepSeek/Aider 模型名前缀问题后,专用空仓库产物生成成功。
  • P6.5 OpenHands Real Canary: 已完成,OpenHands headless/json 在专用空仓库中生成目标产物,生产仍保持默认关闭。
  • P6.6 External Worker Code-run Canary: 已完成,Portal autoDispatch 短窗口关闭,external agent-run-worker --run-id 精确接管一条 code run 并生成 canary 产物,生产已恢复默认关闭。
  • P6.7 Tool Gateway Production Guardrails: 已完成第一步,Tool Gateway 支持 expected file/content 产物校验、stdout/stderr tail 审计和 validation failed 非重试失败语义,已发布生产且默认关闭。
  • P6.8 Tool Gateway Controlled Rollout 基础: 已完成,external agent-run-worker LaunchAgent installer 已进入 runtime,生产已安装为 disabled/not running,后续可显式开启灰度。
  • P6.9 Validated External Worker Canary: 已完成,短窗口启动 external worker,先验证 exit 0 但内容不符会 failed,再验证 expected content 命中会 succeeded;生产已恢复默认关闭,worker 已恢复 disabled/not running。
  • P6.10 External Worker Observability: 已完成,只读 check-agent-run-worker.mjs 已进入 runtime,可检查 worker LaunchAgent、queue lag、最近失败 run;生产验证 ok=true
  • P6.11 Single-user Rollout Policy: 已完成默认关闭的 code-run 放量策略门禁,支持 user allowlist、task type allowlist、required validation,并在 runtime/status 暴露当前策略。
  • P6.12 All-user Long-running Gray: 已完成,生产已默认对所有用户开启 H5 code-run 灰度,external worker running,普通测试用户真实 code-run 已成功生成 MindSpace 产物。
  • P6.13 Agent Run Auto-pause Guard: 已完成,新增 agent-run-guard.mjs 和 LaunchAgent,按失败数/队列积压/running 超时自动关闭 code-run gate 并停 worker。
  • P6.15 H5 Code-run Validation Metadata: 已完成,H5 code-run 自动声明 .memind/agent-runs/<requestId>.json receipt 校验,生产已恢复 MEMIND_AGENT_CODE_RUNS_REQUIRE_VALIDATION=1 并通过普通测试用户真实路径。
  • P6.17 Controlled Worker Concurrency Canary: 已完成,external worker 短窗口提升到并发 2,两条真实 code-run 同时 running 并全部通过 receipt validation。
  • P6.18 Concurrency Rollout Policy: 已完成,生产 worker 和 Portal runtime/status 已对齐到并发 2worker KeepAlive 保持开启,保留回滚备份。
  • P6.19 Task-level Artifact Validation: 已完成第一步,H5 code-run 会在 receipt 之外追加可推断的 public/*.html 目标文件校验,页面编辑 code-run 会追加 page-edit task receipt,生产真实 canary 三项 expectedFiles 全部通过。
  • P8.1 Queue Lease / Stuck Run Recovery: 已完成第一步,worker dispatch 前自动回收超时 running run,新增 --recover-stale dry-run/apply 运维入口,生产 synthetic stale run 验证通过。
  • P8.2 Portal DB/Auth Transient Error Hardening: 已完成第一步,session attach、/auth/status 和 API auth middleware 已捕获 DB/auth 瞬时错误,生产部署后 live health、auth/status、runtime/status、guard 和 SLO 验证通过。
  • P8.3 Worker Lease Heartbeat: 已完成第一步,running run 执行期间写入 worker_heartbeat eventruntime/status、worker check、guard 和 stale recovery 均优先使用 heartbeat age,生产 synthetic 与真实 worker canary 验证通过。
  • P8.4 Long-running Gray Soak and Heartbeat SLO: 已完成第二步,runtime SLO JSON/Markdown 报告加入 agent-run heartbeat 摘要和 failure 条件,并安装独立小时级 soak 快照;继续保持并发 2 soak,不扩大灰度。
  • P5.15 Active Stream TTL Reconcile: 已按用户要求跳过,暂不做报表/定时 reconcile。
  • P5 Worker Pool 运维化: 已完成第一步,Redis Router 支持 worker drain。
  • P5.9 First-token Latency EWMA: 已完成,StreamController 会把首个 SSE chunk 延迟写入 RedisSLO 报告已展示。
  • P5.10 First-token Latency 趋势报表: 已完成 Redis rolling samplesruntime/SLO 已展示最近 5 分钟和 1 小时 p50/p95。
  • P5.11 SLO 日报: 已完成 runtime-slo-report.mjs --write-report,可输出 JSON/Markdown 运维报告。
  • P5.12 SLO 日报定时器: 已完成 LaunchAgent,每日 23:55 自动生成 runtime SLO JSON/Markdown 报告。
  • 生产同步分支: 已从远程 origin/main 新建干净副本和分支 memind-streaming-runtime-20260702,用于远程开发机后续直接拉取。
  • P3.5/P5.5 生产化补强: 已完成 runtime metrics、健康检查脚本、drain 运维脚本、自动采样 LaunchAgent 和 SLO 只读报告。

目标

把当前 Memind H5 + multi-goosed 架构升级为 streaming-first agent runtime:

  • 易扩展: goosed worker pool 横向扩展,调度层按压力分配新会话。
  • 低延迟: SSE 首 token 优先,代理层禁缓冲、及时 flush、处理 backpressure。
  • 高稳定: 流式链路、工具执行、worker 状态、持久化状态分层隔离。

当前基线

  • Memind Portal: /Users/john/Project/Memind/server.mjs,本机 :8081
  • H5 public base: https://mm.tkmind.cnm.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 代理尚未完整处理 flushHeadersX-Accel-Buffering、客户端断开 abort、写入 backpressure 和统一 pipeline 收尾。

目标架构

flowchart LR
  U["H5 / WeChat / Web"] --> E["Edge Gateway (Nginx / Envoy)"]
  E --> S["Stream Controller (Memind Portal v2)"]
  S --> R["Router / Scheduler"]
  R <--> Redis["Redis Runtime State"]
  S <--> Redis
  R --> G1["goosed-prod-1"]
  R --> G2["goosed-prod-2"]
  R --> G3["goosed-prod-3"]
  R --> G4["goosed-prod-4"]
  G1 --> T["Tool Gateway"]
  G2 --> T
  G3 --> T
  G4 --> T
  T --> A["Aider Service"]
  T --> O["OpenHands Service"]
  S --> PG["PostgreSQL / MySQL Persistence"]

分层原则

  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:

worker:{id}:heartbeat
worker:{id}:active_streams
worker:{id}:active_sessions
worker:{id}:ewma_first_token_ms
worker:{id}:error_rate
worker:{id}:memory_pressure
worker:{id}:drain
session:{session_id}:worker
stream:{stream_id}:status
stream:{stream_id}:started_at

调度分数:

score =
  active_streams * 3
+ active_sessions * 1
+ ewma_first_token_ms * 0.01
+ error_rate * 5
+ memory_pressure * 2

原则:

  • 已存在 session 优先使用 session:{id}:worker
  • 新 session 选择最低 score 的健康 worker。
  • heartbeat 过期 worker 不接新 session。
  • 不做无损中途迁移,除非 goose 支持完整 session restore。

P3: Observability

目标: 调度不靠感觉。

指标:

  • stream open/close/abort count
  • active_streams gauge
  • first_token_latency_ms
  • stream_duration_ms
  • worker fetch error rate
  • billing finish count/error count

先写日志和内存聚合,后续接 Prometheus 或 Redis。

P4: Tool Gateway v1

目标: Aider/OpenHands 工具隔离。

任务:

  • 普通聊天默认不暴露 Aider/OpenHands。
  • 代码任务按策略启用 Aider 或 OpenHands。
  • Tool Gateway 提供 queue、timeout、retry、并发上限、失败熔断。
  • stream 中输出 tool progress event,避免用户界面无反馈。

第一阶段实际落地:

  • 普通用户默认不暴露 Aider/OpenHands。
  • 保留显式 user capability override,作为白名单。
  • 后续再将 Aider/OpenHands 从 goosed platform extension 进一步拆成 queue-based Tool Gateway。

P5: Worker Pool 运维化

目标: 多 goosed 的发布、回滚、扩缩容可控。

任务:

  • 固化 goosed-prod-1..N compose/启动配置。
  • 每个 worker 独立日志、健康检查、资源上限。
  • drain 模式: 不接新 session,等 active stream 清零后升级。
  • 保留一键回滚镜像 tag。

Drain 操作:

docker exec memind-runtime-redis redis-cli SET memind:runtime:worker:goosed-3:drain 1
curl -sk https://mm.tkmind.cn/api/runtime/status
docker exec memind-runtime-redis redis-cli DEL memind:runtime:worker:goosed-3:drain

说明:

  • drain=1 后 Router 不再给该 worker 分配新 session。
  • 已存在 session 仍保持 worker affinity,不做中途迁移。
  • activeStreams=0 后再重启或升级该 worker。

执行顺序

  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-8no-cache, no-transformX-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.cnVITE_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 300sproxy_send_timeout 300s
  • 微信菜单脚本默认入口改为 https://mm.tkmind.cnhttps://mm.tkmind.cn/space
  • server.mjsH5_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 的 targetactiveStreamsactiveSessionsewmaFirstTokenMserrorRatememoryPressureheartbeat
    • 每个 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=truenamespace 为 memind:runtime
  • 返回中四个目标 18006..18009healthy: 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 曾将 aideropenhands 覆盖为 allowed=1
  • 已将 h5_capability_grantssubject_type='role' AND subject_id='user'aideropenhands 改为 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.gitorigin/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=useraider / openhands 均为 false
  • 显式白名单用户数为 2
  • 白名单用户 chat mode 不含 Aider/OpenHandscode mode 具备 Aider/OpenHands。
  • scripts/check-stream-runtime.mjs 返回 ok=true,四个 worker healthy。
  • scripts/runtime-worker-drain.mjs status 显示四个 worker activeStreams=0

生产部署:

  • 允许重启生产 Portal,已使用 launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal 生效。
  • 本阶段不写数据库、不删除数据、不修改 /Users/john/Project/Memind/MindSpace
  • 生产代码备份: /Users/john/Project/memind_backups/20260702-071020-pre-p45-tool-guard

2026-07-02 P4.6 Code Task 入口接线

目标:

  • 在不引入新表、不改生产数据的前提下,让显式代码任务可以进入 toolMode='code'
  • 普通 H5 聊天继续默认 toolMode='chat',不注入 Aider/OpenHands。
  • 对已有 session 的 code run 也先 reconcile 到 code policy,再提交用户消息。

已完成:

  • POST /api/agent/runs 支持 tool_mode / toolModetask_type / taskType
  • 支持的 code mode alias:
    • code
    • code-task
    • code_task
    • code-tool
    • code_tool
    • code_tool_task
  • 未知 tool_mode 返回 400,避免误开高权限工具。
  • run 创建时将运行模式写入既有 user_message_json.metadata.memindRun:
    • toolMode
    • taskType
  • 新建 code run session 时使用 userAuth.getCodeAgentSessionPolicy(userId)
  • 提交 code run 到已有 session 前,submitSessionReplyForUser(..., { toolMode: 'code' }) 会按 code policy reconcile。

安全边界:

  • 本阶段不写数据库 schema。
  • 本阶段不修改或删除 /Users/john/Project/Memind/MindSpace
  • Aider/OpenHands 仍只对白名单用户在 code mode 下按 policy 注入。
  • 普通 chat mode 仍保持无 Aider/OpenHands。

本地验证:

  • node --check agent-run-gateway.mjs agent-run-routes.mjs tkmind-proxy.mjs 通过。
  • node --test agent-run-gateway.test.mjs agent-run-routes.test.mjs capabilities.test.mjs policies.test.mjs user-memory-profile.test.mjs tkmind-proxy.test.mjs user-publish.test.mjs message-stream.test.mjs 通过,80 tests pass。

生产部署:

  • 生产代码备份: /Users/john/Project/memind_backups/20260702-071604-pre-p46-code-task-entry
  • 已将 .runtime/portal/server.mjs 部署为 /Users/john/Project/Memind/server.mjs
  • 已执行 launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal
  • 未修改 PG 数据,未修改或删除 /Users/john/Project/Memind/MindSpace

生产验证:

  • https://mm.tkmind.cn/api/status 返回 ok
  • https://mm.tkmind.cn/api/runtime/status 返回 router enabled,四个 goosed target healthy。
  • scripts/check-stream-runtime.mjs 返回 ok=true
  • scripts/check-tool-runtime.mjs 返回 ok=true,普通 chat 不注入 Aider/OpenHandscode mode 对白名单用户可注入。
  • scripts/runtime-worker-drain.mjs status 返回 ok=true,四个 worker drain=false。
  • 观察项: goosed-1 activeStreams=2,无 error;未强制重置,避免误伤生产中的真实 stream。

2026-07-02 P4.7 H5 Code Run 灰度入口

目标:

  • 将 H5 侧 agent run 创建入口接到 P4.6 的 tool_mode
  • 默认关闭灰度,避免普通生产聊天自动扩大 Aider/OpenHands 暴露面。
  • 远程开发机拉取分支后,可通过构建时 env 开关灰度验证。

已完成:

  • createAgentRun() 支持可选 toolMode / taskType,请求体映射为:
    • tool_mode
    • task_type
  • 新增前端 helper src/utils/agentRunMode.ts
  • 新增构建时开关:
    • VITE_AGENT_CODE_RUNS_ENABLED
    • VITE_AGENT_CODE_RUNS_AUTODETECT
  • 开关默认关闭。
  • 页面编辑子聊天在 VITE_AGENT_CODE_RUNS_ENABLED=1 时传 tool_mode=code
  • 普通 H5 聊天仅在 VITE_AGENT_CODE_RUNS_ENABLED=1VITE_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/runstool_mode 入口。
    • /api/runtime/status.toolRuntime 是否保持:
      • defaultMode='chat'
      • codeToolMode='code'
      • chatInjectsCodeTools=false
    • role=useraider / openhands 默认是否仍为 false。
    • 白名单用户 code mode 是否具备代码工具能力。
    • 明确输出 writes.database=falsewrites.mindSpace=falsecreatesAgentRun=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.mjsscripts/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 > 0last_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.mjsRUNBOOK.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=truedryRun=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/MindSpacedata/mindspace 文件/目录/字节数。
    • 读取 Redis runtime key 摘要。
    • 检查 worker target health、container health、metrics freshness、stream errors、chat/code tool gate。
    • 输出 writes.database=falsewrites.mindSpace=falsewrites.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 502Portal 随后正常监听 :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 502Portal 随后正常监听 :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 502Portal 随后正常监听 :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 502Portal 随后正常监听 :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/runstool_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
    • .envMEMIND_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=sidecarheartbeatFreshheartbeatOkheartbeatLatencyMs
  • 不修改 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_runsh5_agent_run_events,不新增生产 schema migration。
  • 默认关闭,只有 MEMIND_TOOL_GATEWAY_ENABLED=1toolMode=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.mjsscripts/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=[]
    • .envMEMIND_TOOL_GATEWAY_*
    • .envMEMIND_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.

恢复/最终状态:

  • 生产 .envMEMIND_TOOL_GATEWAY_*
  • 生产 .envMEMIND_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.

恢复/最终状态:

  • 生产 .envMEMIND_TOOL_GATEWAY_*
  • 生产 .envMEMIND_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_OVERRIDEMEMIND_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.validationmetadata.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=falsetoolGateway.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.validationmetadata.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=0H5 默认 code-run 不强制声明 expected file validation;带 validation 的任务仍走 Tool Gateway 校验。
    • MEMIND_AGENT_RUN_AUTODISPATCH=0Portal 不内联派发 code-run。
    • MEMIND_TOOL_GATEWAY_ENABLED=0Portal 侧 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 前自动回收超过 runTimeoutMsrunning 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、pageIdpageTitle

测试:

  • 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 均生成成功。
    • 两种格式均包含 heartbeatMsoldestRunningHeartbeatAgeMsrunningWithoutHeartbeatCount
  • soak 快照验证:
    • LaunchAgent 已加载。
    • 首次 kickstart 快照已写入 JSON/Markdown。
    • 最新快照 ok=true, failures=[]
    • JSON/Markdown 均包含 heartbeat summary。

回滚策略

  • P0: 修改前保留 server.mjs 备份;如启动失败,恢复备份并 launchctl kickstart Portal。
  • P1: 修改 nginx 前备份 confnginx -t 成功后再 reload。
  • P2: Redis Router 默认可通过 env 开关退回当前 pickTarget() round-robin。
  • P4: Tool Gateway 默认关闭,通过用户策略逐步放量。