111 KiB
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接入 Portal,SSE route 已禁缓冲。 - P2 Redis Runtime State + Router v1: 已完成可回退版本并启用 Redis。
- P3 Observability: 已完成第一版,只读 runtime 状态接口已上线。
- P4 Tool Gateway v1: 已完成第一步,普通用户默认不再暴露 Aider/OpenHands,显式用户白名单保留。
- P6.0 Tool Gateway Queue v0: 已完成最小队列化运行时,
/agent/runs支持全局并发限制、运行超时和 runtime/SLO 可观测状态。 - P6.1 Tool Worker v1 骨架: 已完成外部 worker 接管入口,生产仍保持 Portal
autoDispatch=true,未放量真实外置执行。 - P6.2 Tool Worker Canary: 已完成短窗口真实接管 canary,外部 worker 成功处理一条 chat-mode queued run,生产已恢复 Portal
autoDispatch=true。 - P6.3 Tool Gateway 协议化: 已完成,已新增
agent-run-v1协议模块,code run 可在显式开启后绕开 goosed session extension 交给 Aider/OpenHands 外部执行器。 - P6.4 Tool Gateway Real Canary: 已完成 Aider 真实执行 canary,修复 DeepSeek/Aider 模型名前缀问题后,专用空仓库产物生成成功。
- P6.5 OpenHands Real Canary: 已完成,OpenHands headless/json 在专用空仓库中生成目标产物,生产仍保持默认关闭。
- P6.6 External Worker Code-run Canary: 已完成,Portal autoDispatch 短窗口关闭,external
agent-run-worker --run-id精确接管一条 code run 并生成 canary 产物,生产已恢复默认关闭。 - P6.7 Tool Gateway Production Guardrails: 已完成第一步,Tool Gateway 支持 expected file/content 产物校验、stdout/stderr tail 审计和 validation failed 非重试失败语义,已发布生产且默认关闭。
- P6.8 Tool Gateway Controlled Rollout 基础: 已完成,external agent-run-worker LaunchAgent installer 已进入 runtime,生产已安装为 disabled/not running,后续可显式开启灰度。
- P6.9 Validated External Worker Canary: 已完成,短窗口启动 external worker,先验证 exit 0 但内容不符会 failed,再验证 expected content 命中会 succeeded;生产已恢复默认关闭,worker 已恢复 disabled/not running。
- P6.10 External Worker Observability: 已完成,只读
check-agent-run-worker.mjs已进入 runtime,可检查 worker LaunchAgent、queue lag、最近失败 run;生产验证ok=true。 - P6.11 Single-user Rollout Policy: 已完成默认关闭的 code-run 放量策略门禁,支持 user allowlist、task type allowlist、required validation,并在 runtime/status 暴露当前策略。
- P6.12 All-user Long-running Gray: 已完成,生产已默认对所有用户开启 H5 code-run 灰度,external worker running,普通测试用户真实 code-run 已成功生成 MindSpace 产物。
- P6.13 Agent Run Auto-pause Guard: 已完成,新增
agent-run-guard.mjs和 LaunchAgent,按失败数/队列积压/running 超时自动关闭 code-run gate 并停 worker。 - P6.15 H5 Code-run Validation Metadata: 已完成,H5 code-run 自动声明
.memind/agent-runs/<requestId>.jsonreceipt 校验,生产已恢复MEMIND_AGENT_CODE_RUNS_REQUIRE_VALIDATION=1并通过普通测试用户真实路径。 - P6.17 Controlled Worker Concurrency Canary: 已完成,external worker 短窗口提升到并发 2,两条真实 code-run 同时 running 并全部通过 receipt validation。
- P6.18 Concurrency Rollout Policy: 已完成,生产 worker 和 Portal runtime/status 已对齐到并发 2,worker KeepAlive 保持开启,保留回滚备份。
- P6.19 Task-level Artifact Validation: 已完成第一步,H5 code-run 会在 receipt 之外追加可推断的
public/*.html目标文件校验,页面编辑 code-run 会追加 page-edit task receipt,生产真实 canary 三项 expectedFiles 全部通过。 - P8.1 Queue Lease / Stuck Run Recovery: 已完成第一步,worker dispatch 前自动回收超时 running run,新增
--recover-staledry-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_heartbeatevent,runtime/status、worker check、guard 和 stale recovery 均优先使用 heartbeat age,生产 synthetic 与真实 worker canary 验证通过。 - P8.4 Long-running Gray Soak and Heartbeat SLO: 已完成第二步,runtime SLO JSON/Markdown 报告加入 agent-run heartbeat 摘要和 failure 条件,并安装独立小时级 soak 快照;继续保持并发 2 soak,不扩大灰度。
- P5.15 Active Stream TTL Reconcile: 已按用户要求跳过,暂不做报表/定时 reconcile。
- P5 Worker Pool 运维化: 已完成第一步,Redis Router 支持 worker drain。
- P5.9 First-token Latency EWMA: 已完成,StreamController 会把首个 SSE chunk 延迟写入 Redis,SLO 报告已展示。
- P5.10 First-token Latency 趋势报表: 已完成 Redis rolling samples,runtime/SLO 已展示最近 5 分钟和 1 小时 p50/p95。
- P5.11 SLO 日报: 已完成
runtime-slo-report.mjs --write-report,可输出 JSON/Markdown 运维报告。 - P5.12 SLO 日报定时器: 已完成 LaunchAgent,每日 23:55 自动生成 runtime SLO JSON/Markdown 报告。
- 生产同步分支: 已从远程
origin/main新建干净副本和分支memind-streaming-runtime-20260702,用于远程开发机后续直接拉取。 - P3.5/P5.5 生产化补强: 已完成 runtime metrics、健康检查脚本、drain 运维脚本、自动采样 LaunchAgent 和 SLO 只读报告。
目标
把当前 Memind H5 + multi-goosed 架构升级为 streaming-first agent runtime:
- 易扩展: goosed worker pool 横向扩展,调度层按压力分配新会话。
- 低延迟: SSE 首 token 优先,代理层禁缓冲、及时 flush、处理 backpressure。
- 高稳定: 流式链路、工具执行、worker 状态、持久化状态分层隔离。
当前基线
- Memind Portal:
/Users/john/Project/Memind/server.mjs,本机:8081。 - H5 public base:
https://mm.tkmind.cn;m.tkmind.cn暂时不再作为 H5 public base。 - goosed worker pool: Docker/Colima 内
goosed-prod-1..4,宿主机18006..18009,当前健康。 - H5 上游配置:
TKMIND_API_TARGETS=https://127.0.0.1:18006,...,18009。 - 当前已有 session affinity: start 时分配 worker,后续 reply/events 回到同一 worker。
- imgproxy: 原生
127.0.0.1:20082,兼容代理10.10.0.2:20081。 - 风险点: SSE 代理尚未完整处理
flushHeaders、X-Accel-Buffering、客户端断开 abort、写入 backpressure 和统一 pipeline 收尾。
目标架构
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"]
分层原则
- Control Plane: Redis + Router,负责 worker 心跳、active stream、错误率、首 token 延迟、session worker pointer。
- Stream Data Plane: Memind Portal StreamController + goosed worker,负责低延迟流式传输。
- Persistence: 现有数据库只保存最终状态、会话历史、计费、发布记录,不保存高频 runtime state。
- Worker Affinity: goosed 尽量业务无状态,但 session 运行期必须 worker-affine;已存在 session 优先回原 worker。
- Tool Isolation: Aider/OpenHands 从默认全开逐步改为按任务启用、队列化、限流、超时和熔断。
阶段计划
P0: StreamController v1 加固
目标: 不引入新基础设施,先把现有 H5 SSE 链路做稳。
任务:
- 为
/api/sessions/:sessionId/events增加 SSE 专用响应头:Content-Type: text/event-stream; charset=utf-8Cache-Control: no-cache, no-transformConnection: keep-aliveX-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 offproxy_cache offgzip offproxy_read_timeout 3600proxy_send_timeout 3600add_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..Ncompose/启动配置。 - 每个 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。
执行顺序
- P0 立即执行,低风险高收益。
- P1 基于
mm.tkmind.cn新入口执行;除非明确回滚,不再改造 105 H5 转发链路。 - P2/P3 一起推进,先观测再调度。
- 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增加 upstreamAbortController,客户端断开时主动 abort goosed SSE fetch。- SSE 响应头升级为
text/event-stream; charset=utf-8、no-cache, no-transform、X-Accel-Buffering: no。 - 响应开始后调用
flushHeaders()。 - 用
Writablesink 处理res.write()backpressure,并保留 billing balance event 注入。 - 用
pipeline(source, sanitizer, billing, sink)统一管理 SSE 管道收尾。 - 通过
launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal让变更生效。
验证:
/opt/homebrew/opt/node@24/bin/node --check /Users/john/Project/Memind/server.mjs通过。- Portal 新 PID:
89465,监听*:8081。 http://127.0.0.1:8081/api/status返回ok。https://127.0.0.1:18006/status返回ok。https://127.0.0.1:18007/status返回ok。https://127.0.0.1:18008/status返回ok。https://127.0.0.1:18009/status返回ok。
2026-07-02 H5 public base 临时切换
决策:
- H5 public base 从
https://m.tkmind.cn临时切换为https://mm.tkmind.cn。 - 后续 H5 公网访问不再走 105 转发链路。
- 旧链路
m.tkmind.cn -> 105 nginx -> 127.0.0.1:19081 -> reverse SSH tunnel -> Portal :8081仅保留为 legacy/rollback-only 说明,不作为 Memind 2.0 改造目标。
变更文件:
/Users/john/Project/Memind/.env/Users/john/Project/Memind/scripts/run-memind-portal-prod.sh/Users/john/Project/Memind/RUNBOOK.txt/Users/john/Project/memind_architecture/memind-2-streaming-agent-runtime-plan.md
已执行:
- 将
H5_PUBLIC_BASE_URL改为https://mm.tkmind.cn。 - 将
VITE_MINDSPACE_BASE改为https://mm.tkmind.cn。 - 将 Portal 启动脚本默认
H5_PUBLIC_BASE_URL改为https://mm.tkmind.cn。 - 更新
RUNBOOK.txt,明确 105 转发链路不再作为后续 H5 公网路径。 - 通过
launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal重启 Portal。
验证:
- Portal 新 PID:
94324,监听*:8081。 - 运行环境确认:
H5_PUBLIC_BASE_URL=https://mm.tkmind.cn,VITE_MINDSPACE_BASE=https://mm.tkmind.cn。 - 本机
http://127.0.0.1:8081/api/status返回ok。 - 四个 goosed worker
18006..18009均返回ok。 - 公网
https://mm.tkmind.cn/api/status当前返回 nginx502 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 反向代理到 Portal127.0.0.1:8081。- 为
/api/sessions/<id>/events和/api/agent/runs/<id>/events增加 SSE 专用 nginx location:proxy_buffering offproxy_cache offgzip offproxy_read_timeout 3600sproxy_send_timeout 3600sadd_header X-Accel-Buffering no always
- 通用
location /增加proxy_read_timeout 300s和proxy_send_timeout 300s。 - 微信菜单脚本默认入口改为
https://mm.tkmind.cn和https://mm.tkmind.cn/space。 server.mjs内H5_PUBLIC_BASE_URL缺省值改为https://mm.tkmind.cn。
验证:
nginx -t通过。nginx -s reload已执行。https://mm.tkmind.cn/api/status返回ok。- 未登录访问
https://mm.tkmind.cn/api/sessions/test-session/events返回401,且响应头包含x-accel-buffering: no。 - 未登录访问
https://mm.tkmind.cn/api/agent/runs/test-run/events返回401,且响应头包含x-accel-buffering: no。
2026-07-02 P2 Redis Runtime State + Router v1
变更文件:
/Users/john/Project/Memind/server.mjs/Users/john/Project/Memind/.env
新增运行组件:
- Docker 容器:
memind-runtime-redis - 镜像:
redis:7-alpine - 绑定:
127.0.0.1:6379->6379 - 持久化卷:
memind-runtime-redis-data - 启动参数:
redis-server --appendonly yes - 重启策略:
unless-stopped
已完成:
- 新增可选
createRuntimeRouter。 MEMIND_RUNTIME_REDIS_URL未配置时自动回退到原 round-robin。MEMIND_RUNTIME_REDIS_URL配置后启用 Redis scheduler。- 新 session 选择最低 score 的健康 worker;同分时仍按轮转顺序打散,避免全部压到第一个 worker。
- session pointer 写入 Redis:
memind:runtime:session:<id>:workermemind:runtime:session:<id>:target
- SSE 打开/关闭维护 Redis active stream:
memind:runtime:worker:<id>:active_streamsmemind:runtime:worker:<id>:heartbeatmemind: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=1memind:runtime:worker:goosed-3:heartbeat=<timestamp>memind:runtime:stream:20260701_24:status=active
https://mm.tkmind.cn/api/status返回ok。- 四个 goosed worker
18006..18009均返回ok。
2026-07-02 P3 Observability v1
变更文件:
/Users/john/Project/Memind/server.mjs
已完成:
- 新增只读接口
GET /api/runtime/status。 - 接口返回:
publicBaseUrl- Redis Router 是否启用
- Redis namespace
- 每个 worker 的
target、activeStreams、activeSessions、ewmaFirstTokenMs、errorRate、memoryPressure、heartbeat - 每个
TKMIND_API_TARGETS上游的/status健康状态
- 该接口与
/api/status一样作为运维健康读接口,不要求登录;不返回任何密钥。
验证:
/opt/homebrew/opt/node@24/bin/node --check /Users/john/Project/Memind/server.mjs通过。https://mm.tkmind.cn/api/runtime/status返回ok: true。- 返回中
router.enabled=true,namespace 为memind:runtime。 - 返回中四个目标
18006..18009均healthy: true。 https://mm.tkmind.cn/api/status返回ok。- 未登录访问
https://mm.tkmind.cn/api/sessions/test-session/events返回401,且响应头包含x-accel-buffering: no。
2026-07-02 P4 Tool Gateway v1 第一阶段
已完成:
- 只读验证发现数据库中
role=user曾将aider、openhands覆盖为allowed=1。 - 已将
h5_capability_grants中subject_type='role' AND subject_id='user'的aider、openhands改为allowed=0。 - 保留两个显式 user override:
a6fb1e97-2b0f-447b-b138-4561d8e5c53ea70ff537-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返回四个 workerhealthy=true。https://mm.tkmind.cn/api/runtime/status返回四个 workerdrain=false。https://mm.tkmind.cn/api/status返回ok。- 未登录访问
https://mm.tkmind.cn/api/sessions/test-session/events返回401,且响应头包含x-accel-buffering: no。
2026-07-02 生产同步分支
决策:
- 当前是生产环境,改造过程中不随意删除数据、不清理持久化目录、不覆盖旧本地脏改动。
/Users/john/Project/Memind是生产运行产物目录,不是 git 工作树。- 生产 release manifest 指向
git_head=4fc59729ee222734628dca001e172c127c84488f。 - 远端
https://git.tkmind.cn/tkmind/memind.git的origin/main当前正是该提交。
已执行:
- 新建干净源码目录
/Users/john/Project/memind-clean-main-20260702。 - 从
origin/main创建分支memind-streaming-runtime-20260702。 - 迁入本次已在生产验证的 StreamController、Redis Router、runtime status、
mm.tkmind.cn和 nginx SSE 配置样例。 - 新增远程开发机同步说明
docs/architecture/remote-dev-sync.md。
限制:
- 不提交生产
.env、数据库、MindSpace/、data/、users/、.tailscale/、logs/、证书和密钥。 - 只提交代码、配置模板、部署样例和架构/运维文档。
2026-07-02 P3.5/P5.5 生产数据保护与运维补强
生产数据保护:
- 允许重启生产服务,但重启前必须保护好数据库和
/Users/john/Project/Memind/MindSpace。 - 备份目录:
/Users/john/Project/memind_backups/20260702-065813-pre-p35-p55。 MindSpace使用rsync -a全量副本,文件数2137,大小约120M。- 数据库为 Aliyun RDS MySQL
goose,已导出:mysql-schema.sqlmysql-manifest.jsonmysql-jsonl/*.jsonl- 表数
85,行数28296
改造内容:
- Redis Router worker 状态增加:
stream_open_countstream_abort_countstream_error_countlast_stream_started_atlast_stream_ended_atscore
- 新增只读检查脚本
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_msmetadata.runtime_scope='code_tool_task'
userAuth.getAgentSessionPolicy(userId)默认返回 chat policy。- 新增
userAuth.getCodeAgentSessionPolicy(userId)作为后续代码任务入口。 /api/runtime/status增加toolRuntime摘要:defaultModecodeToolModechatInjectsCodeToolsaiderTimeoutMsopenhandsTimeoutMs
- 新增生产只读检查脚本
scripts/check-tool-runtime.mjs。
生产验证:
scripts/check-tool-runtime.mjs返回ok=true。role=user的aider/openhands均为false。- 显式白名单用户数为
2。 - 白名单用户 chat mode 不含 Aider/OpenHands,code mode 具备 Aider/OpenHands。
scripts/check-stream-runtime.mjs返回ok=true,四个 worker healthy。scripts/runtime-worker-drain.mjs status显示四个 workeractiveStreams=0。
生产部署:
- 允许重启生产 Portal,已使用
launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal生效。 - 本阶段不写数据库、不删除数据、不修改
/Users/john/Project/Memind/MindSpace。 - 生产代码备份:
/Users/john/Project/memind_backups/20260702-071020-pre-p45-tool-guard。
2026-07-02 P4.6 Code Task 入口接线
目标:
- 在不引入新表、不改生产数据的前提下,让显式代码任务可以进入
toolMode='code'。 - 普通 H5 聊天继续默认
toolMode='chat',不注入 Aider/OpenHands。 - 对已有 session 的 code run 也先 reconcile 到 code policy,再提交用户消息。
已完成:
POST /api/agent/runs支持tool_mode/toolMode与task_type/taskType。- 支持的 code mode alias:
codecode-taskcode_taskcode-toolcode_toolcode_tool_task
- 未知
tool_mode返回 400,避免误开高权限工具。 - run 创建时将运行模式写入既有
user_message_json.metadata.memindRun:toolModetaskType
- 新建 code run session 时使用
userAuth.getCodeAgentSessionPolicy(userId)。 - 提交 code run 到已有 session 前,
submitSessionReplyForUser(..., { toolMode: 'code' })会按 code policy reconcile。
安全边界:
- 本阶段不写数据库 schema。
- 本阶段不修改或删除
/Users/john/Project/Memind/MindSpace。 - Aider/OpenHands 仍只对白名单用户在 code mode 下按 policy 注入。
- 普通 chat mode 仍保持无 Aider/OpenHands。
本地验证:
node --check agent-run-gateway.mjs agent-run-routes.mjs tkmind-proxy.mjs通过。node --test agent-run-gateway.test.mjs agent-run-routes.test.mjs capabilities.test.mjs policies.test.mjs user-memory-profile.test.mjs tkmind-proxy.test.mjs user-publish.test.mjs message-stream.test.mjs通过,80 tests pass。
生产部署:
- 生产代码备份:
/Users/john/Project/memind_backups/20260702-071604-pre-p46-code-task-entry。 - 已将
.runtime/portal/server.mjs部署为/Users/john/Project/Memind/server.mjs。 - 已执行
launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal。 - 未修改 PG 数据,未修改或删除
/Users/john/Project/Memind/MindSpace。
生产验证:
https://mm.tkmind.cn/api/status返回ok。https://mm.tkmind.cn/api/runtime/status返回 router enabled,四个 goosed target healthy。scripts/check-stream-runtime.mjs返回ok=true。scripts/check-tool-runtime.mjs返回ok=true,普通 chat 不注入 Aider/OpenHands,code mode 对白名单用户可注入。scripts/runtime-worker-drain.mjs status返回ok=true,四个 worker drain=false。- 观察项:
goosed-1 activeStreams=2,无 error;未强制重置,避免误伤生产中的真实 stream。
2026-07-02 P4.7 H5 Code Run 灰度入口
目标:
- 将 H5 侧 agent run 创建入口接到 P4.6 的
tool_mode。 - 默认关闭灰度,避免普通生产聊天自动扩大 Aider/OpenHands 暴露面。
- 远程开发机拉取分支后,可通过构建时 env 开关灰度验证。
已完成:
createAgentRun()支持可选toolMode/taskType,请求体映射为:tool_modetask_type
- 新增前端 helper
src/utils/agentRunMode.ts。 - 新增构建时开关:
VITE_AGENT_CODE_RUNS_ENABLEDVITE_AGENT_CODE_RUNS_AUTODETECT
- 开关默认关闭。
- 页面编辑子聊天在
VITE_AGENT_CODE_RUNS_ENABLED=1时传tool_mode=code。 - 普通 H5 聊天仅在
VITE_AGENT_CODE_RUNS_ENABLED=1且VITE_AGENT_CODE_RUNS_AUTODETECT=1时,按明确代码/仓库/测试/重构类文本命中后传tool_mode=code。
本地验证:
npm run build通过。- 当前构建未设置灰度 env,因此生产默认行为仍为 chat mode。
安全边界:
- 本阶段不改 PG,不改 schema。
- 本阶段不修改或删除
/Users/john/Project/Memind/MindSpace。 tool_mode=code到达后端后仍受 P4.6/P4.5 policy 约束;非白名单用户不会获得 Aider/OpenHands。
2026-07-02 P4.8 Code Run 灰度前只读检查
目标:
- 固化灰度前检查步骤,避免靠人工记忆判断 H5 code-run 链路是否安全。
- 检查过程不创建 agent run,不写数据库,不修改 MindSpace。
已完成:
- 新增只读脚本
scripts/check-agent-code-run-entry.mjs。 - 检查内容:
- 当前 H5
distbundle 是否包含/agent/runs和tool_mode入口。 /api/runtime/status.toolRuntime是否保持:defaultMode='chat'codeToolMode='code'chatInjectsCodeTools=false
role=user的aider/openhands默认是否仍为 false。- 白名单用户 code mode 是否具备代码工具能力。
- 明确输出
writes.database=false、writes.mindSpace=false、createsAgentRun=false。
- 当前 H5
scripts/build-portal-runtime.mjs已将该脚本复制到 runtime artifact,并在 RUNBOOK 中列出。
验证:
node --check scripts/check-agent-code-run-entry.mjs scripts/build-portal-runtime.mjs通过。- 使用生产 env 验证:
MEMIND_ENV_FILE=/Users/john/Project/Memind/.env node scripts/check-agent-code-run-entry.mjs- 返回
ok=true。
2026-07-02 P4.9 Code Run 后端灰度门禁
目标:
- 在 H5 构建开关之外,再增加 Portal 后端开关,防止客户端直接提交
tool_mode=code绕过前端灰度。 - 默认关闭,不影响普通 chat run。
已完成:
- 新增后端 env:
MEMIND_AGENT_CODE_RUNS_ENABLED=1
POST /api/agent/runs收到tool_mode=code且后端开关未开启时返回 403:代码任务灰度未开启
- 普通
tool_mode=chat不受该开关影响。 /api/runtime/status.toolRuntime增加codeRunsEnabled。scripts/check-agent-code-run-entry.mjs增加后端开关一致性检查。.env.example增加后端灰度开关说明。
验证:
node --check agent-run-routes.mjs tkmind-proxy.mjs scripts/check-agent-code-run-entry.mjs通过。node --test agent-run-routes.test.mjs agent-run-gateway.test.mjs通过,14 tests pass。- 相关回归
capabilities/policies/user-memory/tkmind-proxy/message-stream通过,61 tests pass。
生产部署:
- 生产代码备份:
/Users/john/Project/memind_backups/20260702-072452-pre-p49-code-run-server-gate。 - 已覆盖
/Users/john/Project/Memind/server.mjs和scripts/check-agent-code-run-entry.mjs。 - 已执行
launchctl kickstart -k gui/$(id -u)/cn.tkmind.memind-portal。 - 未修改 PG 数据,未修改或删除
/Users/john/Project/Memind/MindSpace。
生产验证:
https://mm.tkmind.cn/api/status返回ok。/api/runtime/status.toolRuntime.codeRunsEnabled=false。- 四个 goosed target healthy。
scripts/check-agent-code-run-entry.mjs返回ok=true。scripts/check-stream-runtime.mjs返回ok=true。scripts/runtime-worker-drain.mjs status返回ok=true,四个 worker drain=false。- 观察项: worker
activeStreams有非零计数且无 error;未强制重置,避免误伤生产中的真实 stream。
2026-07-02 P5.6 Worker activeStreams 收敛工具
目标:
- 处理 Redis 中
active_streams因异常断流或进程重启可能残留的问题。 - 默认只读,不自动清生产状态。
- 只有显式
--apply且 stream 明显超过 TTL 时才做收敛。
已完成:
scripts/runtime-worker-drain.mjs新增动作:reconcilereconcile --apply--stale-ms=<ms>
- 默认 TTL:
MEMIND_RUNTIME_STALE_STREAM_MS- 未设置时为
900000ms。
reconcile默认 dry-run,只报告:stalestaleAgeMslastStreamReconciledAtstreamReconcileCountreconciled
- 只有
activeStreams > 0且last_stream_started_at超过 TTL,并且没有更新的last_stream_ended_at时,才被视为 stale。 MEMIND_ENV_FILE已被runtime-worker-drain.mjs支持,便于在干净分支读取生产 env 做只读检查。- runtime RUNBOOK 模板增加:
node scripts/runtime-worker-drain.mjs reconcilenode scripts/runtime-worker-drain.mjs reconcile --apply
验证:
node --check scripts/runtime-worker-drain.mjs scripts/build-portal-runtime.mjs通过。- 使用生产 env dry-run:
MEMIND_ENV_FILE=/Users/john/Project/Memind/.env node scripts/runtime-worker-drain.mjs reconcile- 返回
ok=true。 - 四个 worker 均列出。
- 当前无 stale worker,未执行 apply。
生产部署:
- 生产脚本备份:
/Users/john/Project/memind_backups/20260702-072835-pre-p56-worker-reconcile。 - 已覆盖
/Users/john/Project/Memind/scripts/runtime-worker-drain.mjs和RUNBOOK.txt。 - 未重启 Portal。
- 未修改 PG 数据,未修改或删除
/Users/john/Project/Memind/MindSpace。
生产验证:
node scripts/runtime-worker-drain.mjs reconcile返回ok=true。dryRun=true。- 四个 worker 均列出。
- 当前无 stale worker,未执行 apply。
https://mm.tkmind.cn/api/status返回ok。
2026-07-02 P5.7 Worker Metrics 采样接入
目标:
- 将 goosed worker 的进程/容器指标写入 Redis runtime state。
- 让 Router score 不只依赖
active_streams,同时纳入 CPU / memory / fd pressure。 - 保持 PG 和 MindSpace 零写入。
已完成:
- 新增
scripts/runtime-worker-metrics.mjs。 - 支持:
status:采样但不写 Redis。sample:采样并写入 Redis。--dry-run:强制只读。
- 对 Docker worker
goosed-prod-1..4采集:cpuLoadmemoryPressurefdPressurefdCountcontainerPidscontainerHealthcontainerHostPidmetricsSampledAt
tkmind-proxyRouter score 增加:cpu_load * 2fd_pressure * 2
/api/runtime/status.router.workers[]增加 metrics 字段。- runtime artifact 和 RUNBOOK 增加:
node scripts/runtime-worker-metrics.mjs statusnode 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[]已显示:cpuLoadmemoryPressurefdPressurefdCountcontainerPidscontainerHealthmetricsSampledAt- 新
score
https://mm.tkmind.cn/api/status返回ok。scripts/check-stream-runtime.mjs返回ok=true。scripts/check-agent-code-run-entry.mjs返回ok=true。scripts/runtime-worker-drain.mjs reconcile返回ok=true,dryRun=true。
2026-07-02 P5.8 自动采样与 SLO 只读报告
目标:
- 将 P5.7 的手动 worker metrics 采样改为 LaunchAgent 定时采样。
- 增加一份生产 SLO 快照脚本,作为每次灰度/发布前后的统一只读检查。
- PG 和 MindSpace 允许只读统计,不删除、不修改用户数据。
已完成:
- 新增
scripts/install-runtime-metrics-agent.sh。- 安装 LaunchAgent:
cn.tkmind.memind-runtime-metrics。 - 默认每 60 秒执行
node scripts/runtime-worker-metrics.mjs sample。 - 日志:
~/Library/Logs/memind-runtime-metrics.log。
- 安装 LaunchAgent:
- 新增
scripts/runtime-slo-report.mjs。- 读取
/api/runtime/status。 - 只读统计 PG 表行数。
- 只读统计
/Users/john/Project/Memind/MindSpace和data/mindspace文件/目录/字节数。 - 读取 Redis runtime key 摘要。
- 检查 worker target health、container health、metrics freshness、stream errors、chat/code tool gate。
- 输出
writes.database=false、writes.mindSpace=false、writes.redis=false。
- 读取
- runtime artifact 和 RUNBOOK 增加:
bash scripts/install-runtime-metrics-agent.shnode 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
- label:
- LaunchAgent 执行状态:
launchctl print gui/$(id -u)/cn.tkmind.memind-runtime-metrics显示最近运行退出码为0。- 日志显示
runtime-worker-metrics.mjs sample成功。
- 生产 SLO 只读报告:
ok=truefailures=[]- 四个 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=270214686writes.database=falsewrites.mindSpace=falsewrites.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_msworker:{id}:last_first_token_msworker:{id}:last_first_token_atworker:{id}:first_token_count
- EWMA 计算:
next = previous * 0.8 + sample * 0.2,首次样本直接使用 sample。
/api/runtime/status.router.workers[]增加:ewmaFirstTokenMslastFirstTokenMslastFirstTokenAtfirstTokenCount
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曾短暂返回 nginx502;Portal 随后正常监听:8081,公网https://mm.tkmind.cn/api/status返回ok。
生产验证:
/api/runtime/status返回ok=true。- 已观测到真实首 token 样本:
goosed-4.ewmaFirstTokenMs=125goosed-4.lastFirstTokenMs=125goosed-4.firstTokenCount=1
scripts/runtime-slo-report.mjs返回:ok=truefailures=[]writes.database=falsewrites.mindSpace=falsewrites.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增加本机队列调度器:maxConcurrentRunsrunTimeoutMsinFlightpendingDispatchesstatusCounts
- 默认配置:
MEMIND_AGENT_RUN_QUEUE_CONCURRENCY未设置时为1MEMIND_AGENT_RUN_TIMEOUT_MS未设置时为900000ms
- 执行超时时:
- run 标记为
failed - 写入
timeoutevent - 不重试超时任务
- run 标记为
/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曾短暂返回 nginx502;Portal 随后正常监听:8081,公网https://mm.tkmind.cn/api/status返回ok。
生产验证:
/api/runtime/status.toolRuntime.queue返回:maxConcurrentRuns=1runTimeoutMs=900000inFlight=0pendingDispatches=0statusCounts={}
/api/runtime/status.toolRuntime.codeRunsEnabled=false。scripts/runtime-slo-report.mjs返回:ok=truefailures=[]writes.database=falsewrites.mindSpace=falsewrites.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/retryablerun 并走同一队列控制。- 队列满载时不预取 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_AUTODISPATCHMEMIND_AGENT_RUN_WORKER_POLL_MSMEMIND_AGENT_RUN_WORKER_BATCH_SIZE
- RUNBOOK 增加:
node scripts/agent-run-worker.mjs --statusnode 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曾短暂返回 nginx502;Portal 随后正常监听:8081,公网 runtime status 返回ok=true。
生产验证:
/api/runtime/status.toolRuntime.queue.autoDispatch=true。/api/runtime/status.toolRuntime.codeRunsEnabled=false。node scripts/agent-run-worker.mjs --status返回ok=true,且 worker 自身autoDispatch=false。scripts/runtime-slo-report.mjs返回:ok=truefailures=[]writes.database=falsewrites.mindSpace=falsewrites.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.countfirstToken5m.p50MsfirstToken5m.p95MsfirstToken1h.countfirstToken1h.p50MsfirstToken1h.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曾短暂返回 nginx502;Portal 随后正常监听:8081,公网https://mm.tkmind.cn/api/status返回ok。
生产验证:
/api/runtime/status返回ok=true。- 已观测到真实窗口样本:
goosed-4.ewmaFirstTokenMs=101goosed-4.firstToken5m.count=1goosed-4.firstToken5m.p50Ms=188goosed-4.firstToken5m.p95Ms=188goosed-4.firstToken1h.count=1goosed-4.firstToken1h.p50Ms=188goosed-4.firstToken1h.p95Ms=188
scripts/runtime-slo-report.mjs返回:ok=truefailures=[]writes.database=falsewrites.mindSpace=falsewrites.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=truefailures=[]reportFiles.json=/Users/john/Project/Memind/reports/runtime-slo/2026-07-02T00-08-26-316Z.jsonreportFiles.markdown=/Users/john/Project/Memind/reports/runtime-slo/2026-07-02T00-08-26-316Z.mdwrites.database=falsewrites.mindSpace=falsewrites.redis=falsewrites.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
- label:
.env.example增加:MEMIND_RUNTIME_REPORT_DIRMEMIND_RUNTIME_SLO_HOURMEMIND_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 = 0runs = 1Hour = 23Minute = 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=falsewrites.mindSpace=falsewrites.redis=falsewrites.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/runsqueued 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=falsetoolRuntime.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=truemetadata.agentVisible=truecreated=<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
- run id:
- 失败的早期 canary 保留为审计记录:
- run id:
9f0c713a-0aff-4e67-bf86-0ea2952e9326 - final status:
failed - 原因: 手工 payload 缺字段
- run id:
恢复:
- 已移除生产
.env中的MEMIND_AGENT_RUN_AUTODISPATCH=0。 - 已重启 Portal。
- runtime status 确认:
toolRuntime.queue.autoDispatch=truetoolRuntime.queue.inFlight=0toolRuntime.queue.pendingDispatches=0toolRuntime.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=truetoolRuntime.chatInjectsCodeTools=falsetoolRuntime.queue.autoDispatch=truetoolRuntime.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
- user id:
- run events 已记录:
queuedrunningsession_startedsucceeded
恢复:
- 已移除生产
.env中的MEMIND_AGENT_CODE_RUNS_ENABLED=1。 - 已重启 Portal。
- runtime status 确认:
toolRuntime.codeRunsEnabled=falsetoolRuntime.chatInjectsCodeTools=falsetoolRuntime.queue.autoDispatch=truetoolRuntime.queue.inFlight=0toolRuntime.queue.pendingDispatches=0toolRuntime.queue.statusCounts={}
mm.tkmind.cnruntime 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.jsonok=truewrites.database=falsewrites.mindSpace=falsewrites.redis=falsewrites.report=truewrites.reportPrune=truereportPrune.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_IDSagentCodeRunsEnabledForUser(userId)- 普通 H5 chat 和页面编辑子聊天调用
resolveAgentRunOptions()时传入当前 user id
- 后端新增:
MEMIND_AGENT_CODE_RUNS_USER_IDSPOST /agent/runs在tool_mode=code时校验用户白名单- 白名单为空时保持原有全局开关语义
验证:
node --test agent-run-routes.test.mjs agent-run-gateway.test.mjs:- 21 tests pass
npm run build:- 通过
scripts/check-agent-code-run-entry.mjs:ok=trueMEMIND_AGENT_CODE_RUNS_ENABLED未设置VITE_AGENT_CODE_RUNS_ENABLED未设置MEMIND_AGENT_CODE_RUNS_USER_IDS未设置VITE_AGENT_CODE_RUNS_USER_IDS未设置runtimePolicy.codeRunsEnabled=falseruntimePolicy.chatInjectsCodeTools=false
生产发布:
- 已发布默认关闭 runtime:
- release id:
20260702-085001-a0af049 - git head:
a0af049fa7d5a5a9cf88a09449ab4d3bd43f5cb3
- release id:
- 发布脚本修复:
a0af049 fix: pass mindspace link bypass to prod releaseALLOW_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:oktoolRuntime.codeRunsEnabled=falsetoolRuntime.chatInjectsCodeTools=falsetoolRuntime.queue.autoDispatch=truetoolRuntime.queue.inFlight=0toolRuntime.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=1VITE_AGENT_CODE_RUNS_USER_IDS=<john user id>MEMIND_AGENT_CODE_RUNS_ENABLED=1MEMIND_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=1MEMIND_AGENT_CODE_RUNS_USER_IDS=a6fb1e97-2b0f-447b-b138-4561d8e5c53e
- 前端 canary bundle 构建环境:
VITE_AGENT_CODE_RUNS_ENABLED=1VITE_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_taskh5_chat_code_task
- runtime status:
toolRuntime.codeRunsEnabled=truetoolRuntime.chatInjectsCodeTools=falsetoolRuntime.queue.inFlight=0toolRuntime.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 id:
- run events:
queuedrunningsession_startedsucceeded
恢复默认关闭:
- 已移除生产
.env中:MEMIND_AGENT_CODE_RUNS_ENABLEDMEMIND_AGENT_CODE_RUNS_USER_IDS
- 已发布默认关闭 bundle:
- release id:
20260702-085706-a0af049
- release id:
- 恢复发布备份:
/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:oktoolRuntime.codeRunsEnabled=falsetoolRuntime.chatInjectsCodeTools=falsetoolRuntime.queue.autoDispatch=truetoolRuntime.queue.inFlight=0toolRuntime.queue.pendingDispatches=0.env无MEMIND_AGENT_CODE_RUNS_*- frontend bundle 不再包含 john code-run whitelist
2026-07-02 P5.14 Worker Self-heartbeat Sidecar
目标:
- 将 worker freshness 从单纯外部 60 秒 metrics 采样,升级为常驻 sidecar 主动 heartbeat。
- Router 和 SLO 能直接看到
heartbeatSource=sidecar、heartbeatFresh、heartbeatOk、heartbeatLatencyMs。 - 不修改 PG,不修改 MindSpace。
实现:
- 源码分支新增提交:
e24e6ca feat: add runtime worker heartbeat sidecar
- 新增脚本:
scripts/runtime-worker-heartbeat.mjsscripts/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}:heartbeatworker:{id}:heartbeat_atworker:{id}:heartbeat_sourceworker:{id}:heartbeat_okworker:{id}:heartbeat_status_codeworker:{id}:heartbeat_latency_msworker:{id}:heartbeat_error
- Router
/api/runtime/status已展示 heartbeat 字段。 - SLO 报告已检查:
heartbeatFreshheartbeatOk
生产发布:
- 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=62goosed-2:ok=true,statusCode=200,latencyMs=46goosed-3:ok=true,statusCode=200,latencyMs=46goosed-4:ok=true,statusCode=200,latencyMs=53
/api/runtime/status:- all workers
heartbeatSource=sidecar - all workers
heartbeatOk=true - all workers
heartbeatStatusCode=200
- all workers
scripts/runtime-slo-report.mjs:ok=truefailures=[]- all workers
heartbeatFresh=true - all workers
metricsFresh=true
边界:
- 未删除 PG 数据。
- 未修改或删除
/Users/john/Project/Memind/MindSpace用户数据。 - 原 60 秒 metrics agent 保留,用于 CPU/memory/fd 指标。
- 新 sidecar 只负责更高频 freshness 和
/status可用性。
2026-07-02 P6.3 Tool Gateway 协议化
目标:
- 将 Aider/OpenHands 从 goosed session extension 继续外移为独立 Tool Gateway 执行协议。
- 复用现有
h5_agent_runs和h5_agent_run_events,不新增生产 schema migration。 - 默认关闭,只有
MEMIND_TOOL_GATEWAY_ENABLED=1且toolMode=code时才走外部执行器。 - 不删除、不修改 PG 用户数据,不删除、不修改
/Users/john/Project/Memind/MindSpace用户文件。
当前落地:
- 新增
tool-gateway.mjs:- protocol:
agent-run-v1 - executors:
aider,openhands - default executor:
aider repo_refactor,multi_file,complex_repo默认走openhandsMEMIND_TOOL_GATEWAY_DRY_RUN=1支持只生成启动计划不执行进程
- protocol:
agent-run-gateway.mjs:chat和默认关闭状态继续走 Goose 原链路code+ Tool Gateway enabled 时写入事件:tool_gateway_dispatchtool_gateway_result
- 外部工具任务完成后标记 run succeeded,不再强制创建 goosed session
server.mjs和scripts/agent-run-worker.mjs:- 已接入同一个 Tool Gateway 对象
- 独立 worker 后续可接管 code run 队列
scripts/check-tool-runtime.mjs:- 已输出 Tool Gateway enable/dry-run/protocol/default executor 状态
- 测试:
node --test agent-run-gateway.test.mjs tool-gateway.test.mjs- 结果:
15 passed
- 源码提交:
21e03d8 feat: add tool gateway protocol
默认环境:
MEMIND_TOOL_GATEWAY_ENABLED未设置时为关闭。MEMIND_TOOL_GATEWAY_DRY_RUN未设置时为关闭。- 生产发布后若不显式设置 env,不会自动执行 Aider/OpenHands 外部进程。
生产发布:
- release id:
20260702-092457-21e03d8 - git head:
21e03d86b5e8ec28a0bd511c484d0618476528a6 - 发布前:
- 全目录备份已完成
- 持久目录备份已完成
- 未删除 PG 数据
- 未删除或改写 MindSpace 用户数据
- 发布验证:
https://mm.tkmind.cn/api/status:okhttps://mm.tkmind.cn/api/runtime/status:ok=truetoolRuntime.codeRunsEnabled=falsetoolRuntime.chatInjectsCodeTools=falsetoolRuntime.queue.toolGateway.enabled=falsetoolRuntime.queue.toolGateway.protocol=agent-run-v1- all workers
heartbeatSource=sidecar - all workers
heartbeatOk=true scripts/runtime-slo-report.mjs:ok=true,failures=[].env无MEMIND_TOOL_GATEWAY_*.env无MEMIND_AGENT_CODE_RUNS_*
Dry-run canary:
- canary window:
- temporarily enabled
MEMIND_AGENT_CODE_RUNS_ENABLED=1 - temporarily enabled
MEMIND_AGENT_CODE_RUNS_USER_IDS=a6fb1e97-2b0f-447b-b138-4561d8e5c53e - temporarily enabled
MEMIND_TOOL_GATEWAY_ENABLED=1 - temporarily enabled
MEMIND_TOOL_GATEWAY_DRY_RUN=1
- temporarily enabled
.envbackup:/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
- run id:
- event proof:
queuedrunningtool_gateway_dispatchprotocol=agent-run-v1taskType=small_patch
tool_gateway_resultdryRun=trueexecutor=aiderexitCode=null
succeeded
- cleanup:
- restored
.envfrom backup - revoked temporary login session
- restarted Portal
- final
toolRuntime.codeRunsEnabled=false - final
toolRuntime.queue.toolGateway.enabled=false - final SLO:
ok=true,failures=[]
- restored
边界:
- 本阶段未启用生产外部执行器。
- 本阶段未修改 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_dispatchtool_gateway_resultdryRun=falseexecutor=aiderexitCode=0
- 实际问题:
- Aider 进程退出码为 0,但
.aider.chat.history.md显示 LiteLLM 报错:LLM Provider NOT provided- model was
deepseek-reasoner
- 未生成目标
P64_CANARY.md,只生成.aider*和.gitignore。
- Aider 进程退出码为 0,但
- 结论:
- Tool Gateway 进程协议跑通。
- Aider 模型名映射不正确,真实产物 canary 未通过。
修复:
- 源码提交:
0cfd2d3 fix: map deepseek models for aider
- 修复内容:
- Aider executor 对
custom_deepseek自动使用deepseek/<model>。 deepseek-reasoner映射为deepseek/deepseek-reasoner。- 测试改为不依赖本机是否安装
aider。
- Aider executor 对
- 测试:
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.cntunnel 检查处返回 1,但 Portal runtime 已发布并通过健康检查。 - 已手动清理占用 8081 的孤儿 Portal node 进程,让 LaunchAgent 重新接管。
- 最终监听:
- LaunchAgent
cn.tkmind.memind-portal - pid:
86462 - port:
8081
- LaunchAgent
- release id:
第二次真实 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:
queuedrunningtool_gateway_dispatchprotocol=agent-run-v1- workingDir is the canary repo
tool_gateway_resultdryRun=falseexecutor=aiderexitCode=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.
- generated file:
恢复/最终状态:
- 生产
.env无MEMIND_TOOL_GATEWAY_*。 - 生产
.env无MEMIND_AGENT_CODE_RUNS_*。 https://mm.tkmind.cn/api/runtime/status:ok=truetoolRuntime.codeRunsEnabled=falsetoolRuntime.queue.toolGateway.enabled=false- all workers
heartbeatSource=sidecar - all workers
heartbeatOk=true
scripts/runtime-slo-report.mjs:ok=truefailures=[]
边界:
- 未做 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
- version:
- launch plan:
- command:
openhands - args:
--headless--json--override-with-envs--task <instruction>
- env:
LLM_MODEL=deepseek-reasonerLLM_BASE_URL=https://api.deepseek.com/v1LLM_API_KEY=<from provider binding>
- command:
- 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
- about
- status:
succeeded- attempts:
1 agent_session_id=null
event proof:
queuedrunningtool_gateway_dispatchprotocol=agent-run-v1taskType=repo_refactor- workingDir is the canary repo
tool_gateway_resultdryRun=falseexecutor=openhandsexitCode=0
succeeded
file proof:
- generated file:
/Users/john/Project/memind_canary/tool-gateway-p65-openhands-20260702-095402/P65_OPENHANDS_CANARY.md
- content:
P6.5 OpenHands Tool Gateway canary succeeded.
恢复/最终状态:
- 生产
.env无MEMIND_TOOL_GATEWAY_*。 - 生产
.env无MEMIND_AGENT_CODE_RUNS_*。 https://mm.tkmind.cn/api/runtime/status:ok=truetoolRuntime.codeRunsEnabled=falsetoolRuntime.queue.toolGateway.enabled=false- all workers
heartbeatSource=sidecar - all workers
heartbeatOk=true
scripts/runtime-slo-report.mjs:ok=truefailures=[]
边界:
- 未做 PG schema migration。
- 仅新增 canary run/event 审计记录。
- 未删除 PG 数据。
- 未删除或改写生产 MindSpace 用户数据。
- 真实 OpenHands 文件写入只发生在
/Users/john/Project/memind_canary/...。
2026-07-02 P6.6 External Worker Code-run Canary
目标:
- 验证 code-mode run 可以由 Portal 以外的 external worker 精确接管。
- 确认 Portal
autoDispatch=false时不会抢跑 queued run。 - 使用专用 canary workspace,不触碰生产用户页面。
- 执行后恢复生产默认关闭。
源码:
- commit:
a2d69a0 feat: target agent run worker dispatch
- 关键变更:
scripts/agent-run-worker.mjs支持--run-id <id>精确 dispatch 单条 run。- 支持
MEMIND_AGENT_RUN_WORKDIR_OVERRIDE和MEMIND_AGENT_RUN_WORKDIR_USER_ID,用于把 canary 文件写入专用目录。 - worker 输出包含
runId,便于审计。
生产 release:
- release id:
20260702-100212-a2d69a0
- manifest:
git_head=a2d69a0c36f4300bc36a79abfe01300f2237fd07branch=memind-streaming-runtime-20260702
执行:
- 生产
.env短窗口启用:MEMIND_AGENT_RUN_AUTODISPATCH=0MEMIND_AGENT_CODE_RUNS_ENABLED=1MEMIND_AGENT_CODE_RUNS_USER_IDS=a6fb1e97-2b0f-447b-b138-4561d8e5c53e
- 外部 worker 单次执行时启用:
MEMIND_TOOL_GATEWAY_ENABLED=1MEMIND_TOOL_GATEWAY_DRY_RUN=0MEMIND_AGENT_RUN_WORKDIR_OVERRIDE=/Users/john/Project/memind_canary/tool-gateway-p66-worker-20260702-100500MEMIND_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
- run id:
worker proof:
- command path:
/Users/john/Project/Memind/scripts/agent-run-worker.mjs --run-id 94dcca6f-cf32-4fa1-b602-8e6a5bff1e3c
- output:
ok=truemode=dispatchdispatched=1runId=94dcca6f-cf32-4fa1-b602-8e6a5bff1e3c- queue
autoDispatch=false - Tool Gateway
enabled=true dryRun=false
event proof:
queuedrunningtool_gateway_dispatch- canary workingDir
tool_gateway_resultdryRun=falseexecutor=aiderexitCode=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=truetoolRuntime.codeRunsEnabled=falsetoolRuntime.queue.autoDispatch=truetoolRuntime.queue.toolGateway.enabled=false- all workers
heartbeatSource=sidecar - all workers
heartbeatOk=true
scripts/runtime-slo-report.mjs:ok=truefailures=[]
边界:
- 未做 PG schema migration。
- 仅新增 canary run/event 审计记录。
- 未删除 PG 数据。
- 未删除或改写生产 MindSpace 用户数据。
- 真实文件写入只发生在
/Users/john/Project/memind_canary/...。
2026-07-02 P6.7 Tool Gateway Production Guardrails
目标:
- 解决 executor
exitCode=0但目标产物未生成时仍被标记succeeded的风险。 - 为 canary 和灰度 code run 增加显式成功标准。
- 保持生产默认关闭,不改变普通聊天链路。
源码:
- commit:
6340b57 feat: validate tool gateway artifacts
- branch:
memind-streaming-runtime-20260702
关键变更:
agent-run-gateway.mjs- 支持读取
metadata.memindRun.validation或metadata.toolGatewayValidation。 - 支持校验:
expectedFileexpectedFilescontains/expectedContent/contentIncludes
- 校验路径限制在 Tool Gateway working directory 内。
tool_gateway_resultevent 增加stdoutTail/stderrTail。- 校验通过写入
tool_gateway_validationevent。 - 校验失败写入
tool_gateway_validation_failedevent。 - 校验失败
retryable=false,避免确定性产物缺失被重复重试。
- 支持读取
验证:
node --test agent-run-gateway.test.mjs tool-gateway.test.mjs17 passed
node --check agent-run-gateway.mjs- passed
npm run build:portal-runtime- passed
生产 release:
- release id:
20260702-101319-6340b57
- manifest:
git_head=6340b57fda61122a61b0f767eee118be81b151e6git_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=truetoolRuntime.codeRunsEnabled=falsetoolRuntime.queue.autoDispatch=truetoolRuntime.queue.toolGateway.enabled=false
scripts/runtime-slo-report.mjs:ok=truefailures=[]writes.database=falsewrites.mindSpace=falsewrites.redis=falsewrites.report=falsewrites.reportPrune=false
边界:
- 未做 PG schema migration。
- 未删除 PG 数据。
- 未删除或改写生产 MindSpace 用户数据。
- 新能力默认关闭,只影响显式 Tool Gateway code run 且声明 validation 的任务。
2026-07-02 P6.8 Tool Gateway Controlled Rollout 基础
目标:
- 为 external agent-run-worker 准备标准 LaunchAgent 运维入口。
- 默认不启动、不接管生产队列。
- 保持
codeRunsEnabled=false、toolGateway.enabled=false。
源码:
- commit:
3654639 feat: add agent run worker launchagent installer
关键变更:
- 新增:
scripts/install-agent-run-worker-agent.sh
- 更新:
scripts/build-portal-runtime.mjs- runtime artifact 会带上 installer,并在 RUNBOOK 中记录入口。
installer 默认行为:
- 写入:
/Users/john/Library/LaunchAgents/cn.tkmind.memind-agent-run-worker.plist
- 默认:
RunAtLoad=falseKeepAlive=truelaunchctl disable- 不
kickstart
- 默认环境:
MEMIND_AGENT_RUN_AUTODISPATCH=0MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=1MEMIND_TOOL_GATEWAY_ENABLED=0MEMIND_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.mjs17 passed
npm run build:portal-runtime- passed
生产 release:
- release id:
20260702-101834-3654639
- manifest:
git_head=3654639bb314437a7463446f47f40ab1c15aeb92git_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-disabledprint-disabled: disabledlaunchctl print:state = not running- no
agent-run-worker.mjsprocess
发布后状态:
https://mm.tkmind.cn/api/runtime/status:ok=truetoolRuntime.codeRunsEnabled=falsetoolRuntime.queue.autoDispatch=truetoolRuntime.queue.toolGateway.enabled=false
scripts/runtime-slo-report.mjs:ok=truefailures=[]- 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
- installer 支持
短窗口配置:
.envbackup:/Users/john/Project/memind_backups/20260702-1029-p69-validated-worker-canary/.env.before
- Portal:
MEMIND_AGENT_RUN_AUTODISPATCH=0MEMIND_AGENT_CODE_RUNS_ENABLED=1MEMIND_AGENT_CODE_RUNS_USER_IDS=a6fb1e97-2b0f-447b-b138-4561d8e5c53eMEMIND_TOOL_GATEWAY_ENABLED=0
- external worker LaunchAgent:
MEMIND_TOOL_GATEWAY_ENABLED=1MEMIND_TOOL_GATEWAY_DRY_RUN=0MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=1MEMIND_AGENT_RUN_WORKDIR_OVERRIDE=/Users/john/Project/memind_canary/tool-gateway-p69-validated-worker-20260702-102942MEMIND_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:
queuedrunningtool_gateway_dispatchtool_gateway_resultexecutor=aiderdryRun=falseexitCode=0
tool_gateway_validation_failedreason=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:
queuedrunningtool_gateway_dispatchtool_gateway_resultexecutor=aiderdryRun=falseexitCode=0
tool_gateway_validationexpectedFiles[0].path=P69_VALIDATED_WORKER_CANARY_PASS.mdcontains=truesizeBytes=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-disabledstate=not running- no
agent-run-worker.mjsprocess
https://mm.tkmind.cn/api/runtime/status:ok=truetoolRuntime.codeRunsEnabled=falsetoolRuntime.queue.autoDispatch=truetoolRuntime.queue.toolGateway.enabled=false
scripts/runtime-slo-report.mjs:ok=truefailures=[]- 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 printstate- 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.mjsok=true
node --test agent-run-gateway.test.mjs tool-gateway.test.mjs17 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=truetoolRuntime.codeRunsEnabled=falsetoolRuntime.queue.autoDispatch=truetoolRuntime.queue.toolGateway.enabled=false
node scripts/check-agent-run-worker.mjs:ok=trueexpected=disabledinstalled=truedisabled=truerunning=falsequeue.statusCounts={}oldestPendingAgeMs=0
scripts/runtime-slo-report.mjs:ok=truefailures=[]- writes all false
边界:
- 未做 PG schema migration。
- 未删除 PG 数据。
- 未删除或改写生产 MindSpace 用户数据。
- 新脚本只读。
2026-07-02 P6.11 Single-user Rollout Policy
目标:
- 为后续单用户长期灰度建立策略门禁。
- 默认关闭,不改变普通用户路径。
- 在 runtime/status 中可观测当前 code-run policy。
源码:
- commit:
212e163 feat: add code run rollout policy gates
新增策略:
MEMIND_AGENT_CODE_RUNS_ENABLED- code run 总开关。
MEMIND_AGENT_CODE_RUNS_USER_IDS- 用户级 allowlist。
MEMIND_AGENT_CODE_RUN_TASK_TYPES- task type allowlist;为空表示不限制 task type。
MEMIND_AGENT_CODE_RUNS_REQUIRE_VALIDATION- 开启后,code run 必须声明
metadata.memindRun.validation或metadata.toolGatewayValidation。
- 开启后,code run 必须声明
入口行为:
- 非 allowlist 用户:
403 当前用户未开启代码任务灰度
- 非 allowlist task type:
403 当前代码任务类型未开启灰度
- 缺少 required validation:
400 代码任务必须声明产物校验规则
runtime/status:
- 新增:
toolRuntime.codeRunPolicy.enabledtoolRuntime.codeRunPolicy.userAllowlisttoolRuntime.codeRunPolicy.taskTypeAllowlisttoolRuntime.codeRunPolicy.requireValidation
验证:
node --test agent-run-routes.test.mjs agent-run-gateway.test.mjs tool-gateway.test.mjs31 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.cntunnel 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=truetoolRuntime.codeRunPolicy.enabled=falsetoolRuntime.codeRunPolicy.userAllowlist=[]toolRuntime.codeRunPolicy.taskTypeAllowlist=[]toolRuntime.codeRunPolicy.requireValidation=falsetoolRuntime.codeRunsEnabled=falsetoolRuntime.queue.autoDispatch=truetoolRuntime.queue.toolGateway.enabled=false
node scripts/check-agent-run-worker.mjs:ok=true- worker disabled/not running
scripts/runtime-slo-report.mjs:ok=truefailures=[]- 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=1MEMIND_AGENT_CODE_RUNS_USER_IDS=,空值表示不限制用户。MEMIND_AGENT_CODE_RUN_TASK_TYPES=,空值表示不限制任务类型。MEMIND_AGENT_CODE_RUNS_REQUIRE_VALIDATION=0,H5 默认 code-run 不强制声明 expected file validation;带 validation 的任务仍走 Tool Gateway 校验。MEMIND_AGENT_RUN_AUTODISPATCH=0,Portal 不内联派发 code-run。MEMIND_TOOL_GATEWAY_ENABLED=0,Portal 侧 Tool Gateway 保持关闭。
- H5 前端重建:
VITE_AGENT_CODE_RUNS_ENABLED=1VITE_AGENT_CODE_RUNS_AUTODETECT=1- 当前公网 bundle:
/assets/index-CI7mXNQo.js
- external worker:
cn.tkmind.memind-agent-run-workerenabled/running。MEMIND_TOOL_GATEWAY_ENABLED=1MEMIND_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
- 事件链:
queuedrunningtool_gateway_dispatchtool_gateway_resultsucceeded
- executor:
aiderdryRun=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=truefailures=[]
边界:
- 未做 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.mjsscripts/install-agent-run-guard-agent.shdocs/agent-run-worker-rollout-runbook.mdscripts/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
- label
- 安装命令:
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=0MEMIND_AGENT_RUN_AUTODISPATCH=1
launchctl bootout/disable cn.tkmind.memind-agent-run-workerlaunchctl kickstart -k cn.tkmind.memind-portal
验证:
node scripts/agent-run-guard.mjs --dry-run:ok=trueshouldPause=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
- receipt path:
- 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=1MEMIND_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=truecodeRunPolicy.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
- user id
- 产物验证:
- 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。
- receipt:
- SLO:
ok=truefailures=[]- 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=2MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=2--limit 2KeepAlive=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, attempts1
- user
- run
64717bcc-4065-4947-a0d3-a706f3b62640- user
07f29934-8cf8-408a-af92-3c4276e579c8 - request
p617-concurrency-20260702040600-2 - started
1782965163135 - completed
1782965186566 - status
succeeded, attempts1
- user
- run
- 两条 run 都写入:
tool_gateway_dispatchtool_gateway_resulttool_gateway_validationsucceeded
- MindSpace 验证:
- 两个 receipt JSON 均包含 requestId。
- 两个
public/p617-concurrency-*.html均包含目标 marker。
- worker log:
limit=2maxConcurrentRuns=2dispatched=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=2MEMIND_AGENT_RUN_WORKER_BATCH_SIZE=2
- worker LaunchAgent:
MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=2MEMIND_AGENT_RUN_WORKER_BATCH_SIZE=2--limit 2KeepAlive=true
验证:
https://mm.tkmind.cn/api/status返回ok。/api/runtime/status:codeRunPolicy.requireValidation=truetoolRuntime.queue.maxConcurrentRuns=2toolRuntime.queue.statusCounts={}
- worker check:
- running
true - process args include
--limit 2
- running
- guard dry-run:
ok=trueshouldPause=false- queue empty
- SLO:
ok=truefailures=[]
观察:
- Portal 在一次重启窗口中出现
read EADDRNOTAVAIL并由 LaunchAgent 自动拉起,随后/api/status和/api/runtime/status恢复ok。 - 后续建议优先做 running/stuck recovery 与 DB/session auth 异常不致命化,避免瞬时 DB/socket 错误扩大为进程退出。
2026-07-02 P8.1 Queue Lease / Stuck Run Recovery
目标:
- 防止 worker/进程在 run 进入
running后异常退出,导致该 run 永久卡住。 - 在不新增 schema、不修改真实用户数据的前提下,先用
started_at + runTimeoutMs作为最小 lease。
新增能力:
agent-run-gateway.mjs:- 新增
recoverStaleRunningRuns()。 dispatchQueuedRuns()每轮 dispatch 前自动回收超过runTimeoutMs的runningrows。- stale row 会标记为
failed,写入stale_recoveredevent。
- 新增
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 摘要增加:
oldestRunningStartedAtoldestRunningAgeMs
- queue 摘要增加:
- 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_recoveredevent。
生产部署:
- 备份:
/Users/john/Project/memind_backups/20260702-120900-p81-stale-recovery
- 已部署:
- bundled
server.mjs - bundled
scripts/agent-run-worker.mjs scripts/check-agent-run-worker.mjsRUNBOOK.txt
- bundled
- 已重启:
cn.tkmind.memind-portalcn.tkmind.memind-agent-run-worker
生产验证:
/api/status返回ok。/api/runtime/status.toolRuntime.queue:maxConcurrentRuns=2statusCounts={}oldestRunningStartedAt=nulloldestRunningAgeMs=0
- stale dry-run:
considered=0recovered=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
- run id
- post-check:
- worker running with
--limit 2 - queue empty
- guard
shouldPause=false - SLO
ok=true,failures=[]
- worker running with
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
- bundled
- 已重启:
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=truerequireValidation=truemaxConcurrentRuns=2oldestRunningStartedAt=nulloldestRunningAgeMs=0
- worker LaunchAgent running:
--limit 2MEMIND_TOOL_GATEWAY_ENABLED=1MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=2
- guard dry-run:
ok=trueshouldPause=false
- SLO:
ok=truefailures=[]
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-relativepublic/*.html。 - 新增
buildAgentRunTaskValidation()。 resolveAgentRunOptions()现在会合并:- 默认
.memind/agent-runs/<requestId>.jsonreceipt。 - 推断出的
public/*.htmlexpectedFiles。 page_edit_code_task的.memind/agent-runs/<requestId>-page-edit.json,内容必须包含pageId。
- 默认
- 新增
src/hooks/useTKMindChat.ts:- 普通 H5 code-run 调用
resolveAgentRunOptions()时传入 MindSpace context。
- 普通 H5 code-run 调用
src/hooks/usePageEditSubChat.ts:- 页面编辑子聊天传入 MindSpace context、
pageId和pageTitle。
- 页面编辑子聊天传入 MindSpace context、
测试:
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=1VITE_AGENT_CODE_RUNS_AUTODETECT=1VITE_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=truecodeRunsEnabled=truerequireValidation=truemaxConcurrentRuns=2statusCounts={}
- guard dry-run:
ok=trueshouldPause=falsefailedRecentCount=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.jsonpublic/p619-task-validation-20260702042911.html.memind/agent-runs/p619-task-validation-20260702042911-page-edit.json
- user
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_heartbeatevent,后续按 heartbeat interval 定时写入。 - run 进入 terminal 或失败后停止 heartbeat timer。
recoverStaleRunningRuns()使用COALESCE(latest worker_heartbeat, started_at)判断 stale。- recovery UPDATE 也复核最新 heartbeat,避免 select/update 之间误杀刚刷新 heartbeat 的 run。
getQueueStatus()增加:heartbeatMsoldestRunningHeartbeatAtoldestRunningHeartbeatAgeMsrunningWithoutHeartbeatCountlatestRunningRun
- 新增
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。
- worker dispatch 会写
生产部署:
- 备份:
/Users/john/Project/memind_backups/20260702-123510-p83-worker-heartbeat
- 已部署:
- bundled
server.mjs - bundled
scripts/agent-run-worker.mjs scripts/agent-run-guard.mjsscripts/check-agent-run-worker.mjs
- bundled
- 已重启:
cn.tkmind.memind-portalcn.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=2heartbeatMs=30000statusCounts={}oldestRunningHeartbeatAgeMs=0runningWithoutHeartbeatCount=0
check-agent-run-worker.mjs:ok=trueexpected=runningrunning=true
- guard dry-run:
ok=trueshouldPause=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=nullandheartbeatAgeMs - fresh-heartbeat synthetic run
97a2ac16-0234-483d-be4f-836ba95a91e7was not recovered and then deleted to avoid queue residue.
- no-heartbeat run
- real worker heartbeat canary:
- run
ad2894a7-dc02-49c6-a859-da6af95cb143 - request
p83-heartbeat-real-20260702043642 - status
succeeded - event chain includes
worker_heartbeatbeforetool_gateway_dispatch - heartbeat data includes
pid=31341,attempt=1,heartbeatMs=30000 tool_gateway_validationpassed for receipt andpublic/p83-heartbeat-real-20260702043642.html
- run
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 摘要:heartbeatMsmaxConcurrentRunsstatusCountsoldestRunningStartedAtoldestRunningAgeMsoldestRunningHeartbeatAtoldestRunningHeartbeatAgeMsrunningWithoutHeartbeatCountlatestRunningRun
- failure 条件新增:
agent_run_heartbeat_stale_<age>: running heartbeat age 超过阈值。agent_run_missing_heartbeat_<count>: running run 长时间缺失 heartbeat。
- Markdown 报告的 Tool Queue 段落展示:
heartbeatMsoldestRunningHeartbeatAgeMsrunningWithoutHeartbeatCount
.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=truefailures=[]toolQueueSlo.heartbeatMs=30000toolQueueSlo.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.mjsscripts/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-soakStartInterval=3600--write-report --prune --retention-days 7- report dir
/Users/john/Project/Memind/reports/runtime-slo-soak
生产验证:
- 生产只读 SLO:
ok=truefailures=[]toolQueueSlo.heartbeatMs=30000toolQueueSlo.statusCounts={}toolQueueSlo.oldestRunningHeartbeatAgeMs=0toolQueueSlo.runningWithoutHeartbeatCount=0
- 临时 report-dir 写入验证:
- JSON 和 Markdown 均生成成功。
- 两种格式均包含
heartbeatMs、oldestRunningHeartbeatAgeMs、runningWithoutHeartbeatCount。
- soak 快照验证:
- LaunchAgent 已加载。
- 首次 kickstart 快照已写入 JSON/Markdown。
- 最新快照
ok=true,failures=[]。 - JSON/Markdown 均包含 heartbeat summary。
回滚策略
- P0: 修改前保留
server.mjs备份;如启动失败,恢复备份并launchctl kickstartPortal。 - P1: 修改 nginx 前备份 conf;
nginx -t成功后再 reload。 - P2: Redis Router 默认可通过 env 开关退回当前
pickTarget()round-robin。 - P4: Tool Gateway 默认关闭,通过用户策略逐步放量。