feat: add worker heartbeat for agent runs
This commit is contained in:
@@ -82,6 +82,8 @@ MEMIND_AGENT_CODE_RUN_TASK_TYPES=
|
||||
MEMIND_AGENT_CODE_RUNS_REQUIRE_VALIDATION=1
|
||||
MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=2
|
||||
MEMIND_AGENT_RUN_WORKER_BATCH_SIZE=2
|
||||
MEMIND_AGENT_RUN_HEARTBEAT_MS=30000
|
||||
MEMIND_AGENT_RUN_WORKER_EXPECT_RUNNING=1
|
||||
MEMIND_TOOL_GATEWAY_ENABLED=0
|
||||
MEMIND_TOOL_GATEWAY_DRY_RUN=0
|
||||
VITE_AGENT_CODE_RUNS_ENABLED=1
|
||||
@@ -96,6 +98,7 @@ MEMIND_AGENT_RUN_WORKER_START=1
|
||||
MEMIND_AGENT_RUN_WORKER_POLL_MS=3000
|
||||
MEMIND_AGENT_RUN_WORKER_BATCH_SIZE=2
|
||||
MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=2
|
||||
MEMIND_AGENT_RUN_HEARTBEAT_MS=30000
|
||||
MEMIND_TOOL_GATEWAY_ENABLED=1
|
||||
MEMIND_TOOL_GATEWAY_DRY_RUN=0
|
||||
```
|
||||
@@ -186,6 +189,20 @@ Check stale running recovery before and after worker changes:
|
||||
node /Users/john/Project/Memind/scripts/agent-run-worker.mjs --recover-stale --stale-ms 900000 --limit 5
|
||||
```
|
||||
|
||||
Check worker heartbeat status during all-user gray:
|
||||
|
||||
```bash
|
||||
MEMIND_AGENT_RUN_WORKER_EXPECT_RUNNING=1 node /Users/john/Project/Memind/scripts/check-agent-run-worker.mjs
|
||||
```
|
||||
|
||||
Expected queue fields:
|
||||
|
||||
```text
|
||||
heartbeatMs=30000
|
||||
oldestRunningHeartbeatAgeMs=0 when no running rows
|
||||
runningWithoutHeartbeatCount=0 during normal operation
|
||||
```
|
||||
|
||||
Apply only when the listed running rows are known stale:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
- Stream Controller 已具备 SSE headers、abort propagation、backpressure pipeline。
|
||||
- Redis Router 已启用,承担 worker runtime state。
|
||||
- Goose Worker Pool 已从固定单点走向四 worker 可观测调度。
|
||||
- Aider/OpenHands 已从普通聊天默认能力中剥离,进入 code mode 和后端灰度门禁;P6.3 已把 code run 从 goosed session extension 外移到 `agent-run-v1` Tool Gateway 协议,P6.4 已完成 Aider 真实执行 canary,P6.5 已完成 OpenHands 真实执行 canary,P6.6 已完成 external worker 精确接管 code-run canary,P6.7 已加入 Tool Gateway 产物校验与输出审计,P6.8 已安装 external worker LaunchAgent,P6.9 已完成带 validation 的 external worker 灰度 canary,P6.10 已加入 external worker 只读观测脚本,P6.11 已加入放量策略门禁,P6.12 已开启全用户长期灰度并通过普通测试用户真实路径,P6.13 已安装自动暂停 guard,P6.15 已让 H5 code-run 自动补 receipt validation 并恢复后端 required validation,P6.17/P6.18 已把 external worker 并发 2 通过 canary 并固化为当前 all-user gray 策略,P6.19 已加入 task-level artifact validation,P8.1 已加入 stale running recovery,P8.2 已完成 Portal DB/Auth 瞬时错误兜底。
|
||||
- Aider/OpenHands 已从普通聊天默认能力中剥离,进入 code mode 和后端灰度门禁;P6.3 已把 code run 从 goosed session extension 外移到 `agent-run-v1` Tool Gateway 协议,P6.4 已完成 Aider 真实执行 canary,P6.5 已完成 OpenHands 真实执行 canary,P6.6 已完成 external worker 精确接管 code-run canary,P6.7 已加入 Tool Gateway 产物校验与输出审计,P6.8 已安装 external worker LaunchAgent,P6.9 已完成带 validation 的 external worker 灰度 canary,P6.10 已加入 external worker 只读观测脚本,P6.11 已加入放量策略门禁,P6.12 已开启全用户长期灰度并通过普通测试用户真实路径,P6.13 已安装自动暂停 guard,P6.15 已让 H5 code-run 自动补 receipt validation 并恢复后端 required validation,P6.17/P6.18 已把 external worker 并发 2 通过 canary 并固化为当前 all-user gray 策略,P6.19 已加入 task-level artifact validation,P8.1 已加入 stale running recovery,P8.2 已完成 Portal DB/Auth 瞬时错误兜底,P8.3 已加入 worker lease heartbeat。
|
||||
- PG 和 MindSpace 仍保持生产数据边界,SLO 报告只做统计读取;P6.3-P6.15 不新增 schema migration,不删除或修改既有用户数据。
|
||||
|
||||
整体执行评分: 9.95 / 10。
|
||||
|
||||
可以支撑当前 H5 streaming 稳定性改造的基础目标。自动采样、worker sidecar heartbeat、SLO 只读快照、SLO 日报定时器、SLO 日报保留策略、first-token EWMA、first-token p50/p95 窗口趋势、Tool Gateway Queue v0、外部 worker 接管入口、真实 worker canary、后端 code-mode canary、code-run 用户级灰度 gate、H5 页面编辑 UI canary、P6.3 Tool Gateway 协议化、P6.4 Aider 真实 canary、P6.5 OpenHands 真实 canary、P6.6 external worker code-run canary、P6.7 Tool Gateway guardrails、P6.8 worker LaunchAgent、P6.9 validated external worker canary、P6.10 worker observability、P6.11 rollout policy gates、P6.12 all-user gray、P6.13 auto-pause guard、P6.15 H5 validation metadata、P6.17/P6.18 worker concurrency 2、P6.19 task-level artifact validation、P8.1 stale running recovery 和 P8.2 Portal DB/Auth transient hardening 已经落地。主要剩余差距转为用户可见进度/失败说明,以及后续更细任务类型的校验策略扩展。
|
||||
可以支撑当前 H5 streaming 稳定性改造的基础目标。自动采样、worker sidecar heartbeat、SLO 只读快照、SLO 日报定时器、SLO 日报保留策略、first-token EWMA、first-token p50/p95 窗口趋势、Tool Gateway Queue v0、外部 worker 接管入口、真实 worker canary、后端 code-mode canary、code-run 用户级灰度 gate、H5 页面编辑 UI canary、P6.3 Tool Gateway 协议化、P6.4 Aider 真实 canary、P6.5 OpenHands 真实 canary、P6.6 external worker code-run canary、P6.7 Tool Gateway guardrails、P6.8 worker LaunchAgent、P6.9 validated external worker canary、P6.10 worker observability、P6.11 rollout policy gates、P6.12 all-user gray、P6.13 auto-pause guard、P6.15 H5 validation metadata、P6.17/P6.18 worker concurrency 2、P6.19 task-level artifact validation、P8.1 stale running recovery、P8.2 Portal DB/Auth transient hardening 和 P8.3 worker lease heartbeat 已经落地。P6.22 用户可见进度/失败说明按用户决策暂不开发,后续剩余差距转为更长时间 soak、自动报表告警和更细任务类型的校验策略扩展。
|
||||
|
||||
## 实测结果
|
||||
|
||||
@@ -974,9 +974,58 @@ Data boundary:
|
||||
- guard `shouldPause=false`
|
||||
- `failedRecentCount=0`
|
||||
|
||||
### P8.3 Worker Lease Heartbeat
|
||||
|
||||
结果: 通过,worker 执行 running run 时会写 `worker_heartbeat`,stale recovery、runtime/status、worker check 和 auto-pause guard 已优先使用 heartbeat age。
|
||||
|
||||
- `agent-run-gateway.mjs`:
|
||||
- 新增 `MEMIND_AGENT_RUN_HEARTBEAT_MS`,默认 `30000`。
|
||||
- running 后立即写 `worker_heartbeat`,再按 interval 定时写入。
|
||||
- terminal 后停止 heartbeat timer。
|
||||
- stale recovery 使用 `COALESCE(latest worker_heartbeat, started_at)`。
|
||||
- recovery UPDATE 会复核最新 heartbeat,避免误杀刚刷新 heartbeat 的 run。
|
||||
- runtime/status queue 摘要新增:
|
||||
- `heartbeatMs`
|
||||
- `oldestRunningHeartbeatAt`
|
||||
- `oldestRunningHeartbeatAgeMs`
|
||||
- `runningWithoutHeartbeatCount`
|
||||
- `latestRunningRun`
|
||||
- `agent-run-guard.mjs`:
|
||||
- running guard 改用 `oldestRunningHeartbeatAgeMs`。
|
||||
- `check-agent-run-worker.mjs`:
|
||||
- 增加 heartbeat queue 摘要。
|
||||
- 测试:
|
||||
- `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。
|
||||
- 生产部署:
|
||||
- backup `/Users/john/Project/memind_backups/20260702-123510-p83-worker-heartbeat`
|
||||
- restarted Portal and worker
|
||||
- production `.env` added `MEMIND_AGENT_RUN_WORKER_EXPECT_RUNNING=1`
|
||||
- synthetic recovery canary:
|
||||
- no-heartbeat run `042276ae-ecac-4b7c-9c53-c02154ea85cd` recovered to `failed`
|
||||
- fresh-heartbeat run `97a2ac16-0234-483d-be4f-836ba95a91e7` was not recovered and was deleted after verification
|
||||
- real worker 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 `pid=31341`, `attempt=1`, `heartbeatMs=30000`
|
||||
- post-check:
|
||||
- runtime/status queue empty
|
||||
- `runningWithoutHeartbeatCount=0`
|
||||
- worker check `ok=true`, `expected=running`
|
||||
- guard `shouldPause=false`
|
||||
|
||||
## 下一步执行建议
|
||||
|
||||
### P6.22 Task Artifact UX and Failure Messages
|
||||
|
||||
- 用户侧显示 queued/running/validation/succeeded/failed。
|
||||
- 失败原因分层展示。
|
||||
- 用户已决定暂时跳过,不进入下一步开发。
|
||||
|
||||
### P8.4 Long-running Gray Soak and Heartbeat SLO
|
||||
|
||||
- 基于 heartbeat 字段做一轮 24h all-user gray soak。
|
||||
- SLO 日报增加 running heartbeat age 和 missing heartbeat count。
|
||||
- guard 阈值保守观察,不扩大并发。
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
- P6.19 Task-level Artifact Validation: 已完成第一步,H5 code-run 会在 receipt 之外追加可推断的 `public/*.html` 目标文件校验,页面编辑 code-run 会追加 page-edit task receipt,生产真实 canary 三项 expectedFiles 全部通过。
|
||||
- P8.1 Queue Lease / Stuck Run Recovery: 已完成第一步,worker dispatch 前自动回收超时 running run,新增 `--recover-stale` dry-run/apply 运维入口,生产 synthetic stale run 验证通过。
|
||||
- P8.2 Portal DB/Auth Transient Error Hardening: 已完成第一步,session attach、`/auth/status` 和 API auth middleware 已捕获 DB/auth 瞬时错误,生产部署后 live health、auth/status、runtime/status、guard 和 SLO 验证通过。
|
||||
- P8.3 Worker Lease Heartbeat: 已完成第一步,running run 执行期间写入 `worker_heartbeat` event,runtime/status、worker check、guard 和 stale recovery 均优先使用 heartbeat age,生产 synthetic 与真实 worker canary 验证通过。
|
||||
- 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 报告已展示。
|
||||
@@ -2929,6 +2930,91 @@ runtime/status:
|
||||
- `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`
|
||||
|
||||
## 回滚策略
|
||||
|
||||
- P0: 修改前保留 `server.mjs` 备份;如启动失败,恢复备份并 `launchctl kickstart` Portal。
|
||||
|
||||
Reference in New Issue
Block a user