# Agent 路由机制重设计方案

**版本**: v2.0  
**作者**: 庞统（副军师）🐦  
**日期**: 2026-05-17  
**状态**: 待评审  
**触发**: E2E 测试暴露 review 阶段派错 Agent（张飞被派去审查自己），根因是 Daemon 硬编码路由  
**评审**: 司马懿

---

## 1. 问题诊断

### 1.1 Bug 根因

任务生命周期中 `assignee` 只在执行阶段被设置（张飞 claim → assignee="zhangfei-dev"）。到 review 阶段，`decide()` 走 Level 2：`task.assignee` 在注册列表中 → 又派给张飞。

### 1.2 更深层的问题

**Daemon 在做 AI 该做的决策。** v2.6 架构明确定义：

| 维度 | v2.6 设计目标 | 当前实现 |
|------|-------------|---------|
| 决策者 | Agent（在黑板上自主决策） | Daemon（if-else 硬编码） |
| Daemon 角色 | 投递员（执行黑板上的决策） | 调度器（决定谁干什么） |
| 编排方式 | AI agent 在黑板上自主领活 | 配置表驱动（非 AI 判断） |

T3-10 设计原文写着"配置表驱动非 AI 判断"——与 v2.6 核心原则矛盾。

---

## 2. 调研发现

### 2.1 学术前沿

| 来源 | 核心发现 | 对我们的价值 |
|------|---------|-------------|
| **bMAS** arXiv 2507.01701 | Control Unit（LLM 驱动）根据黑板当前内容动态选择 Agent | 路由本身可以是 LLM 调用，不是 if-else |
| **Self-Selection** arXiv 2510.01285 | 任务不显式分配，Agent 根据自己能力自主决定是否参与 | 最 AI Native 的模式，我们的演进目标 |
| **MasRouter** arXiv 2601.04861 | 根据任务复杂度动态选模型规模 + confidence 机制 | confidence 阈值 + 历史表现动态评分 |
| **AgentGate** arXiv 2604.06696 | 3B-7B 小模型做结构化路由决策 | 验证"路由可以是 AI"的可行性 |

### 2.2 生产实践

| 项目 | 模式 | 启发 |
|------|------|------|
| **Microsoft Conductor**（2026.05 开源） | YAML 确定性编排 | 确定性流程 + LLM 动态路由分层混合 |
| **Azure Agent Patterns** | 5 种模式：顺序/并发/群聊/**Handoff**/Magentic | **Handoff**：Agent 完成后自己决定交接给谁 |
| **AWS 动态分派** | 事件驱动 + 上下文感知路由 | 路由变成事件，不是轮询 |
| **Claude Code Agent Teams** | Lead coordinator + context 隔离 | Lead 做分解+分配+监控，subagent 只拿相关 context |

### 2.3 已有调研的线索

- architecture-v2.6.md：**"Agent 决策，Daemon 执行"**；Daemon 是投递员不是决策者
- shared-consciousness-research.md：Control Unit 是 LLM 驱动的，不是规则路由
- v2.6-research-01：Hermes 幻觉门控——不信任 Agent 完成声明

---

## 3. 设计原则

| # | 原则 | 说明 |
|---|------|------|
| P1 | 路由决策在 Agent 层，不在 Daemon 层 | "谁该做这个任务"由 Agent 自己或 LLM 决定，Daemon 只执行 |
| P2 | 当前 Agent 最清楚下一步需要谁 | 刚做完工作的人最清楚该交接给谁（Azure Handoff） |
| P3 | 路由可审计 | 每次路由决策记录到黑板，可回溯 |

---

## 4. 三种路由模式

### 4.1 模式总览

```
┌───────────────────────────────────────────────────────────┐
│                     路由决策入口                            │
│               Dispatcher.decide(task)                      │
└────────┬──────────────┬──────────────────┬────────────────┘
         │              │                  │
    ┌────▼────┐   ┌────▼─────┐   ┌────────▼────────┐
    │ Mode A  │   │ Mode B   │   │    Mode C       │
    │ LLM路由  │   │ Agent交接 │   │  Agent自主领活   │
    │(中心化)  │   │(去中心化) │   │   (去中心化)     │
    └────┬────┘   └────┬─────┘   └────────┬────────┘
         │              │                  │
    LLM选Agent     执行者说需要谁     Agent自己来领
```

### 4.2 Mode A：LLM 路由（中心化）

**场景**：首次分配（pending → claimed）、异常升级（failed/blocked）、无明确 handoff 指令时。

**机制**：Daemon 调用一次轻量 LLM API，传入任务信息 + Agent 能力画像 + 负载状态，LLM 返回选择的 Agent + 理由 + 置信度。

**关键**：不是 spawn 一个 Agent session，是一次 ~300 token 的 API 调用（~1-2s，<¥0.01）。

```
输入: 任务描述 + 6个Agent画像 + 负载
输出: {"agent_id": "xxx", "reason": "...", "confidence": 0.9}
约束: ~200 token response, temperature=0.1
```

### 4.3 Mode B：Agent 声明式交接（去中心化）⭐ 最高频

**场景**：Agent 完成当前阶段后，明确声明下一步需要什么。

**机制**：Agent 在 POST /status 时附带 `next_capability` 字段：

```json
{
  "status": "review",
  "agent": "zhangfei-dev",
  "next_capability": "review",
  "handoff_note": "代码已实现，请审查质量和安全性"
}
```

Daemon 读 `next_capability`，查 Agent 能力画像找到匹配者（排除当前执行者），直接 spawn。

**这是最 AI Native 的模式**——刚做完工作的人最清楚下一步需要谁。不需要 LLM 调用，0ms 延迟。

### 4.4 Mode C：Agent 自主领活（去中心化）— 未来演进

**场景**：Daemon 广播任务需求，Agent 自己决定是否 claim。

**当前阶段不实现**，保留演进空间。数据结构（agent_profiles、capabilities）不变，只需把"Daemon 查表派发"改为"Daemon 广播 + Agent 自己 claim"。

### 4.5 模式选择逻辑

```python
def decide(self, task, action_type=""):
    # 确定性快速路径（0ms，不调 LLM）
    if self._is_deterministic(task, action_type):
        return self._deterministic_route(task, action_type)
    
    # Mode B: Agent 声明了 next_capability → 直接匹配
    if task.next_capability:
        return self._match_capability(task.next_capability, 
                                       exclude={task.assignee})
    
    # Mode A: 无明确 handoff → LLM 路由
    return self._llm_route(task, action_type)
```

**确定性快速路径**包括：
- 机械检查（L1_guardrail、format_check）→ Daemon 本地执行
- 已有 assignee 且非生命周期流转（如 crashed → retry 同一人）→ 直接用

---

## 5. 核心组件设计

### 5.1 Agent 能力画像（Agent Profile）

每个 Agent 在配置中声明自己的能力（**不是 Daemon 硬编码**）：

```yaml
# config/default.yaml → agents 段扩展
agents:
  zhangfei-dev:
    capabilities: [coding, implementation, scripting]
    can_review: false
    max_concurrent: 1
    
  simayi-challenger:
    capabilities: [review, quality_check, debate]
    can_review: true
    max_concurrent: 2
    
  guanyu-dev:
    capabilities: [risk, compliance, position_check]
    can_review: true
    max_concurrent: 1
    
  zhaoyun-data:
    capabilities: [data, acquisition, cleaning, verification]
    can_review: false
    max_concurrent: 1
    
  jiangwei-infra:
    capabilities: [deploy, infrastructure, docker, vnpy]
    can_review: false
    max_concurrent: 1
    
  pangtong-fujunshi:
    capabilities: [planning, coordination, escalation, strategy]
    can_review: true
    is_fallback: true
    max_concurrent: 3
```

Daemon 启动时读取配置，写入黑板 `agent_profiles` 表。未来可演进为 Agent 自己注册。

### 5.2 LLM 路由器（LLMDriver）

```python
class LLMDriver:
    """bMAS Control Unit — 轻量 LLM 路由决策"""
    
    def __init__(self, model: str, api_base: str, api_key: str):
        self.model = model
        self.client = OpenAI(base_url=api_base, api_key=api_key)
    
    def route(self, task, agent_profiles, active_agents) -> RouteDecision:
        prompt = self._build_prompt(task, agent_profiles, active_agents)
        
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[{"role": "user", "content": prompt}],
            response_format={"type": "json_object"},
            max_tokens=200,
            temperature=0.1,
        )
        
        result = json.loads(response.choices[0].message.content)
        return RouteDecision(
            agent_id=result["agent_id"],
            reason=result["reason"],
            confidence=result.get("confidence", 0.5),
        )
```

**Routing Prompt 模板**：

```
你是任务路由器。根据任务需求和 Agent 能力，选择最合适的 Agent。

## 当前任务
- ID: {task_id}
- 标题: {task_title}  
- 状态: {task_status}
- 描述: {task_description}
- 上一步执行者: {previous_assignee}

## 可用 Agent
{每个Agent: ID, 能力列表, 当前负载}

## 约束
1. review/quality_check 不能选上一步执行者
2. 同等能力优先选负载最低的
3. 必须匹配任务所需能力

## 输出
{"agent_id": "...", "reason": "...", "confidence": 0.0-1.0}
```

### 5.3 Dispatcher 重写

```python
class Dispatcher:
    def __init__(self, config, counter):
        self.counter = counter
        self.agent_profiles = config.get("agent_profiles", {})
        self.llm = LLMDriver(
            model=config.get("routing", {}).get("model", "zhipu/glm-5.1"),
            api_base=config.get("routing", {}).get("api_base", ""),
            api_key=config.get("routing", {}).get("api_key", ""),
        )
        self.LOCAL_ACTIONS = {"L1_guardrail", "format_check", "file_exists_check"}
    
    def decide(self, task, action_type="") -> dict:
        # ── 快速路径：确定性路由 ──
        if action_type in self.LOCAL_ACTIONS:
            return {"level": "local", "reason": "机械检查，Daemon本地执行"}
        
        # retry 同一人
        if action_type == "retry" and task.assignee:
            return {"level": "full_agent", "agent_id": task.assignee,
                    "reason": "retry原执行者", "mode": "deterministic"}
        
        # ── Mode B: Agent 声明式交接 ──
        if task.next_capability:
            agent = self._match_capability(task.next_capability, 
                                            exclude={task.assignee})
            if agent:
                return {"level": "full_agent", "agent_id": agent,
                        "reason": f"执行者handoff: 需要{task.next_capability}",
                        "mode": "agent_handoff"}
        
        # ── Mode A: LLM 路由 ──
        decision = self.llm.route(task, self.agent_profiles, 
                                   self.counter.active_agents)
        
        # 合法性校验
        if (decision.agent_id not in self.agent_profiles 
            or decision.confidence < 0.7):
            return {"level": "full_agent", "agent_id": "pangtong-fujunshi",
                    "reason": f"LLM低置信度({decision.confidence}): {decision.reason}",
                    "mode": "fallback"}
        
        return {"level": "full_agent", "agent_id": decision.agent_id,
                "reason": decision.reason, "mode": "llm_route",
                "confidence": decision.confidence}
    
    def _match_capability(self, capability, exclude=None):
        """从能力画像中匹配 Agent"""
        candidates = [
            aid for aid, prof in self.agent_profiles.items()
            if aid not in (exclude or set())
            and capability in prof.get("capabilities", [])
        ]
        if not candidates:
            return None
        if len(candidates) == 1:
            return candidates[0]
        return min(candidates, key=lambda a: self.counter.active_agents.get(a, 0))
```

### 5.4 assignee 语义变更

| 维度 | 当前 | 改为 |
|------|------|------|
| `assignee` 含义 | 任务负责人（贯穿全生命周期） | **当前阶段执行者**（随状态流转更新） |
| 新增 `previous_assignee` | 无 | 保存前一阶段执行者（用于排除和审计） |

```python
# 状态流转时更新
task.previous_assignee = task.assignee
task.assignee = new_agent_id
```

---

## 6. 路由审计

每次路由决策写入黑板 `routing_decisions` 表。

### 6.1 表结构

```sql
CREATE TABLE IF NOT EXISTS routing_decisions (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    task_id TEXT NOT NULL,
    from_status TEXT,           -- 前一状态
    to_status TEXT,             -- 目标状态
    mode TEXT NOT NULL,         -- deterministic / agent_handoff / llm_route / fallback
    selected_agent TEXT NOT NULL,
    previous_agent TEXT,        -- 前一阶段执行者
    reason TEXT,                -- 路由理由
    confidence REAL,            -- LLM 置信度（Mode A 才有）
    model TEXT,                 -- 使用的 LLM 模型（Mode A 才有）
    latency_ms INTEGER,         -- 路由耗时
    created_at TEXT DEFAULT (datetime('now')),
    FOREIGN KEY (task_id) REFERENCES tasks(id)
);

CREATE INDEX idx_routing_task ON routing_decisions(task_id);
```

### 6.2 审计日志示例

```
task=test-e2e-001 | pending→claimed | mode=llm_route
  → zhangfei-dev (confidence=0.95, reason="编码任务匹配coding能力")
  → model=zhipu/glm-5.1, latency=1200ms

task=test-e2e-001 | working→review | mode=agent_handoff
  → simayi-challenger (reason="执行者handoff: 需要review")
  → latency=2ms

task=test-e2e-001 | review→done | mode=agent_handoff  
  → pangtong-fujunshi (reason="审查通过，交接给协调者收尾")
  → latency=1ms
```

---

## 7. 路由模型配置

### 7.1 后端配置

```yaml
# config/default.yaml 新增
routing:
  model: "zhipu/glm-5.1"     # 默认路由模型
  api_base: ""                # 空=用 OpenClaw Gateway
  api_key: ""                 # 空=用 OpenClaw 默认
  confidence_threshold: 0.7   # 低于此值 fallback
  max_tokens: 200
  temperature: 0.1
```

### 7.2 前端配置入口

在现有 `ModelConfig.tsx` 页面顶部新增"路由模型"配置区域：

```
┌─────────────────────────────────────────────┐
│ 🎯 路由模型（Control Unit）                  │
│ ┌─────────────────────┐ ┌────┐              │
│ │ zhipu/glm-5.1     ▾ │ │应用│              │
│ └─────────────────────┘ └────┘              │
│ 任务路由使用的 LLM（推荐轻量快速模型）         │
├─────────────────────────────────────────────┤
│ 🐦 庞统  pangtong-fujunshi                  │
│ 当前: zhipu/glm-5.1                         │
│ ...                                         │
```

- 模型下拉列表复用 OpenClaw 已注册的 `knownModels`（和 Agent 模型选的是同一个数据源）
- 通过后端 API `PATCH /api/config/routing-model` 保存
- 调用 `api.setModel` 同理，走 Gateway 模型配置

### 7.3 API

```python
# blackboard_routes.py 新增
@api_route("GET", "/api/config/routing")
def get_routing_config(request):
    return {"model": config.routing.model, 
            "confidence_threshold": config.routing.confidence_threshold}

@api_route("PATCH", "/api/config/routing")  
def set_routing_config(request):
    new_model = request.json.get("model")
    # 校验模型在 OpenClaw 已注册模型列表中
    config.routing.model = new_model
    config.save()
    return {"ok": True}
```

---

## 8. 改动清单

### 8.1 数据模型

| 变更 | 类型 | 说明 |
|------|------|------|
| 新增 `agent_profiles` 配置段 | 配置 | 每个 Agent 声明能力列表 |
| 新增 `routing` 配置段 | 配置 | 路由模型 + 参数 |
| tasks 新增 `next_capability` 字段 | DDL | Agent 声明下一步需要的能力 |
| tasks 新增 `previous_assignee` 字段 | DDL | 保存前一阶段执行者 |
| 新增 `routing_decisions` 表 | DDL | 路由审计日志 |
| `assignee` 语义变更 | 逻辑 | 从"任务负责人"改为"当前阶段执行者" |

### 8.2 代码

| 文件 | 变更 |
|------|------|
| `dispatcher.py` | 重写：新增 LLMDriver + Mode A/B/C 路由逻辑 |
| `config/default.yaml` | 新增 `agent_profiles` + `routing` 配置段 |
| `blackboard_routes.py` | status API 接受 `next_capability`；新增路由配置 API |
| `ticker.py` | 使用新 dispatcher；路由结果写 routing_decisions |
| `blackboard/db.py` | 新增 routing_decisions 表 DDL；tasks 表新增字段 |
| `ModelConfig.tsx` | 新增路由模型配置区域 |

### 8.3 不变

| 不变 | 原因 |
|------|------|
| 状态机（pending→claimed→working→review→done） | 状态流转语义正确 |
| Agent prompt 模板（S2） | Agent 仍按 4 步流程，只在 POST /status 时多传一个字段 |
| Spawner 逻辑 | spawn 机制不变 |
| 前端 Dashboard 核心布局 | 只在 ModelConfig 加一个区域 |

---

## 9. 和现有实践的对标

| 实践 | 本方案对应 |
|------|----------|
| bMAS Control Unit（LLM 驱动） | Mode A: LLMDriver 实现，轻量 API 调用 |
| Azure Handoff（Agent 交接） | Mode B: next_capability + handoff_note |
| 自主选择（arXiv 2510.01285） | Mode C: 未来演进，数据结构预留 |
| MasRouter（confidence） | confidence 阈值 + fallback 机制 |
| Microsoft Conductor（确定性 + 动态混合） | 快速路径（确定性）+ LLM 路由（动态）分层 |
| 幻觉门控（Hermes） | LLM 输出合法性校验 + confidence 阈值 |
| "Agent 决策，Daemon 执行"（v2.6 原则） | Mode B 是最直接的实现：Agent 自己决定交接给谁 |

---

## 10. 演进路线

```
Phase 1（本次实现）: Mode A + Mode B
  - LLMDriver 路由（首次分配、异常场景）
  - Agent 声明式交接（最高频场景）
  - 路由审计表
  - 前端路由模型配置
  
Phase 2（未来）: Mode C
  - 同样的 agent_profiles 和 capabilities 数据结构
  - Daemon 广播需求 → Agent 自己 claim
  - 迁移成本极低（数据结构不变，只改消费方式）
  
Phase 3（更远）: 经验驱动的路由
  - 路由审计数据反哺 LLM prompt（历史匹配成功率）
  - Agent 可靠性评分（参考 MasRouter）
  - 动态能力发现（Agent 完成新类型任务后自动更新画像）
```

---

## 11. 司马懿评审要点

请重点关注：

1. **LLMDriver 的异常处理**：API 超时/失败时的 fallback 策略是否合理
2. **Mode B 的安全性**：Agent 声明 `next_capability` 时是否需要校验（防恶意指定）
3. **assignee 语义变更**的影响范围：是否有其他模块依赖"assignee = 任务负责人"
4. **routing_decisions 表设计**：字段是否充分，索引是否合理
5. **配置 API 的安全性**：修改路由模型是否需要鉴权
6. **性能影响**：Mode A 的 ~2s 延迟在 tick cycle 中是否可接受

---

## 12. 参考

- bMAS: arXiv 2507.01701 — Blackboard LLM Multi-Agent System
- Self-Selection: arXiv 2510.01285 — Agent 自主选择模式
- MasRouter: arXiv 2601.04861 — Confidence-Aware Routing
- AgentGate: arXiv 2604.06696 — 结构化路由引擎
- Microsoft Conductor: github.com/microsoft/conductor — 确定性编排
- Azure Agent Patterns: learn.microsoft.com — Handoff 模式
- v2.6 调研报告: docs/research/shared-consciousness-research.md
- v2.6 架构设计: docs/design/architecture-v2.6.md
- T3-10 调度判据: docs/design/topic3-challenge-review-proposal.md §5.4
