Skip to main content
Glama

mcp-multi-agent-dispatch

MCP 工具层 + LangGraph 三代理编排:调度员说一句话,系统查队列、选机器人、算路径、派单、上报故障,写操作走人工审批,重复提交走幂等。

不是"多代理聊天演示":13 条端到端用例全部走真 MCP 协议(in-memory transport)和真 LangGraph 状态机,覆盖越权、状态冲突、信息不足、重复提交四类边界。

指标

实测

端到端用例通过率

13/13

平均步数(每条指令)

3.69

工具调用总数(13 条用例)

58 次(执行 13 + 只读 45)

越权拦截

2/2(viewer 写操作、跨仓查询)

澄清正确率

1/1(缺任务号时不猜)

MCP 工具数

6(4 只读 + 2 写)

提案采纳率

13/13 = 1.00

执行阶段重试倍数

1.00x(没有一次动用 fallback)

决策读放大

3.46 次只读调用 / 每个写提案

停滞断路器触发次数

0(当前图是 DAG,本来就不会触发)

Agent 级指标:正例和负例必须分开算

代理

提案数

正例成功

负例正确拒绝

AssignAgent

7(正 2 / 负 5)

1.00

1.00

CollabAgent

5(正 4 / 负 1)

1.00

1.00

PathAgent

1(正 1 / 负 0)

1.00

—

如果把正负例混在一起算"工具成功率",AssignAgent 会掉到 0.17——看上去像这个代理基本不工作。真实原因是 5 条负例(低电量、任务已被占用、越权)本来就该被工具拒绝,拒绝是正确行为。

这也解释了为什么没做 token 成本与通信效率:这个项目里 planner 是正则规则、三个代理是顺序函数调用,既没有 LLM 调用也没有 agent 间消息。那两项指标在这里没有定义,硬凑出来就是编的。

跑起来

pip install -e .

python -m dispatch_agent.cli tools                       # 列出 6 个 MCP 工具
python -m dispatch_agent.cli run "查一下 W1 排队的任务" --user U001
python -m dispatch_agent.cli run "把任务 T2002 派给机器人" --user U001
python -m dispatch_agent.cli run "R102 一级故障,马上处理" --user U002 --manual   # 走人工审批
python scripts/eval_graph.py                             # 13 条端到端评测

--transport direct 可绕过 MCP 直调工具实现,只用于排查是"协议问题"还是"逻辑问题"。

要求 Python ≥ 3.11。人工审批那条路径(--manual)用 interrupt() 实现,它靠 ContextVar 取 thread_id; 把 context 传进工作线程的 run_coroutine_threadsafe(context=...) 是 3.11 才有的参数,3.10 上会在审批时抛 Called get_config outside of a runnable context。同步节点不受影响,所以症状是"别的都能跑,只有 --manual 挂"—— 这是解释器版本问题,不是图写错了。原因与验证过程见 docs/design.md §3。

Related MCP server: Codex Control Plane MCP

架构

用户指令
   │
   ▼
┌──────────────┐   规则化解析:intent + slots + 缺失项
│  understand  │   缺参数 → clarify(不猜)
└──────┬───────┘
       ▼
┌──────────────┐   AssignAgent(单任务选车)
│   propose    │   PathAgent(只读路径)
└──────┬───────┘   CollabAgent(批量/异常)
       ▼
┌──────────────┐   只有 write=True 的动作进审批
│    review    │   interrupt + checkpointer,断点可恢复
└──────┬───────┘
       ▼
┌──────────────┐   失败按 fallbacks 换候选;步数上限 8;动作指纹去重
│   execute    │
└──────┬───────┘
       ▼
    finalize  →  中文答复 + 每个工具的执行码

   工具层(MCP):query_tasks / list_robots / get_battery / plan_path
                  assign_task / report_fault
                  鉴权 → 幂等 → 资源存在 → 业务规则 → 执行

三个关键设计

① 授权在工具层,不在 Agent 层。 Agent 可以"提议"任意操作,但工具自己决定执不执行。模型被诱导、Agent 写错逻辑,都越不过这条线。这也是为什么 authorize() 在 _assign_task 的第一行,而不是在 graph 里。

② 写操作与只读操作共用一条图,只有写操作进审批。 只读(查队列、算路径)直接执行,写(派单、报障)必须过 review。判断依据是 Action.write,不是"哪个 agent 调的"——同一个 agent 可能既查又写。 审批用 interrupt() + checkpointer:暂停时整个 state 被序列化进 checkpoint,恢复时从断点继续、不重新执行 propose。 但要分清机制和配置——demo 配的是 MemorySaver,它活在进程内存里,真的重启进程状态还是丢。所以准确说法是: 「机制上支持断点恢复;demo 的 checkpointer 是内存实现,跨进程要换 Postgres checkpointer」。这就是"能续跑"和"假装能续跑"的差别所在。

③ 防死循环靠三道,但当前这张图其实用不上。

防护

防什么

什么时候才真的生效

步数上限 MAX_STEPS = 8

停不下来

加了回边之后

动作指纹去重(tool + sorted(args))

两个动作来回跳

同上

停滞断路器(连续 2 次工具失败即停手)

每轮参数都不同但一直在错

一个请求产生多个写动作时

说清楚一件事:当前图是 DAG(understand → propose → review → execute → finalize),没有回边,一个请求最多产生一个写动作,所以这三道在 13 条用例下一次都没触发——stall_events = 0 是正确结果,不是没实现。它们是给"加反思回边"预留的保险丝。

诚实一点讲这件事比硬说"我做了防死循环"更值:能用代码证明它有效的前提,是先说清它什么时候才需要生效。

业务规则(都在 store.py,改这里不影响编排)

规则

值

拒绝码

派单最低电量

20%

BATTERY_TOO_LOW

电池健康度下限

80%

ROBOT_FAULT

充电提醒阈值

25%

(只提示,不拦截)

同一巷道作业上限

2 台

LANE_CAPACITY_EXCEEDED

图搜索步数上限

8

STEP_LIMIT

机器人与任务数据的口径见 data/mock_business.json:3 个仓库、11 台机器人、7 条任务、4 个角色。

评测怎么做的

scripts/eval_graph.py 里 13 条用例,每条重置内存库再跑(用例间状态污染是这类测试最常见的假通过来源),断言落在三处:

  1. 最终回答(是否说清"派给了谁""路径是什么")

  2. 每个工具的返回码(BATTERY_TOO_LOW / UNAUTHORIZED 这些不是异常,是业务语义)

  3. 批内一致性(一次派三单不能把同一台机器人派两次)

刻意设计的三类用例:

  • 点名但不可用:用户说"派给 R103",而 R103 电量 18% → 必须拒绝,不能悄悄换成别的机器人。所以点名时 fallbacks 为空。

  • 越权:viewer 派单、dispatcher 查别的仓 → 由工具拒绝,不是 Agent 拒绝。

  • 重复提交:同一条派单连跑两次 → 第二次返回 IDEMPOTENT_REPLAY,不重复下发给机器人。

三个指标的定义与局限

指标

定义

它能说明什么 / 不能说明什么

提案采纳率 1.00

被执行的动作数 ÷ propose 产生的动作数

说明"提议的东西都真的去做了",没有生成完就丢;不能说明提议对不对(对不对看正/负例成功率)

执行重试倍数 1.00x

执行阶段的工具调用数 ÷ 提案数

1.00 表示主方案一次成功、没动用 fallback;>1 说明候选选择质量下降

决策读放大 3.46x

只读调用数 ÷ 写提案数

每次派单平均要查 3.5 次队列/机器人/路径。这是下一个优化点:机器人状态缓存能把这块压到 1–2 次

⚠️ 这几个数都只来自 13 条用例,属于"能证明链路通、不能当性能基准"。3.46 这个数尤其不能外推——它完全取决于 assign 里查了几次、语料里几个仓几台车。

与真实系统的差异(别把它当成生产代码)

这里

生产里

规则化 planner.py 做意图识别

LoRA 微调的 14B 意图分类(同一接口:intent + slots + 缺失项)

内存态 Store + JSON mock 数据

MySQL + Redis,工具层走真库

单机 in-memory MCP transport

独立 MCP Server 进程,stdio/SSE 传输

审批用命令行 input()

前端确认卡片 + 逐项批准(一次只能批一项)

没有 LLM 参与决策

主 Agent 用云端模型规划,工具返回码驱动下一步

保留规则化解析是故意的:换成模型后,每个失败都会变成"模型没理解"这种不可定位的原因,评测数字就不可复现了。先把编排和边界验证对,再换模型。

目录

src/dispatch_agent/
  models.py      枚举与数据结构
  store.py       mock 业务库 + 幂等表 + Dijkstra 路径
  auth.py        工具级 + 仓库级双层鉴权
  tools.py       FastMCP 6 个工具(唯一能改状态的入口)
  planner.py     规则化意图解析
  agents/        AssignAgent / PathAgent / CollabAgent + 工具网关
  graph.py       LangGraph 状态机(含 interrupt 与防死循环)
  cli.py         命令行入口
data/mock_business.json   3 仓库 / 11 机器人 / 7 任务 / 4 角色
scripts/eval_graph.py     13 条端到端评测
results/eval_report.json  实测报告

License

MIT —— 见 LICENSE。data/mock_business.json 为虚构业务数据。

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables inspecting robot motion-planning tasks and historical records, starting isolated offline planning runs, checking execution and validation status, and retrieving independent validation evidence through MCP tools. It does not control physical robots.
    6
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to claim, heartbeat, submit, split, park, and resume atomic work-queue tasks, with fencing tokens and receipts, while storing and versioning records and content-addressed files through MCP.
    MIT