auto-sync: 2026-05-29 20:10:37
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
**日期**: 2026-05-29
|
||||
**作者**: 庞统
|
||||
**状态**: 设计中
|
||||
**状态**: 待评审
|
||||
**前置**: `01-four-phase-loop.md`(四相循环 E2E 验证暴露 session 爆炸问题)
|
||||
|
||||
---
|
||||
@@ -84,9 +84,9 @@ spawner → 新 UUID session → Agent 执行 → 进程退出 → session 残
|
||||
|
||||
| 路径 | 现行 | 改为 |
|
||||
|------|------|------|
|
||||
| **Broadcast dispatch** | `spawn_full_agent(session_id=UUID)` 新 session | 投递到 main session,Agent 自己决定执行方式 |
|
||||
| **Mention** | `spawn_full_agent(session_id=UUID)` 新 session | 投递到 main session,Agent 自主处理 |
|
||||
| **Review**(庞统) | `spawn_full_agent(session_id=UUID)` 新 session | 投递到庞统 main session,庞统自主 review |
|
||||
| **Broadcast dispatch** | `spawn_full_agent(session_id=UUID)` 新 session | `spawn_full_agent(use_main_session=True)` main session |
|
||||
| **Mention** | `spawn_full_agent(session_id=UUID)` 新 session | `spawn_full_agent(use_main_session=True)` main session |
|
||||
| **Review**(庞统) | `spawn_full_agent(session_id=UUID)` 新 session | `spawn_full_agent(use_main_session=True)` main session |
|
||||
|
||||
---
|
||||
|
||||
@@ -94,17 +94,34 @@ spawner → 新 UUID session → Agent 执行 → 进程退出 → session 残
|
||||
|
||||
### 3.1 投递到 Main Session
|
||||
|
||||
**方式**:`openclaw agent --agent <id> --message "..."`(不传 `--session-id`)
|
||||
**方式**:`spawn_full_agent(use_main_session=True)`(不传 `--session-id`)
|
||||
|
||||
底层命令行:`openclaw agent --agent <id> --message "..." --json --timeout N`
|
||||
|
||||
Gateway 行为(已确认):
|
||||
- 不传 `--session-id` → 投递到 Agent 的 main session(`agent:<id>:main`)
|
||||
- main session 正忙 → 消息排队,Agent 当前 turn 完成后处理
|
||||
- Agent 有完整上下文(SOUL.md / MEMORY.md / workspace bootstrap)
|
||||
|
||||
**spawner 改动**:
|
||||
- 新增 `deliver_to_main()` 方法,用 `openclaw agent --agent <id> --message "..." --json` 投递
|
||||
- 不走 `spawn_full_agent` 的 session 管理,改用 Gateway 的原生投递机制
|
||||
- counter 仍然生效(per main session 粒度,`max_per_session=1` 保证不并发)
|
||||
**调用链路**:
|
||||
|
||||
```
|
||||
spawner.spawn_full_agent(use_main_session=True)
|
||||
→ session_id = None(不传 --session-id)
|
||||
→ counter.acquire(agent_id, "main")
|
||||
→ asyncio.create_subprocess_exec("openclaw", "agent", "--agent", "xxx", ...)
|
||||
→ asyncio.create_task(_monitor_process(...)) # 异步不阻塞
|
||||
→ 返回 "main"
|
||||
|
||||
_monitor_process()
|
||||
→ await proc.wait() + 读 stdout # 异步等子进程退出
|
||||
→ 子进程退出 → _handle_exit()
|
||||
→ 解析 --json 输出
|
||||
→ on_complete 回调(幻觉门控、标状态等)
|
||||
→ counter.release(agent_id, "main")
|
||||
```
|
||||
|
||||
**关键:`openclaw agent` 子进程内部是同步阻塞的**——它会同步等 Gateway 投递消息到 main session → Agent 执行 turn → 回复 → 子进程退出。所以 `on_complete` 只在 Agent 真正完成 turn 后才触发。
|
||||
|
||||
### 3.2 Delegation(subagent-delegation skill)
|
||||
|
||||
@@ -121,20 +138,27 @@ sessions_spawn({
|
||||
|
||||
**关键特性**:
|
||||
- `cleanup: "delete"` → sub 执行完 session 立即删除(transcript 保留可查)
|
||||
- main session 通过 `sessions_yield` 等待 sub 完成(非阻塞,可被新消息打断)
|
||||
- main session 通过 `sessions_yield` 等待 sub 完成(**非阻塞**,可被新消息打断,Gateway 还能投递新消息)
|
||||
- sub 的结果通过完成事件回传给 main session
|
||||
- subagent 是 Agent 自己 spawn 的,**不经过 Daemon counter**
|
||||
|
||||
### 3.3 续杯机制(已有,无需改动)
|
||||
### 3.3 续杯机制
|
||||
|
||||
Agent 在 main session 中执行,如果超时/中断:
|
||||
- 续杯机制(`_do_retry`)已经实现了重新投递逻辑
|
||||
- counter 的 acquire/release 在 `spawn_full_agent` 内部管理
|
||||
- 改用 main session 后,续杯变成"重新投递到 main session",逻辑更简单
|
||||
改用 main session 后,续杯逻辑简化:
|
||||
|
||||
### 3.4 Counter 调整
|
||||
| 维度 | 现行 | 改为 |
|
||||
|------|------|------|
|
||||
| 续杯 session | `reuse_session_id=UUID` 复用原 session | `use_main_session=True` 重新投递到 main session |
|
||||
| 上下文 | 新 session 无历史 | main session 有完整历史 |
|
||||
| counter | acquire 新 session key | acquire 同一个 "main" key |
|
||||
| 失败处理 | 重试时可能创建新的 fallback session | 直接重投 main session,无 fallback 问题 |
|
||||
|
||||
改用 main session 后:
|
||||
- 所有 spawn 走同一个 session key(`agent:main`)
|
||||
**改动**:`_do_retry` 中把 `reuse_session_id` 改为 `use_main_session=True`,移除 `reuse_session_id` 参数。
|
||||
|
||||
### 3.4 Counter(无需改动)
|
||||
|
||||
改用 main session 后 counter 逻辑不变:
|
||||
- 所有 spawn 走同一个 session key(`"main"`)
|
||||
- `max_per_session=1` 保证同一时间只有一个 active turn
|
||||
- `max_concurrent_sessions=3` 限制同一 Agent 最多 3 个不同 session(main + 可能的 sub)
|
||||
- `max_global=5` 限制全局并发
|
||||
@@ -147,7 +171,7 @@ Agent 在 main session 中执行,如果超时/中断:
|
||||
|
||||
### 4.1 投递消息格式
|
||||
|
||||
Daemon 投递到 main session 的消息需要包含足够的上下文,让 Agent 自主决策:
|
||||
Daemon 投递到 main session 的消息包含足够的上下文,让 Agent 自主决策:
|
||||
|
||||
```markdown
|
||||
📋 新任务到达
|
||||
@@ -169,7 +193,7 @@ Daemon 投递到 main session 的消息需要包含足够的上下文,让 Agen
|
||||
**约束**:
|
||||
- 完成后必须写产出 + 标 review
|
||||
- 失败了标 failed 并写明原因
|
||||
- 使用 subagent-delegation skill 进行具体执行
|
||||
- 优先使用 subagent-delegation skill 进行具体执行
|
||||
- 禁止使用 sessions_send 直接发消息
|
||||
```
|
||||
|
||||
@@ -216,52 +240,44 @@ Agent 收到消息后的自主行为(不是硬编码步骤):
|
||||
|
||||
## 五、Daemon 侧改动
|
||||
|
||||
### 5.1 Spawner 新增方法
|
||||
### 5.1 改动范围
|
||||
|
||||
复用 Mail 的 `use_main_session=True` 模式,将三条路径统一:
|
||||
**不需要新增方法**。直接复用 `spawn_full_agent(use_main_session=True)`,Mail 路径已验证此模式。
|
||||
|
||||
```python
|
||||
async def deliver_to_main(self, agent_id: str, message: str,
|
||||
task_id: str = None,
|
||||
on_complete: Callable = None) -> str:
|
||||
"""投递到 Agent main session
|
||||
改动集中在调用侧(把参数改对)和回调侧(把完成检测从邮件改为黑板):
|
||||
|
||||
复用 spawn_full_agent(use_main_session=True),不传 --session-id。
|
||||
Gateway 自动路由到 main session。
|
||||
"""
|
||||
return await self.spawn_full_agent(
|
||||
agent_id=agent_id,
|
||||
message=message,
|
||||
use_main_session=True, # 关键:走 main session
|
||||
task_id=task_id,
|
||||
on_complete=on_complete,
|
||||
)
|
||||
```
|
||||
| 文件 | 改动点 | 改动量 |
|
||||
|------|--------|-------|
|
||||
| `ticker.py` | 3 处 `spawn_full_agent` 调用加 `use_main_session=True`;移除 `new_session=True` | ~15 行 |
|
||||
| `dispatcher.py` | 确定性路由改 `use_main_session=True`;新增 `_task_auto_complete` | ~40 行 |
|
||||
| `spawner.py` | `_do_retry` 续杯改 `use_main_session=True`;标注 `new_session`/`reuse_session_id` 废弃 | ~10 行 |
|
||||
| `ticker.py` | 新增 `_task_verify_completion`(第一层幻觉门控);review 前跑 `_verify_output_files`(第二层) | ~50 行 |
|
||||
|
||||
**不需要新建方法**,只需调用 `spawn_full_agent(use_main_session=True)`。Mail 路径已验证此模式。
|
||||
**总改动量**:~115 行,涉及 3 个文件。不涉及 DB schema 变更、不涉及前端。
|
||||
|
||||
### 5.2 Ticker 改动
|
||||
|
||||
| 方法 | 改动 |
|
||||
|------|------|
|
||||
| `_broadcast_and_dispatch` | `spawn_full_agent(..., use_main_session=True)` |
|
||||
| `_process_mentions` | `spawn_full_agent(..., use_main_session=True)` |
|
||||
| `_spawn_pangtong_review` | `spawn_full_agent(..., use_main_session=True)` |
|
||||
| `_check_round_complete` | 保留 `reviewing` 中间态逻辑 |
|
||||
| `_handle_review_conclusion` | 改为从黑板检查状态变化(复用 Mail 的 `_task_auto_complete` 模式) |
|
||||
| `_broadcast_and_dispatch` | `spawn_full_agent(..., use_main_session=True)` 替代当前的默认参数 |
|
||||
| `_process_mentions` | 同上 |
|
||||
| `_spawn_pangtong_review` | 同上 |
|
||||
| `_check_round_complete` | 保留 `reviewing` 中间态逻辑(SYS-BUG-01 修复) |
|
||||
| `_handle_review_conclusion` | 保留,从 `on_complete` 回调中的黑板状态变化触发 |
|
||||
| 新增 `_task_verify_completion` | 第一层幻觉门控(三信号检查) |
|
||||
| 新增 `_verify_output_files` | 第二层幻觉门控(文件存在性检查) |
|
||||
|
||||
### 5.3 Dispatcher 改动
|
||||
|
||||
- 所有路由统一走 `use_main_session=True`
|
||||
- 移除 `new_session` 参数(不再需要)
|
||||
- 新增 `_task_auto_complete`(复用 Mail 的 `_mail_auto_complete` 模式,检查 outputs + status)
|
||||
- counter 逻辑不变(main session 粒度,per key = `agent:main`)
|
||||
- 确定性路由改 `use_main_session=True`
|
||||
- 新增 `_task_auto_complete`(复用 Mail 的 `_mail_auto_complete` 模式)
|
||||
- `new_session` 参数标注废弃
|
||||
|
||||
### 5.4 废弃代码
|
||||
|
||||
- `spawn_full_agent` 的 `new_session` 参数标注废弃(所有调用改为 `use_main_session=True`)
|
||||
- `gateway-fallback-*` session 的产生路径消失
|
||||
- 续杯简化:main session 的续杯由 Gateway 排队 + ticker 超时兜底处理
|
||||
- `spawn_full_agent` 的 `new_session` 参数 → 标注废弃(保留接口兼容,不再有调用方传 True)
|
||||
- `reuse_session_id` 参数 → 标注废弃(续杯改用 `use_main_session=True`)
|
||||
- `gateway-fallback-*` session 的产生路径 → 自然消失(不再传 `--session-id UUID`)
|
||||
|
||||
---
|
||||
|
||||
@@ -301,19 +317,20 @@ sub session(临时,完成即删)
|
||||
|------|------|
|
||||
| **Session 数量** | 从无限增长 → 6 个 main + 临时 sub |
|
||||
| **Agent 上下文** | 从无历史 → 完整身份 + 记忆 + workspace |
|
||||
| **代码简化** | 移除 `new_session` / `use_main_session` / `reuse_session_id` 逻辑 |
|
||||
| **代码简化** | `new_session` / `reuse_session_id` 不再有调用方 |
|
||||
| **Prompt 灵活度** | 从硬编码步骤 → Agent 自主决策 |
|
||||
| **空 payloads** | 消除(main session 有完整生命周期管理) |
|
||||
| **续杯可靠性** | 不再创建 fallback session,直接重投 main session |
|
||||
|
||||
### 风险
|
||||
### 风险与缓解
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|------|------|
|
||||
| **main session 上下文膨胀** | OpenClaw 自带的 compact 机制处理;sub session 不膨胀(用完删) |
|
||||
| **main session 被阻塞** | `sessions_yield` 非阻塞,可被新消息打断;Gateway 排队机制保证不丢消息 |
|
||||
| **sub 质量不可控** | main session review sub 结果;reviewing 中间态保证 round 不重复触发 |
|
||||
| **Prompt 依赖** | Agent 需要正确的 prompt 来知道怎么 delegation;subagent-delegation skill 已调优 |
|
||||
| **Gateway 投递延迟** | main session 排队是异步的,Daemon 不同步等结果;改为"投递后等黑板状态变化" |
|
||||
| **Prompt 依赖** | 投递消息中明确告诉 Agent "优先使用 subagent-delegation skill";skill 已调优 |
|
||||
| **main session 排队延迟** | Daemon 投递后 `on_complete` 异步等结果;ticker 轮询黑板状态兜底 |
|
||||
|
||||
---
|
||||
|
||||
@@ -323,11 +340,13 @@ Mail 路径(`dispatcher.py` `_mail_on_checks_passed` + `_mail_auto_complete`
|
||||
|
||||
```
|
||||
openclaw agent --agent xxx(不传 --session-id)→ Gateway 投递到 main session
|
||||
→ Agent 处理 → 进程退出
|
||||
→ on_complete 回调触发 → _mail_auto_complete
|
||||
→ 幻觉门控:检查黑板是否有回复
|
||||
→ 有回复 → 标 done
|
||||
→ 无回复 → 留 working,等 ticker 超时兜底再查
|
||||
→ Agent 处理 → 子进程退出
|
||||
→ _monitor_process → _handle_exit
|
||||
→ on_complete 回调 → _mail_auto_complete
|
||||
→ 幻觉门控:检查黑板是否有回复
|
||||
→ 有回复 → 标 done
|
||||
→ 无回复 → 留 working,等 ticker 超时兜底再查
|
||||
→ counter.release(agent_id, "main")
|
||||
```
|
||||
|
||||
### 已验证的机制
|
||||
@@ -335,53 +354,15 @@ openclaw agent --agent xxx(不传 --session-id)→ Gateway 投递到 main se
|
||||
| 机制 | Mail 实现 | 普通 Task 复用 |
|
||||
|------|----------|--------------|
|
||||
| Main session 投递 | `use_main_session=True` | ✅ 直接复用 |
|
||||
| `on_complete` 回调 | `_mail_on_complete` → `_mail_auto_complete` | ✅ 改为检查产出/状态而非邮件回复 |
|
||||
| 幻觉门控 | `_mail_check_reply` 检查回复邮件 | ⚠️ 改为检查 `outputs` 表 + `status` 字段 |
|
||||
| `on_complete` 回调 | `_mail_on_complete` → `_mail_auto_complete` | ✅ 改为 `_task_auto_complete`(检查产出/状态而非邮件回复) |
|
||||
| 幻觉门控 | `_mail_check_reply` 检查回复邮件 | ⚠️ 改为三信号检查(status + outputs + comments) |
|
||||
| 超时兜底 | ticker 轮询 working 状态 + 再次检查 | ✅ 直接复用 |
|
||||
| auto-working | `on_checks_passed` 回调中标 working | ✅ 直接复用 |
|
||||
| Counter acquire/release | `"main"` session key | ✅ 直接复用 |
|
||||
|
||||
### 普通 Task 的完成检测(复用 Mail 模式)
|
||||
### 普通 Task 的完成检测
|
||||
|
||||
Mail 的 `_mail_auto_complete` 检查的是"是否有回复邮件"。普通 Task 改为检查:
|
||||
|
||||
```python
|
||||
def _task_auto_complete(self, task_id, agent_id, db_path):
|
||||
"""普通 Task 完成检测(复用 Mail 模式)
|
||||
|
||||
检查逻辑:
|
||||
1. outputs 表是否有产出(非空)
|
||||
2. status 是否已由 Agent 自行更新(review/done/failed)
|
||||
3. 如果都没有 → 留 working,等 ticker 超时兜底
|
||||
"""
|
||||
conn = get_connection(db_path)
|
||||
try:
|
||||
row = conn.execute("SELECT status FROM tasks WHERE id=?", (task_id,)).fetchone()
|
||||
if not row or row["status"] != "working":
|
||||
return # Agent 已自行更新状态
|
||||
|
||||
# 检查是否有产出
|
||||
output_count = conn.execute(
|
||||
"SELECT COUNT(*) FROM outputs WHERE task_id=?", (task_id,)
|
||||
).fetchone()[0]
|
||||
|
||||
if output_count > 0:
|
||||
# 有产出 → Agent 忘了标状态,帮它标 review
|
||||
conn.execute("UPDATE tasks SET status='review' WHERE id=?", (task_id,))
|
||||
conn.commit()
|
||||
else:
|
||||
# 无产出 → 留 working 等 ticker 超时兜底
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
```
|
||||
|
||||
### 待确认
|
||||
|
||||
| # | 问题 | 状态 |
|
||||
|---|------|------|
|
||||
| 1 | **投递消息的 prompt 模板** | ✅ 告诉 Agent "优先使用 subagent-delegation skill",让 Agent 自己决定 |
|
||||
| 2 | **E2E 测试适配** | ✅ 测试验证黑板状态变化,不依赖具体 session 机制 |
|
||||
| 3 | **幻觉门控策略** | ✅ 三层门控(见下文),已确认 |
|
||||
Mail 的 `_mail_auto_complete` 检查"是否有回复邮件"。普通 Task 改为 `_task_auto_complete`,检查黑板三信号(详见第九节幻觉门控方案)。
|
||||
|
||||
---
|
||||
|
||||
@@ -389,16 +370,14 @@ def _task_auto_complete(self, task_id, agent_id, db_path):
|
||||
|
||||
### 第一层:Daemon 确定性检查(on_complete 触发)
|
||||
|
||||
`on_complete` 回调触发时机:`openclaw agent` 子进程退出时(Agent 在 main session 执行完 turn)。
|
||||
Agent 没执行完进程不会退出,不存在长任务误判。
|
||||
**触发时机**:`on_complete` 回调(`openclaw agent` 子进程退出时)。
|
||||
Agent 在 main session 里执行时子进程还活着,不会触发。不存在长任务误判。
|
||||
|
||||
**三信号检查**:status / outputs / comments 任一存在即视为有效完成。
|
||||
|
||||
```python
|
||||
def _task_verify_completion(self, task_id, db_path):
|
||||
"""普通 Task 完成验证(泛化 Mail 幻觉门控)
|
||||
|
||||
三信号检查:status / outputs / comments 是否有变化。
|
||||
任一信号存在即视为有效完成。
|
||||
"""
|
||||
def _task_verify_completion(self, task_id, db_path) -> bool:
|
||||
"""普通 Task 完成验证(泛化 Mail 幻觉门控)"""
|
||||
conn = get_connection(db_path)
|
||||
try:
|
||||
# 信号 1:Agent 已自行更新状态(review/done/failed)
|
||||
@@ -433,15 +412,13 @@ def _task_verify_completion(self, task_id, db_path):
|
||||
- 通过 → 标 review(等待庞统 review)
|
||||
- 不通过 → 留 working,ticker 重查(最多 3 次,然后标 failed)
|
||||
|
||||
**不会误判长任务**:`on_complete` 只在 `openclaw agent` 子进程退出时触发。Agent 在 main session 里执行时进程还活着,不会触发。
|
||||
### 第二层:Daemon 产出物文件验证(review 阶段触发)
|
||||
|
||||
### 第二层:Daemon 产出物文件验证(review 触发)
|
||||
|
||||
对 `outputs` 表中有 `content_path` 的产出,验证文件是否真实存在。放在 Daemon(确定性代码,不依赖 AI):
|
||||
对 `outputs` 表中有 `content_path` 的产出,验证文件是否真实存在。放在 Daemon(确定性代码,成本低、不依赖 AI):
|
||||
|
||||
```python
|
||||
def _verify_output_files(self, task_id, db_path):
|
||||
"""验证产出物文件是否真实存在(Daemon 确定性检查)"""
|
||||
def _verify_output_files(self, task_id, db_path) -> list[str]:
|
||||
"""验证产出物文件是否真实存在"""
|
||||
conn = get_connection(db_path)
|
||||
outputs = conn.execute(
|
||||
"SELECT content_path FROM outputs WHERE task_id=? AND content_path IS NOT NULL",
|
||||
@@ -463,7 +440,7 @@ def _verify_output_files(self, task_id, db_path):
|
||||
1. 检查产出是否覆盖 must_haves 中的验收标准
|
||||
2. 如果产出不可验证,在评论中标注
|
||||
|
||||
这一层由 AI 做,成本高但准确度高,只 review 时触发。
|
||||
这一层由 AI 做,成本高但准确度高,只在 review 时触发。
|
||||
|
||||
### 三层关系
|
||||
|
||||
@@ -483,12 +460,14 @@ openclaw agent 子进程退出
|
||||
|
||||
---
|
||||
|
||||
## 九、实施计划
|
||||
## 十、实施计划
|
||||
|
||||
### Phase 1:统一投递到 main session
|
||||
### Phase 1:统一投递到 main session + 幻觉门控
|
||||
|
||||
- 所有 `spawn_full_agent` 调用改为 `use_main_session=True`
|
||||
- 新增 `_task_auto_complete`(复用 Mail 模式)
|
||||
- 所有 `spawn_full_agent` 调用改为 `use_main_session=True`(3 处)
|
||||
- 续杯 `_do_retry` 改为 `use_main_session=True`
|
||||
- 新增 `_task_verify_completion`(第一层幻觉门控)
|
||||
- 新增 `_verify_output_files`(第二层幻觉门控)
|
||||
- 单元测试覆盖
|
||||
|
||||
### Phase 2:E2E 测试适配 + 验证
|
||||
@@ -500,7 +479,7 @@ openclaw agent 子进程退出
|
||||
|
||||
---
|
||||
|
||||
## 十、参考文件
|
||||
## 十一、参考文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
@@ -509,3 +488,4 @@ openclaw agent 子进程退出
|
||||
| `subagent-delegation/SKILL.md` | Delegation skill(Agent 侧执行指南) |
|
||||
| `counter.py` | 并发控制(per session 粒度) |
|
||||
| `spawner.py` `_do_retry` | 续杯机制 |
|
||||
| `dispatcher.py` `_mail_auto_complete` | Mail 幻觉门控(已验证,可复用) |
|
||||
|
||||
Reference in New Issue
Block a user