auto-sync: 2026-05-22 12:58:15

This commit is contained in:
cfdaily
2026-05-22 12:58:15 +08:00
parent 0b817a5e13
commit 4bfa7e2b0d
+83 -30
View File
@@ -1,6 +1,6 @@
# Spawner Monitor 设计文档 # Spawner Monitor 设计文档
> 版本:v1.0 | 日期:2026-05-22 | 作者:庞统 | 状态:评审 > 版本:v1.1 | 日期:2026-05-22 | 作者:庞统 | 状态:评审通过(v1.1 已修正评审意见)
## 1. 背景与问题 ## 1. 背景与问题
@@ -72,8 +72,8 @@ daemon:
处理: 处理:
- 续杯次数 +1 - 续杯次数 +1
- 超过上限(3) → ❌ failed + escalate - 超过上限(3) → ❌ failed + escalate → counter.release()
- 未超限 → 🔄 counter.release() → 用同一 session_id spawn(续杯) - 未超限 → 🔄 用同一 session_id spawn(续杯)counter 不 release
- 续杯 message:提示 Agent 检查历史继续未完成工作 - 续杯 message:提示 Agent 检查历史继续未完成工作
``` ```
@@ -89,8 +89,8 @@ daemon:
处理: 处理:
- 续杯次数 +1 - 续杯次数 +1
- 超过上限(3) → ❌ failed + escalate - 超过上限(3) → ❌ failed + escalate → counter.release()
- 未超限 → 🔄 counter.release() → 用同一 session_id spawn(续杯) - 未超限 → 🔄 用同一 session_id spawn(续杯)counter 不 release
- 续杯 message:完整任务 prompt(和首次一样) - 续杯 message:完整任务 prompt(和首次一样)
``` ```
@@ -126,6 +126,7 @@ daemon:
- 查任务实际 API 状态 - 查任务实际 API 状态
- 如果已是 done/review → 同 A1 - 如果已是 done/review → 同 A1
- 如果仍是 working/claimed → 同 A2/A3(续杯,用原 session_id,不用 fallback session - 如果仍是 working/claimed → 同 A2/A3(续杯,用原 session_id,不用 fallback session
- 续杯 prompt 额外提示:"之前有 fallback 执行,请调 API 检查任务当前状态和已有产出,确认是否已完成"
- 记录 outcome = "fallback_timeout",附带 warning - 记录 outcome = "fallback_timeout",附带 warning
``` ```
@@ -169,10 +170,11 @@ daemon:
原因:Gateway 进程挂了、网络断 原因:Gateway 进程挂了、网络断
处理: 处理:
- 续杯次数 +1(不计入 max_retries,用单独的 connect_retry_count - connect_retry_count +1(不计入 max_retries
- connect_retry_count ≥ 3 → ❌ failed + escalate - connect_retry_count ≥ 3 → ❌ failed + escalate → counter.release()
- 未超限 → 🔄 等待 30s(一个 tick)后重试 - 未超限 → 不改变任务状态(保持 claimed/working),不 release counter
- 记录 outcome = "gateway_unreachable" - 只写 metadataoutcome=gateway_unreachable),让 ticker 下个 tick 自然重新调度
- ⚠️ 不在 spawner 里 sleep 等待(避免阻塞 monitor 逻辑)
``` ```
### A9exit≠0 + stderr 含 rate_limit/500/503/API error ### A9exit≠0 + stderr 含 rate_limit/500/503/API error
@@ -185,10 +187,11 @@ daemon:
原因:模型提供商限流、服务异常 原因:模型提供商限流、服务异常
处理: 处理:
- 续杯次数 +1(不计入 max_retries,用单独的 api_retry_count - api_retry_count +1(不计入 max_retries
- api_retry_count ≥ 3 → ❌ failed + escalate - api_retry_count ≥ 3 → ❌ failed + escalate → counter.release()
- 未超限 → 🔄 等待 60s 后重试(给 API 恢复时间) - 未超限 → 不改变任务状态,不 release counter
- 记录 outcome = "api_error" - 只写 metadataoutcome=api_error),让 ticker 下个 tick 自然重新调度
- ⚠️ 不在 spawner 里 sleep 等待
``` ```
### A10exit≠0 + stderr 含 compaction-diag/context-overflow/timeout-compaction ### A10exit≠0 + stderr 含 compaction-diag/context-overflow/timeout-compaction
@@ -201,9 +204,9 @@ daemon:
原因:compact 后模型返回错误(丢失上下文导致无法继续) 原因:compact 后模型返回错误(丢失上下文导致无法继续)
处理: 处理:
- 续杯次数 +1(计入 max_retries - retry_count +1(计入 max_retries
- 超过上限(3) → ❌ failed + escalate - 超过上限(3) → ❌ failed + escalate → counter.release()
- 未超限 → 🔄 用同一 session_id spawn(续杯) - 未超限 → 🔄 用同一 session_id spawn(续杯)counter 不 release
- 记录 outcome = "compact_failed" - 记录 outcome = "compact_failed"
``` ```
@@ -218,9 +221,11 @@ daemon:
v2 用 --session-id uuid4 已基本避免,但保留兜底 v2 用 --session-id uuid4 已基本避免,但保留兜底
处理: 处理:
- 续杯次数 +1(不计入 max_retries - lock_retry_count +1(不计入 max_retries
- 等待 30s 后重试 - lock_retry_count ≥ 3 → ❌ failed + escalate → counter.release()
- 记录 outcome = "lock_conflict" - 未超限 → 不改变任务状态,不 release counter
- 只写 metadataoutcome=lock_conflict),让 ticker 下个 tick 自然重新调度
- ⚠️ 不在 spawner 里 sleep 等待
``` ```
### A12exit≠0 + stderr 无特殊关键字 ### A12exit≠0 + stderr 无特殊关键字
@@ -233,9 +238,9 @@ daemon:
原因:Agent 自身逻辑错误、工具执行失败、或其他未知错误 原因:Agent 自身逻辑错误、工具执行失败、或其他未知错误
处理: 处理:
- 续杯次数 +1(计入 max_retries - retry_count +1(计入 max_retries
- 超过上限(3) → ❌ failed + escalate - 超过上限(3) → ❌ failed + escalate → counter.release()
- 未超限 → 🔄 用同一 session_id spawn(续杯) - 未超限 → 🔄 用同一 session_id spawn(续杯)counter 不 release
- 记录 outcome = "agent_error" - 记录 outcome = "agent_error"
``` ```
@@ -280,8 +285,8 @@ daemon:
处理: 处理:
- monitor_timeout_count +1 - monitor_timeout_count +1
- 未超限(< 3) → counter.release() → 再启动一轮 _monitor_process 继续等 - 未超限(< 3) → 不 release counter → 再启动一轮 _monitor_process 继续等
- 超限(≥ 3,累计 31.5 分钟) → ❌ failed + escalate,不 kill - 超限(≥ 3,累计 31.5 分钟) → ❌ failed + escalate → counter.release(),不 kill
- 记录 outcome = "compact_hanging" - 记录 outcome = "compact_hanging"
``` ```
@@ -300,8 +305,8 @@ daemon:
处理: 处理:
- monitor_timeout_count +1 - monitor_timeout_count +1
- 未超限(< 3) → counter.release() → 再启动一轮 _monitor_process 继续等 - 未超限(< 3) → 不 release counter → 再启动一轮 _monitor_process 继续等
- 超限(≥ 3) → ❌ failed + escalate,不 kill - 超限(≥ 3) → ❌ failed + escalate → counter.release(),不 kill
- 记录 outcome = "process_hanging" - 记录 outcome = "process_hanging"
区别 a 和 b 区别 a 和 b
@@ -338,6 +343,7 @@ daemon:
RETRY_PROMPT = """你收到一个续杯提醒。你的任务在执行过程中被中断了。 RETRY_PROMPT = """你收到一个续杯提醒。你的任务在执行过程中被中断了。
## 任务信息 ## 任务信息
- 项目: {project_id} - 项目: {project_id}
- 任务ID: {task_id} - 任务ID: {task_id}
- 标题: {title} - 标题: {title}
@@ -345,8 +351,35 @@ RETRY_PROMPT = """你收到一个续杯提醒。你的任务在执行过程中
请检查 session 历史中你之前做了什么,然后继续未完成的工作。 请检查 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 ### 续杯 spawn
@@ -371,11 +404,29 @@ await self.spawner.spawn_full_agent(
| `retry_count` | 续杯次数(A2/A3/A10/A12 | 3 | failed + escalate | | `retry_count` | 续杯次数(A2/A3/A10/A12 | 3 | failed + escalate |
| `connect_retry_count` | 连接失败次数(A8 | 3 | failed + escalate | | `connect_retry_count` | 连接失败次数(A8 | 3 | failed + escalate |
| `api_retry_count` | API 错误次数(A9 | 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 | | `monitor_timeout_count` | monitor timeout 次数(B2/B3 | 3 | failed + escalate |
存储在 `task_attempts.metadata` JSON 中。 存储在 `task_attempts.metadata` JSON 中。
### counter 生命周期
```
首次 acquiredispatcher.dispatch
├─ 续杯 spawn → counter 不 release(保持占用)
├─ 继续等(B2/B3 → counter 不 release
├─ 暂时性失败回 tickerA8/A9/A11 → counter 不 release
└─ 最终完成/failed/escalate → counter.release()
```
counter 占用贯穿整个续杯链,只在以下情况 release:
- 任务最终完成(A1/A4
- 超过重试上限 → failed + escalate
- 认证失败(A7
- 假死(B1
## 8. escalate 消息格式 ## 8. escalate 消息格式
``` ```
@@ -410,10 +461,12 @@ Session: {session_key}
| `src/daemon/spawner.py` | `spawn_full_agent``--timeout` + session_id 复用 | ~15 行 | | `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` | 新增辅助方法:`_get_task_status``_classify_exit``_read_sessions_json``_check_lock_pid` | ~80 行 |
| `src/daemon/spawner.py` | 新增 `RETRY_PROMPT` 模板 | ~20 行 | | `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/guardrails.yaml` | 无需改动 | — |
| `config/default.yaml` | 新增 `gateway_timeout``max_retries``max_monitor_timeouts` | ~3 行 | | `config/default.yaml` | 新增 `gateway_timeout``max_retries``max_monitor_timeouts` | ~3 行 |
注:`task_attempts` 表已有 `metadata` 列(TEXT 类型),无需改 db.py/models.py。
总计约 280 行,3 个文件。 总计约 280 行,3 个文件。
## 10. 测试计划 ## 10. 测试计划