Files
sanguo_moziplus_v2/docs/design/agent-routing-redesign.md
T
2026-05-17 19:47:56 +08:00

16 KiB
Raw Blame History

Agent 路由机制重设计方案

版本: v1.0
作者: 庞统(副军师)🐦
日期: 2026-05-17
状态: 待确认
触发: E2E 测试暴露 review 阶段派错 Agent(张飞被派去审查自己),根因是 Daemon 硬编码路由


1. 问题诊断

1.1 当前实现

Ticker tick → dispatcher.decide(task, action_type) → 返回 agent_id → spawn

decide() 的逻辑:

  1. action_type 是机械检查 → Daemon 本地执行
  2. task.assignee 有值且已注册 → spawn 这个 agent直接用 assignee
  3. task.assignee 为空 → 查 capability_map → fallback 庞统

1.2 Bug 根因

任务生命周期中 assignee 只在 执行阶段 被设置(张飞 claim → assignee="zhangfei-dev")。

review 阶段ticker 调用 dispatcher.dispatch(task, action_type="review"),但 decide() 走 Level 2task.assignee="zhangfei-dev" 在注册列表中 → 又派给张飞。

1.3 更深层的问题

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

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

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


2. 调研发现

2.1 学术前沿

bMASBlackboard Multi-Agent System)— arXiv 2507.01701

核心机制Control UnitLLM 驱动)根据黑板当前内容动态选择下一轮该哪个 Agent 行动。

关键发现:

  • 不是固定 DAGControl Unit 根据黑板状态决定下一步
  • token 效率更高(智能路由不浪费在不相关的 Agent 上)
  • Agent 轮流行动 → 更新黑板 → Control Unit 判断 → 直到共识

自主选择模式(Self-Selection)— arXiv 2510.01285

核心发现任务不显式分配给 Agent。 相反,中央 Agent 把需求发布到黑板上,每个 Agent 自主决定是否参与

"Tasks are not explicitly assigned to helper agents; instead, each agent autonomously decides whether to participate based on its capabilities."

这是最 AI Native 的模式——不需要任何路由规则表。

MasRouterConfidence-Aware Routing)— arXiv 2601.04861

根据任务复杂度动态选择模型规模,引入 confidence 机制:

  • 简单任务 → 小模型
  • 复杂任务 → 大模型
  • 基于历史表现动态更新 Agent 可靠性评分

AgentGate — arXiv 2604.06696

结构化路由引擎,用 3B-7B 小模型做路由决策,candidate-aware 微调策略。验证了"路由本身也可以是 AI"的可行性。

2.2 生产实践

Microsoft Conductor2026.05

刚开源的确定性编排工具。核心思路:YAML 定义工作流,路由是确定性的

但它的定位是:当任务不是探索性的时(如 code review pipeline),确定性路由比 LLM 动态路由更可靠。关键洞察是:

  • 探索性任务 → LLM 编排(动态)
  • 确定性流程 → 声明式编排(YAML
  • 两者不是互斥的,而是分层混合

AWS 动态分派模式

事件驱动架构 + 动态分派:LLM 调用变成智能路由的、上下文感知的事件。

Azure Agent Orchestration Patterns

五种模式:顺序、并发、群聊、交接(Handoff)、Magentic。

  • Handoff 模式Agent 完成自己的部分后,自己决定交接给谁
  • 关键:控制权从一个 Agent 转移到另一个,不是中央调度

2.3 已有调研报告中的线索

来源 关键洞察
shared-consciousness-research.md Control Unit 是 LLM 驱动的,不是规则路由;Agent 能力画像是关键
v2.6-research-01 Hermes 不信任 Agent 完成声明(系统级保护);Claude Code Lead 主动协调
v2.6-research-02 事件驱动:complete→auto-unlock 是核心模式
architecture-v2.6.md "Agent 决策,Daemon 执行"Daemon 是投递员不是决策者

3. 设计原则

从调研中提炼出三个核心原则:

P1: 路由决策在 Agent 层,不在 Daemon 层

Daemon 只做"投递"——读黑板、spawn Agent、清理 session。"谁该做这个任务"的决策由 Agent 自己或由黑板上的声明式数据驱动。

P2: Agent 通过黑板声明自己的能力和意图

不是 Daemon 维护一个 capability_map,而是 Agent 自己在黑板上注册能力画像。Daemon 查黑板找到匹配的 Agent。

P3: 执行者声明下一步需要什么

执行阶段的 Agent 完成任务后,在提交产出时声明"下一步需要什么能力"。Daemon 读这个声明,找到匹配的 Agent,spawn 它。


4. 方案设计

4.1 核心机制:Agent 能力画像 + 声明式路由

机制一:Agent 能力画像(Agent Profile

每个 Agent 在黑板上注册自己的能力画像(不是 Daemon 硬编码):

# 存储在黑板的 agents 表或独立 agent_profiles 表
zhangfei-dev:
  capabilities: [coding, implementation, scripting]
  can_review: false        # 张飞不做审查
  max_concurrent: 1
  performance_score: 0.85  # 基于历史表现的动态评分

simayi-challenger:
  capabilities: [review, quality_check, debate]
  can_review: true         # 司马懿专门做审查
  max_concurrent: 2
  performance_score: 0.92

pangtong-fujunshi:
  capabilities: [planning, coordination, escalation, strategy]
  can_review: true
  is_fallback: true        # 庞统是最终兜底
  max_concurrent: 3
  performance_score: 0.90

关键:能力画像是声明式的、可演进的。Agent 的 SOUL.md/IDENTITY.md 中就定义了自己的能力。Daemon 启动时读取 Agent 配置,写入黑板。

机制二:任务生命周期的声明式流转

任务的 status 字段仍然驱动状态机,但每个状态需要什么能力由黑板上的元数据声明,不是 Daemon 硬编码:

# 任务的 metadata 字段存储生命周期声明
# 创建时由创建者(用户或庞统)或默认模板设置
TASK_LIFECYCLE = {
    "pending": {
        "needs": "execution",      # pending 阶段需要 execution 能力
        "capability": "auto",      # 从 task_type 推断,或显式声明
    },
    "review": {
        "needs": "review",         # review 阶段需要 review 能力
        "capability": "review",    # 固定查 review 能力的 Agent
        "exclude_assignee": True,  # 排除执行者(不能自己审自己)
    },
    "failed": {
        "needs": "escalation",     # 失败后需要升级能力
        "capability": "escalation",
    }
}

这不是模板! 这是任务生命周期本身固有的语义。区别在于:

  • 模板(v1.0:预先定义完整的 DAG 流程,每个节点固定
  • 声明式流转(本方案):只声明每个状态需要什么能力,具体谁来由能力画像动态匹配

机制三:执行者声明下一步

Agent 在完成产出提交时,可以声明下一步需要什么:

// Agent 调用 POST /api/projects/{pid}/tasks/{id}/status 时
{
  "status": "review",
  "agent": "zhangfei-dev",
  "next_capability": "review",      // 声明下一步需要 review 能力
  "handoff_note": "代码已实现,请审查质量和安全性"
}

Daemon 读 next_capability,在 Agent 能力画像中找到匹配的 Agent(且排除当前 assignee),spawn 它。

如果不声明 next_capabilityDaemon 从 TASK_LIFECYCLE[status].needs 推断。

4.2 Daemon 路由逻辑重写

class Dispatcher:
    """Agent 路由器 — 基于能力画像的声明式路由"""
    
    def decide(self, task: Task, action_type: str = "") -> dict:
        # Level 1: 纯机械检查 → Daemon 本地执行(不变)
        if action_type in self.LOCAL_ACTIONS:
            return {"level": DispatchLevel.LOCAL, ...}
        
        # Level 2: 基于能力画像的路由(替代原来的 assignee 硬编码)
        needed_capability = self._resolve_needed_capability(task, action_type)
        exclude = self._get_exclusions(task, action_type)
        agent_id = self._find_agent_by_capability(
            needed_capability, 
            exclude_agents=exclude
        )
        
        if agent_id:
            return {
                "level": DispatchLevel.FULL_AGENT,
                "agent_id": agent_id,
                "reason": f"Matched capability '{needed_capability}' → {agent_id}",
            }
        
        # Level 3: 无匹配 → 庞统兜底
        return {
            "level": DispatchLevel.FULL_AGENT,
            "agent_id": "pangtong-fujunshi",
            "reason": "No agent matched capability, fallback to coordinator",
        }

    def _resolve_needed_capability(self, task: Task, action_type: str) -> str:
        """推断当前任务阶段需要什么能力"""
        
        # 1. 优先看 Agent 声明的 next_capability(黑板上的 handoff_note
        if task.next_capability:
            return task.next_capability
        
        # 2. 看任务当前状态对应的生命周期需求
        lifecycle = TASK_LIFECYCLE.get(task.status)
        if lifecycle:
            return lifecycle["capability"]
        
        # 3. 看任务类型(fallback
        return self._infer_from_task_type(task.task_type)

    def _get_exclusions(self, task: Task, action_type: str) -> set:
        """获取需要排除的 Agent"""
        exclude = set()
        lifecycle = TASK_LIFECYCLE.get(task.status, {})
        
        # review 阶段排除执行者(不能自己审自己)
        if lifecycle.get("exclude_assignee") and task.assignee:
            exclude.add(task.assignee)
        
        return exclude

    def _find_agent_by_capability(self, capability: str, 
                                   exclude_agents: set = None) -> str | None:
        """从 Agent 能力画像中找到匹配的 Agent"""
        candidates = []
        for agent_id, profile in self.agent_profiles.items():
            if agent_id in (exclude_agents or set()):
                continue
            if capability in profile.get("capabilities", []):
                candidates.append(agent_id)
        
        if not candidates:
            return None
        
        # 多候选时:选负载最低的
        if len(candidates) > 1:
            return min(candidates, 
                       key=lambda a: self.counter._active.get(a, 0))
        
        return candidates[0]

4.3 assignee 字段语义变更

当前:assignee 是"负责人"(整个任务的),一旦设置就贯穿全生命周期。

改为assignee 是"当前阶段的执行者",每次状态流转时更新。

# 状态流转时自动更新 assignee
def transition_status(task_id, new_status, agent):
    # ...
    if lifecycle.get("exclude_assignee"):
        # review 阶段:assignee 改为审查者
        old_assignee = task.assignee  # 保存执行者信息
        task.previous_assignee = old_assignee  # 新增字段
        task.assignee = new_agent_id  # 设为审查者

4.4 和 v2.6 架构的对齐

v2.6 原则 本方案实现
Agent 决策,Daemon 执行 路由决策基于 Agent 的能力画像(Agent 声明的能力),Daemon 只做匹配
Daemon 是投递员不是决策者 Daemon 不做"谁该做什么"的价值判断,只做能力匹配
编排是 AI agent 自主领活 Agent 自己声明能力、声明下一步需要什么能力
黑板是唯一真相源 能力画像、任务生命周期声明都在黑板上

4.5 和模板机制的本质区别

维度 v1.0 模板 当前 capability_map 本方案
路由定义位置 模板 YAML Daemon config YAML 黑板(Agent 能力画像)
谁定义能力 用户/开发者 开发者 Agent 自己SOUL.md → 黑板)
每个阶段谁做 模板固定 config 硬编码 声明式匹配 + 排除规则
可扩展性 加模板 改代码 Agent 注册即可
AI Native 程度 中高Agent 自声明)

4.6 演进路线

本方案是务实的第一步。它不是最终的 AI Native 终极形态,而是从"Daemon 硬编码"到"Agent 自主领活"之间的关键跳板

当前: Daemon if-else 硬编码
  ↓ 本方案
第一步: Agent 能力画像 + 声明式路由(Daemon 做能力匹配)
  ↓ 未来
第二步: Agent 自主领活(Daemon 只广播,Agent 自己 claim
  ↓ 更远
第三步: bMAS Control UnitLLM 驱动的动态选择)

第一步到第二步的迁移成本很低——能力画像和声明式路由机制不变,只是把"Daemon 查找匹配 → 派发"变成"Daemon 广播需求 → Agent 自己 claim"。这是同一个数据结构的两种消费方式。


5. 具体改动清单

5.1 数据模型变更

变更 说明
新增 agent_profiles 表(或用 agents 表扩展) 存储 Agent 能力画像
tasks 表新增 next_capability 字段 Agent 声明下一步需要的能力
tasks 表新增 previous_assignee 字段 状态流转时保存前一阶段执行者
assignee 语义变更 从"任务负责人"改为"当前阶段执行者"

5.2 代码变更

文件 变更
dispatcher.py 重写 decide():能力匹配替代 assignee 查表
dispatcher.py 新增 _resolve_needed_capability()_find_agent_by_capability()_get_exclusions()
config/default.yaml capability_map 改为 agent_profiles(每个 Agent 声明自己的能力列表)
blackboard_routes.py status API 接受 next_capability 参数
ticker.py _dispatch_reviews() 使用新的 dispatcher 路由
blackboard/db.py 新增 agent_profiles 表 / 字段

5.3 不变的部分

不变 原因
态机(pending→claimed→working→review→done 状态流转语义正确
前端 Dashboard 前端不感知路由逻辑
Agent prompt 模板(S2 Agent 仍然按 4 步流程执行
Spawner 逻辑 spawn 机制不变
API 契约(S1 对 Agent 透明

6. 和现有优秀实践的对标

实践 本方案对应
bMAS Control UnitLLM 驱动) 本方案用能力画像做结构化匹配(成本更低、确定性更高),未来可演进为 LLM 驱动
自主选择模式(arXiv 2510.01285 本方案的演进方向:Agent 自主 claim 而非被指派
Handoff 模式(Azure Agent 声明 next_capability 就是 Handoff
声明式编排(Conductor 生命周期声明 TASK_LIFECYCLE 是声明式的
能力画像(OpenClaw RFC #35203 agent_profiles 直接实现能力画像
幻觉门控(Hermes 不变,产出验证逻辑独立于路由

7. 待确认

  1. agent_profiles 数据来源:从 config/default.yaml 读取(启动时写入黑板),还是从 Agent 的 SOUL.md 动态解析?
  2. TASK_LIFECYCLE 定义位置:硬编码在 dispatcher.py 中,还是也放到 config
  3. assignee 语义变更的影响:前端 Dashboard 是否有依赖 assignee = 执行者的假设?
  4. 是否要一步到位到"Agent 自主领活"(第二步),还是先实现本方案(第一步)?

8. 参考

  • bMAS: arXiv 2507.01701 — Blackboard LLM Multi-Agent System
  • Self-Selection: arXiv 2510.01285 — Agent 自主选择模式
  • MasRouter: arXiv 2601.04861 — Confidence-Aware Routing
  • Microsoft Conductor: github.com/microsoft/conductor — 确定性编排
  • Azure Agent Patterns: learn.microsoft.com — Handoff 模式
  • OpenClaw RFC #35203 — Capability Profiling + Shared Blackboard
  • v2.6 调研报告: docs/research/shared-consciousness-research.md
  • v2.6 架构设计: docs/design/architecture-v2.6.md