Files
sanguo_moziplus_v2/docs/design/02-main-session-delegation.md
T
2026-05-29 18:33:32 +08:00

365 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 02-main-session-delegation.md — Main Session + Delegation 架构
**日期**: 2026-05-29
**作者**: 庞统
**状态**: 设计中
**前置**: `01-four-phase-loop.md`(四相循环 E2E 验证暴露 session 爆炸问题)
---
## 一、问题:现行 spawn 方案的 session 爆炸
### 现状
spawner 通过 `openclaw agent --session-id UUID --message "..." --json` spawn Agent
```
spawner → 新 UUID session → Agent 执行 → 进程退出 → session 残留
```
### 问题
| 问题 | 证据 | 影响 |
|------|------|------|
| **Session 爆炸** | 今天 E2E 测试 6 个 Agent 产生 68 个 session16~484 每个),其中 56 个 `gateway-fallback-*` | 文件系统堆积、磁盘浪费 |
| **空 payloads** | 绝大部分 spawn 返回 `status: None, payloads: []` | Daemon 以为任务完成了但实际没有产出 |
| **上下文丢失** | 每次 spawn 新 sessionAgent 无历史、无 workspace bootstrap | Agent 不知道自己是谁、不知道之前的决策 |
| **续杯浪费** | 续杯时 `reuse_session_id` 复用 UUID,但 Gateway 可能又创建 fallback | round_count 空转、Agent busy 但不产出 |
### 根因
`openclaw agent --session-id UUID` 的语义是"在指定 session 里执行 turn",但 Gateway 的实际行为是 find-or-create
- session 不存在 → 创建 `gateway-fallback-<UUID>` session
- 每次都是全新上下文
- Agent 没有历史,没有 SOUL.md 等身份注入
**这是结构性问题,不是 bug。** 在这个方案上继续修 bug 没有意义。
---
## 二、方案:Main Session + Delegation
### 核心思路
```
现行:Daemon 硬编码步骤 → spawner 直接 spawn 新 session → Agent 被动执行
改为:Daemon 投递任务 → Agent main session 收到 → Agent 自主分析 → sessions_spawn(subagent) 执行 → sub 完成 → Agent review → 写回黑板
```
### 架构对比
```
┌─ 现行 ──────────────────────────────────────────┐
│ │
│ Daemon/Ticker │
│ ├── broadcast → spawn_full_agent(UUID session) │
│ │ → Agent 执行固定步骤 prompt │
│ │ → 空 payloads / session 残留 │
│ ├── mention → spawn_full_agent(UUID session) │
│ └── review → spawn_full_agent(UUID session) │
│ │
└──────────────────────────────────────────────────┘
┌─ 改为 ───────────────────────────────────────────┐
│ │
│ Daemon/Ticker │
│ ├── broadcast → 投递到 Agent main session │
│ ├── mention → 投递到 Agent main session │
│ └── review → 投递到庞统 main session │
│ │
│ Agent Main Session(有完整身份+上下文) │
│ ├── 收到任务消息 │
│ ├── 分析黑板(API 读任务详情/上下文/依赖) │
│ ├── sessions_spawn(subagent, cleanup: "delete") │
│ │ └── sub 执行具体工作 → 完成后自动清理 │
│ ├── sessions_yield → 等待 sub 完成 │
│ ├── review sub 结果 │
│ └── 写回黑板(产出/状态/评论) │
│ │
└───────────────────────────────────────────────────┘
```
### 三条路径的变化
| 路径 | 现行 | 改为 |
|------|------|------|
| **Broadcast dispatch** | `spawn_full_agent(session_id=UUID)` 新 session | 投递到 main sessionAgent 自己决定执行方式 |
| **Mention** | `spawn_full_agent(session_id=UUID)` 新 session | 投递到 main sessionAgent 自主处理 |
| **Review**(庞统) | `spawn_full_agent(session_id=UUID)` 新 session | 投递到庞统 main session,庞统自主 review |
---
## 三、关键机制
### 3.1 投递到 Main Session
**方式**`openclaw agent --agent <id> --message "..."`(不传 `--session-id`
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` 保证不并发)
### 3.2 Delegationsubagent-delegation skill
Agent 收到任务后,用 `sessions_spawn` 创建 subagent 执行具体工作:
```javascript
sessions_spawn({
task: "任务目标\n\n上下文信息",
cleanup: "delete", // 完成后自动清理 session
model: "zhipu/glm-5.1",
label: "T1-01"
})
```
**关键特性**
- `cleanup: "delete"` → sub 执行完 session 立即删除(transcript 保留可查)
- main session 通过 `sessions_yield` 等待 sub 完成(非阻塞,可被新消息打断)
- sub 的结果通过完成事件回传给 main session
### 3.3 续杯机制(已有,无需改动)
Agent 在 main session 中执行,如果超时/中断:
- 续杯机制(`_do_retry`)已经实现了重新投递逻辑
- counter 的 acquire/release 在 `spawn_full_agent` 内部管理
- 改用 main session 后,续杯变成"重新投递到 main session",逻辑更简单
### 3.4 Counter 调整
改用 main session 后:
- 所有 spawn 走同一个 session key`agent:main`
- `max_per_session=1` 保证同一时间只有一个 active turn
- `max_concurrent_sessions=3` 限制同一 Agent 最多 3 个不同 sessionmain + 可能的 sub
- `max_global=5` 限制全局并发
**注意**subagent 是 Agent 自己 spawn 的,不经过 Daemon counter。Daemon 只控制 main session 的投递。
---
## 四、Prompt 设计
### 4.1 投递消息格式
Daemon 投递到 main session 的消息需要包含足够的上下文,让 Agent 自主决策:
```markdown
📋 新任务到达
**任务**: {title}
**描述**: {description}
**Project**: {project_id}
**Task ID**: {task_id}
**类型**: {task_type} | **风险**: {risk_level}
**验收标准**: {must_haves}
**依赖产出**: {depends_on_outputs_summary}
**黑板 API**: http://localhost:8083/api/projects/{project_id}
- 读任务详情: GET /api/projects/{project_id}/tasks/{task_id}?expand=all
- 写产出: POST /api/projects/{project_id}/tasks/{task_id}/outputs
- 写评论: POST /api/projects/{project_id}/tasks/{task_id}/comments
- 更新状态: POST /api/projects/{project_id}/tasks/{task_id}/status
**约束**:
- 完成后必须写产出 + 标 review
- 失败了标 failed 并写明原因
- 使用 subagent-delegation skill 进行具体执行
- 禁止使用 sessions_send 直接发消息
```
### 4.2 Agent 自主决策
Agent 收到消息后的自主行为(不是硬编码步骤):
1. **分析上下文**:读黑板 API 获取任务详情、依赖、历史讨论
2. **决定执行策略**
- 简单任务 → 自己直接做
- 复杂任务 → `sessions_spawn` 拆分 sub task
- 需要协作 → @mention 其他 Agent
3. **执行**:通过 subagent 或直接执行
4. **Review**:检查 sub 结果质量
5. **写回**:产出 + 状态 + handoff comment
### 4.3 庞统 Review 消息格式
```markdown
🔍 一轮结束,需要你的 review
**Parent Task**: {title}ID: {task_id}
**Project**: {project_id}
**Round**: {round_num}/{max_rounds}
**本轮状态**:
- 完成: {done} | 失败: {failed} | 取消: {cancelled}
- 总计: {total}
**三问**:
1. Goal 还清晰吗?(是否有 goal drift)
2. 成果物覆盖 goal 了吗?(逐条检查验收标准)
3. 下一轮需要做什么?(创建新 sub tasks / 标记完成 / 调整方向)
**你可以**:
- 创建新 sub task: curl -X POST http://localhost:8083/api/projects/{project_id}/tasks ...
- 调整 goal
- 标记 GOAL_ACHIEVED
回复 GOAL_ACHIEVED 表示完成,否则系统会等待你创建新 sub tasks。
```
---
## 五、Daemon 侧改动
### 5.1 Spawner 新增方法
```python
async def deliver_to_main(self, agent_id: str, message: str,
task_id: str = None,
on_complete: Callable = None) -> str:
"""投递到 Agent main session(不创建新 session
用 openclaw agent --agent <id> --message "..." 投递,
Gateway 自动路由到 main session。
"""
cmd = [
"openclaw", "agent",
"--agent", agent_id,
"--message", message,
"--json",
"--timeout", str(int(self.gateway_timeout)),
]
# ... 执行 + 监控,和 spawn_full_agent 类似但不传 --session-id
```
### 5.2 Ticker 改动
| 方法 | 改动 |
|------|------|
| `_broadcast_and_dispatch` | 调用 `deliver_to_main` 替代 `spawn_full_agent` |
| `_process_mentions` | 调用 `deliver_to_main` 替代 `spawn_full_agent` |
| `_spawn_pangtong_review` | 调用 `deliver_to_main` 替代 `spawn_full_agent` |
| `_check_round_complete` | 保留 `reviewing` 中间态逻辑 |
| `_handle_review_conclusion` | 保留,但改为从 main session 的回复中解析 |
### 5.3 Dispatcher 改动
确定性路由也改用 `deliver_to_main`
- 移除 `use_main_session` 参数(全部走 main session
- 移除 `new_session` 参数
- counter 逻辑简化(per main session 粒度)
### 5.4 废弃代码
- `spawn_full_agent``new_session` / `use_main_session` 参数标注废弃
- `gateway-fallback-*` session 的产生路径消失
- 续杯逻辑简化(`reuse_session_id` 不再需要)
---
## 六、Session 生命周期对比
### 现行
```
每次 spawn → 创建新 session → 执行 → 残留 → 手动清理
gateway-fallback-* 堆积
```
### 改为
```
main session(常驻,有完整上下文)
├── 收到任务 A → spawn sub(A1, cleanup: delete) → yield → review → 写回
├── 收到任务 B → spawn sub(B1, cleanup: delete) → yield → review → 写回
└── 收到 mention → 自主处理 → 写回
sub session(临时,完成即删)
├── sub(A1) → 执行 → 完成 → session 删除(transcript 保留)
└── sub(B1) → 执行 → 完成 → session 删除(transcript 保留)
```
**Session 数量**6 个 main session(常驻)+ 临时 sub session(用完即删)
**对比现行**6 个 main + N 个 `gateway-fallback-*`(无限增长)
---
## 七、收益与风险
### 收益
| 维度 | 改善 |
|------|------|
| **Session 数量** | 从无限增长 → 6 个 main + 临时 sub |
| **Agent 上下文** | 从无历史 → 完整身份 + 记忆 + workspace |
| **代码简化** | 移除 `new_session` / `use_main_session` / `reuse_session_id` 逻辑 |
| **Prompt 灵活度** | 从硬编码步骤 → Agent 自主决策 |
| **空 payloads** | 消除(main session 有完整生命周期管理) |
### 风险
| 风险 | 缓解 |
|------|------|
| **main session 上下文膨胀** | OpenClaw 自带的 compact 机制处理;sub session 不膨胀(用完删) |
| **main session 被阻塞** | `sessions_yield` 非阻塞,可被新消息打断;Gateway 排队机制保证不丢消息 |
| **sub 质量不可控** | main session review sub 结果;reviewing 中间态保证 round 不重复触发 |
| **Prompt 依赖** | Agent 需要正确的 prompt 来知道怎么 delegationsubagent-delegation skill 已调优 |
| **Gateway 投递延迟** | main session 排队是异步的,Daemon 不同步等结果;改为"投递后等黑板状态变化" |
---
## 八、待确认
| # | 问题 | 说明 |
|---|------|------|
| 1 | **Daemon 如何知道任务完成了?** | 现行用 `--json` 同步等结果。改用 main session 后,需要改为"投递后 ticker 轮询黑板状态变化" |
| 2 | **投递消息的 prompt 模板** | 需要和 v2.8 方向的 Prompt 进化对齐(身份+能力+约束 vs 固定步骤) |
| 3 | **on_complete 回调** | 改为基于黑板状态变化(status/review/done)触发,不再依赖 spawn 返回值 |
| 4 | **Mail 投递路径** | Mail 已经走 main session`use_main_session=is_mail`),无需改动 |
| 5 | **E2E 测试适配** | 现有 E2E 测试依赖 spawner 的 `--session-id UUID` 机制,需要适配 |
---
## 九、实施计划
### Phase 1spawner 新增 `deliver_to_main`
- 新增方法,不改动现有 `spawn_full_agent`
- 单元测试覆盖
### Phase 2Ticker 三条路径切换
- `_broadcast_and_dispatch``deliver_to_main`
- `_process_mentions``deliver_to_main`
- `_spawn_pangtong_review``deliver_to_main`
### Phase 3Daemon 结果收集改造
- 移除 `--json` 同步等结果
- 改为 ticker 轮询黑板状态变化
- on_complete 改为基于黑板事件触发
### Phase 4E2E 测试适配 + 验证
- 适配新的投递机制
- 验证 session 不爆炸
- 验证 Agent 自主决策行为
---
## 十、参考文件
| 文件 | 说明 |
|------|------|
| `01-four-phase-loop.md` | 四相循环设计(现行方案) |
| `v2.8-direction-notes.md` | v2.8/v2.9 方向(Prompt 进化 + Daemon 退化) |
| `subagent-delegation/SKILL.md` | Delegation skillAgent 侧执行指南) |
| `counter.py` | 并发控制(per session 粒度) |
| `spawner.py` `_do_retry` | 续杯机制 |