auto-sync: 2026-05-22 12:58:15
This commit is contained in:
@@ -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. 测试计划
|
||||
|
||||
Reference in New Issue
Block a user