feat: recover stale running agent runs

This commit is contained in:
Your Name
2026-07-02 12:12:41 +08:00
parent af5396c127
commit c27bf04a71
7 changed files with 334 additions and 15 deletions
+12
View File
@@ -180,6 +180,18 @@ MEMIND_AGENT_RUN_WORKER_EXPECT_RUNNING=1 node /Users/john/Project/Memind/scripts
The worker LaunchAgent must have `KeepAlive=true` during all-user gray so a clean worker exit does not leave code-run queue processing stopped.
Check stale running recovery before and after worker changes:
```bash
node /Users/john/Project/Memind/scripts/agent-run-worker.mjs --recover-stale --stale-ms 900000 --limit 5
```
Apply only when the listed running rows are known stale:
```bash
node /Users/john/Project/Memind/scripts/agent-run-worker.mjs --recover-stale --apply-recovery --stale-ms 900000 --limit 5
```
8. Submit only code runs with validation metadata.
Required user message metadata:
@@ -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 真实执行 canaryP6.5 已完成 OpenHands 真实执行 canaryP6.6 已完成 external worker 精确接管 code-run canaryP6.7 已加入 Tool Gateway 产物校验与输出审计,P6.8 已安装 external worker LaunchAgentP6.9 已完成带 validation 的 external worker 灰度 canaryP6.10 已加入 external worker 只读观测脚本,P6.11 已加入放量策略门禁,P6.12 已开启全用户长期灰度并通过普通测试用户真实路径,P6.13 已安装自动暂停 guardP6.15 已让 H5 code-run 自动补 receipt validation 并恢复后端 required validationP6.17/P6.18 已把 external worker 并发 2 通过 canary 并固化为当前 all-user gray 策略。
- Aider/OpenHands 已从普通聊天默认能力中剥离,进入 code mode 和后端灰度门禁;P6.3 已把 code run 从 goosed session extension 外移到 `agent-run-v1` Tool Gateway 协议,P6.4 已完成 Aider 真实执行 canaryP6.5 已完成 OpenHands 真实执行 canaryP6.6 已完成 external worker 精确接管 code-run canaryP6.7 已加入 Tool Gateway 产物校验与输出审计,P6.8 已安装 external worker LaunchAgentP6.9 已完成带 validation 的 external worker 灰度 canaryP6.10 已加入 external worker 只读观测脚本,P6.11 已加入放量策略门禁,P6.12 已开启全用户长期灰度并通过普通测试用户真实路径,P6.13 已安装自动暂停 guardP6.15 已让 H5 code-run 自动补 receipt validation 并恢复后端 required validationP6.17/P6.18 已把 external worker 并发 2 通过 canary 并固化为当前 all-user gray 策略P8.1 已加入 stale running recovery
- 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 metadataP6.17/P6.18 worker concurrency 2 已经落地。主要剩余差距转为 running/stuck recoveryDB/session auth 瞬时错误收敛、更细粒度的任务级产物规范和用户可见进度/失败说明。
可以支撑当前 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 metadataP6.17/P6.18 worker concurrency 2 和 P8.1 stale running recovery 已经落地。主要剩余差距转为 DB/session auth 瞬时错误收敛、更细粒度的任务级产物规范和用户可见进度/失败说明。
## 实测结果
@@ -891,21 +891,32 @@ Data boundary:
- observation:
- Portal had one restart-window `read EADDRNOTAVAIL`, then LaunchAgent recovered and status returned `ok`.
## 下一步执行建议
### P8.1 Queue Lease / Stuck Run Recovery
-`running` run 增加 lease / stale recovery:
- worker 启动和轮询时识别超过 TTL 的 running run。
- 将超时 running 标记 failed 或 retryable,避免 worker/进程异常退出后永久卡住
- guard 同步读取 stale running 作为暂停依据
- 增加只读检查脚本输出:
- oldest running age
- stale running count
- recovery dry-run actions
- 同时修补 Portal auth/session DB 瞬时错误:
- `read EADDRNOTAVAIL` 不应作为 uncaught exception 杀掉 Portal。
- session attach 失败应降级为 503/401 或跳过认证态,而不是进程退出
结果: 通过,worker dispatch 前会自动回收超过 timeout 的 running run,并提供手动 dry-run/apply 运维入口。
- 新增 `recoverStaleRunningRuns()`
- `dispatchQueuedRuns()` 自动执行 stale recovery
- `agent-run-worker.mjs` 新增:
- `--recover-stale`
- `--apply-recovery`
- `--stale-ms`
- `check-agent-run-worker.mjs` 和 runtime/status queue 摘要新增 running age。
- 测试:
- 29 tests pass
- stale dry-run 不改状态。
- stale apply 标记 failed 并写 `stale_recovered`
- 生产 synthetic stale run 验证:
- run id `9e22ad3d-ad5d-42b5-9ce3-24fe7aace3f5`
- status `failed`
- event `stale_recovered`
- post-check:
- queue empty
- worker running `--limit 2`
- guard `shouldPause=false`
- SLO `ok=true`, `failures=[]`
## 下一步执行建议
### P6.19 Task-level Artifact Validation
@@ -913,6 +924,12 @@ Data boundary:
- 页面编辑任务追加目标 `public/*.html` 校验。
- 文件/仓库任务根据任务参数声明目标相对路径。
### P8.2 Portal DB/Auth Transient Error Hardening
- `read EADDRNOTAVAIL` 不应作为 uncaught exception 杀掉 Portal。
- session attach / auth verify 的 DB 瞬时错误应返回 503/401 或跳过认证态。
- 增加回归测试,证明 DB read transient error 不会触发进程级 unhandled rejection。
### P6.22 Task Artifact UX and Failure Messages
- 用户侧显示 queued/running/validation/succeeded/failed。
@@ -26,6 +26,7 @@
- P6.15 H5 Code-run Validation Metadata: 已完成,H5 code-run 自动声明 `.memind/agent-runs/<requestId>.json` receipt 校验,生产已恢复 `MEMIND_AGENT_CODE_RUNS_REQUIRE_VALIDATION=1` 并通过普通测试用户真实路径。
- P6.17 Controlled Worker Concurrency Canary: 已完成,external worker 短窗口提升到并发 2,两条真实 code-run 同时 running 并全部通过 receipt validation。
- P6.18 Concurrency Rollout Policy: 已完成,生产 worker 和 Portal runtime/status 已对齐到并发 2worker KeepAlive 保持开启,保留回滚备份。
- P8.1 Queue Lease / Stuck Run Recovery: 已完成第一步,worker dispatch 前自动回收超时 running run,新增 `--recover-stale` dry-run/apply 运维入口,生产 synthetic stale run 验证通过。
- P5.15 Active Stream TTL Reconcile: 已按用户要求跳过,暂不做报表/定时 reconcile。
- P5 Worker Pool 运维化: 已完成第一步,Redis Router 支持 worker drain。
- P5.9 First-token Latency EWMA: 已完成,StreamController 会把首个 SSE chunk 延迟写入 RedisSLO 报告已展示。
@@ -2736,6 +2737,74 @@ runtime/status:
- Portal 在一次重启窗口中出现 `read EADDRNOTAVAIL` 并由 LaunchAgent 自动拉起,随后 `/api/status``/api/runtime/status` 恢复 `ok`
- 后续建议优先做 running/stuck recovery 与 DB/session auth 异常不致命化,避免瞬时 DB/socket 错误扩大为进程退出。
### 2026-07-02 P8.1 Queue Lease / Stuck Run Recovery
目标:
- 防止 worker/进程在 run 进入 `running` 后异常退出,导致该 run 永久卡住。
- 在不新增 schema、不修改真实用户数据的前提下,先用 `started_at + runTimeoutMs` 作为最小 lease。
新增能力:
- `agent-run-gateway.mjs`:
- 新增 `recoverStaleRunningRuns()`
- `dispatchQueuedRuns()` 每轮 dispatch 前自动回收超过 `runTimeoutMs``running` rows。
- stale row 会标记为 `failed`,写入 `stale_recovered` event。
- `scripts/agent-run-worker.mjs`:
- 新增只读入口:
- `node scripts/agent-run-worker.mjs --recover-stale --stale-ms 900000 --limit 5`
- 新增应用入口:
- `node scripts/agent-run-worker.mjs --recover-stale --apply-recovery --stale-ms 900000 --limit 5`
- `scripts/check-agent-run-worker.mjs`:
- queue 摘要增加:
- `oldestRunningStartedAt`
- `oldestRunningAgeMs`
- runtime/status 的 queue 摘要增加 running age 字段。
测试:
- `node --check agent-run-gateway.mjs scripts/agent-run-worker.mjs scripts/check-agent-run-worker.mjs` 通过。
- `node --test agent-run-gateway.test.mjs agent-run-routes.test.mjs` 通过,29 tests pass。
- 新增测试:
- dry-run reports stale running rows without mutating them。
- apply marks stale running rows failed and writes `stale_recovered` event。
生产部署:
- 备份:
- `/Users/john/Project/memind_backups/20260702-120900-p81-stale-recovery`
- 已部署:
- bundled `server.mjs`
- bundled `scripts/agent-run-worker.mjs`
- `scripts/check-agent-run-worker.mjs`
- `RUNBOOK.txt`
- 已重启:
- `cn.tkmind.memind-portal`
- `cn.tkmind.memind-agent-run-worker`
生产验证:
- `/api/status` 返回 `ok`
- `/api/runtime/status.toolRuntime.queue`:
- `maxConcurrentRuns=2`
- `statusCounts={}`
- `oldestRunningStartedAt=null`
- `oldestRunningAgeMs=0`
- stale dry-run:
- `considered=0`
- `recovered=0`
- synthetic stale apply:
- run id `9e22ad3d-ad5d-42b5-9ce3-24fe7aace3f5`
- request id `p81-stale-recovery-1782965482040`
- recovery `considered=1`, `recovered=1`
- final status `failed`
- event chain includes `stale_recovered`
- post-check:
- worker running with `--limit 2`
- queue empty
- guard `shouldPause=false`
- SLO `ok=true`, `failures=[]`
## 回滚策略
- P0: 修改前保留 `server.mjs` 备份;如启动失败,恢复备份并 `launchctl kickstart` Portal。