From 4bfa7e2b0d3772fb8606eecda050bba465c64d20 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Fri, 22 May 2026 12:58:15 +0800 Subject: [PATCH] auto-sync: 2026-05-22 12:58:15 --- docs/design/spawner-monitor-design.md | 113 +++++++++++++++++++------- 1 file changed, 83 insertions(+), 30 deletions(-) diff --git a/docs/design/spawner-monitor-design.md b/docs/design/spawner-monitor-design.md index d3a7f75..dd21b45 100644 --- a/docs/design/spawner-monitor-design.md +++ b/docs/design/spawner-monitor-design.md @@ -1,6 +1,6 @@ # Spawner Monitor 设计文档 -> 版本:v1.0 | 日期:2026-05-22 | 作者:庞统 | 状态:评审中 +> 版本:v1.1 | 日期:2026-05-22 | 作者:庞统 | 状态:评审通过(v1.1 已修正评审意见) ## 1. 背景与问题 @@ -72,8 +72,8 @@ daemon: 处理: - 续杯次数 +1 - - 超过上限(3) → ❌ failed + escalate - - 未超限 → 🔄 counter.release() → 用同一 session_id spawn(续杯) + - 超过上限(3) → ❌ failed + escalate → counter.release() + - 未超限 → 🔄 用同一 session_id spawn(续杯),counter 不 release - 续杯 message:提示 Agent 检查历史继续未完成工作 ``` @@ -89,8 +89,8 @@ daemon: 处理: - 续杯次数 +1 - - 超过上限(3) → ❌ failed + escalate - - 未超限 → 🔄 counter.release() → 用同一 session_id spawn(续杯) + - 超过上限(3) → ❌ failed + escalate → counter.release() + - 未超限 → 🔄 用同一 session_id spawn(续杯),counter 不 release - 续杯 message:完整任务 prompt(和首次一样) ``` @@ -126,6 +126,7 @@ daemon: - 查任务实际 API 状态 - 如果已是 done/review → 同 A1 - 如果仍是 working/claimed → 同 A2/A3(续杯,用原 session_id,不用 fallback session) + - 续杯 prompt 额外提示:"之前有 fallback 执行,请调 API 检查任务当前状态和已有产出,确认是否已完成" - 记录 outcome = "fallback_timeout",附带 warning ``` @@ -169,10 +170,11 @@ daemon: 原因:Gateway 进程挂了、网络断 处理: - - 续杯次数 +1(不计入 max_retries,用单独的 connect_retry_count) - - connect_retry_count ≥ 3 → ❌ failed + escalate - - 未超限 → 🔄 等待 30s(一个 tick)后重试 - - 记录 outcome = "gateway_unreachable" + - connect_retry_count +1(不计入 max_retries) + - connect_retry_count ≥ 3 → ❌ failed + escalate → counter.release() + - 未超限 → 不改变任务状态(保持 claimed/working),不 release counter + - 只写 metadata(outcome=gateway_unreachable),让 ticker 下个 tick 自然重新调度 + - ⚠️ 不在 spawner 里 sleep 等待(避免阻塞 monitor 逻辑) ``` ### A9:exit≠0 + stderr 含 rate_limit/500/503/API error @@ -185,10 +187,11 @@ daemon: 原因:模型提供商限流、服务异常 处理: - - 续杯次数 +1(不计入 max_retries,用单独的 api_retry_count) - - api_retry_count ≥ 3 → ❌ failed + escalate - - 未超限 → 🔄 等待 60s 后重试(给 API 恢复时间) - - 记录 outcome = "api_error" + - api_retry_count +1(不计入 max_retries) + - api_retry_count ≥ 3 → ❌ failed + escalate → counter.release() + - 未超限 → 不改变任务状态,不 release counter + - 只写 metadata(outcome=api_error),让 ticker 下个 tick 自然重新调度 + - ⚠️ 不在 spawner 里 sleep 等待 ``` ### A10:exit≠0 + stderr 含 compaction-diag/context-overflow/timeout-compaction @@ -201,9 +204,9 @@ daemon: 原因:compact 后模型返回错误(丢失上下文导致无法继续) 处理: - - 续杯次数 +1(计入 max_retries) - - 超过上限(3) → ❌ failed + escalate - - 未超限 → 🔄 用同一 session_id spawn(续杯) + - retry_count +1(计入 max_retries) + - 超过上限(3) → ❌ failed + escalate → counter.release() + - 未超限 → 🔄 用同一 session_id spawn(续杯),counter 不 release - 记录 outcome = "compact_failed" ``` @@ -218,9 +221,11 @@ daemon: v2 用 --session-id uuid4 已基本避免,但保留兜底 处理: - - 续杯次数 +1(不计入 max_retries) - - 等待 30s 后重试 - - 记录 outcome = "lock_conflict" + - lock_retry_count +1(不计入 max_retries) + - lock_retry_count ≥ 3 → ❌ failed + escalate → counter.release() + - 未超限 → 不改变任务状态,不 release counter + - 只写 metadata(outcome=lock_conflict),让 ticker 下个 tick 自然重新调度 + - ⚠️ 不在 spawner 里 sleep 等待 ``` ### A12:exit≠0 + stderr 无特殊关键字 @@ -233,9 +238,9 @@ daemon: 原因:Agent 自身逻辑错误、工具执行失败、或其他未知错误 处理: - - 续杯次数 +1(计入 max_retries) - - 超过上限(3) → ❌ failed + escalate - - 未超限 → 🔄 用同一 session_id spawn(续杯) + - retry_count +1(计入 max_retries) + - 超过上限(3) → ❌ failed + escalate → counter.release() + - 未超限 → 🔄 用同一 session_id spawn(续杯),counter 不 release - 记录 outcome = "agent_error" ``` @@ -280,8 +285,8 @@ daemon: 处理: - monitor_timeout_count +1 - - 未超限(< 3) → counter.release() → 再启动一轮 _monitor_process 继续等 - - 超限(≥ 3,累计 31.5 分钟) → ❌ failed + escalate,不 kill + - 未超限(< 3) → 不 release counter → 再启动一轮 _monitor_process 继续等 + - 超限(≥ 3,累计 31.5 分钟) → ❌ failed + escalate → counter.release(),不 kill - 记录 outcome = "compact_hanging" ``` @@ -300,8 +305,8 @@ daemon: 处理: - monitor_timeout_count +1 - - 未超限(< 3) → counter.release() → 再启动一轮 _monitor_process 继续等 - - 超限(≥ 3) → ❌ failed + escalate,不 kill + - 未超限(< 3) → 不 release counter → 再启动一轮 _monitor_process 继续等 + - 超限(≥ 3) → ❌ failed + escalate → counter.release(),不 kill - 记录 outcome = "process_hanging" 区别 a 和 b: @@ -338,6 +343,7 @@ daemon: RETRY_PROMPT = """你收到一个续杯提醒。你的任务在执行过程中被中断了。 ## 任务信息 + - 项目: {project_id} - 任务ID: {task_id} - 标题: {title} @@ -345,8 +351,35 @@ RETRY_PROMPT = """你收到一个续杯提醒。你的任务在执行过程中 请检查 session 历史中你之前做了什么,然后继续未完成的工作。 -如果已经完成,请调 API 标记完成。 -如果遇到无法解决的问题,标记失败并说明原因。""" +## 操作指令 + +### 查看任务当前状态 +```bash +curl http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}?expand=all +``` + +### 如果已经完成,标记 review +```bash +curl -X POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/status \ + -H 'Content-Type: application/json' \ + -d '{{"status": "review", "agent": "{agent_id}"}}' +``` + +### 写入产出(如果之前没写) +```bash +curl -X POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/outputs \ + -H 'Content-Type: application/json' \ + -d '{{"agent": "{agent_id}", "type": "<类型>", "title": "<标题>", "content": "<内容>", "summary": "<摘要>"}}' +``` + +### 如果无法解决,标记失败 +```bash +curl -X POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/status \ + -H 'Content-Type: application/json' \ + -d '{{"status": "failed", "agent": "{agent_id}", "detail": "<失败原因>"}}' +``` + +{fallback_hint}""" ``` ### 续杯 spawn @@ -371,11 +404,29 @@ await self.spawner.spawn_full_agent( | `retry_count` | 续杯次数(A2/A3/A10/A12) | 3 | failed + escalate | | `connect_retry_count` | 连接失败次数(A8) | 3 | failed + escalate | | `api_retry_count` | API 错误次数(A9) | 3 | failed + escalate | -| `lock_retry_count` | Lock 冲突次数(A11) | 3 | 等待后重试 | +| `lock_retry_count` | Lock 冲突次数(A11) | 3 | ticker 下个 tick 重试 | | `monitor_timeout_count` | monitor timeout 次数(B2/B3) | 3 | failed + escalate | 存储在 `task_attempts.metadata` JSON 中。 +### counter 生命周期 + +``` +首次 acquire(dispatcher.dispatch) + │ + ├─ 续杯 spawn → counter 不 release(保持占用) + ├─ 继续等(B2/B3) → counter 不 release + ├─ 暂时性失败回 ticker(A8/A9/A11) → counter 不 release + │ + └─ 最终完成/failed/escalate → counter.release() +``` + +counter 占用贯穿整个续杯链,只在以下情况 release: +- 任务最终完成(A1/A4) +- 超过重试上限 → failed + escalate +- 认证失败(A7) +- 假死(B1) + ## 8. escalate 消息格式 ``` @@ -410,10 +461,12 @@ Session: {session_key} | `src/daemon/spawner.py` | `spawn_full_agent` 加 `--timeout` + session_id 复用 | ~15 行 | | `src/daemon/spawner.py` | 新增辅助方法:`_get_task_status`、`_classify_exit`、`_read_sessions_json`、`_check_lock_pid` | ~80 行 | | `src/daemon/spawner.py` | 新增 `RETRY_PROMPT` 模板 | ~20 行 | -| `src/daemon/ticker.py` | `_check_timeouts`:增加 retry_count 检查,不再直接 failed | ~15 行 | +| `src/daemon/ticker.py` | `_check_timeouts`:暂时性失败(A8/A9/A11)不改状态,等 ticker 自然重试 | ~15 行 | | `config/guardrails.yaml` | 无需改动 | — | | `config/default.yaml` | 新增 `gateway_timeout`、`max_retries`、`max_monitor_timeouts` | ~3 行 | +注:`task_attempts` 表已有 `metadata` 列(TEXT 类型),无需改 db.py/models.py。 + 总计约 280 行,3 个文件。 ## 10. 测试计划