关于 Agent Dispatch
Agent Dispatch 是供 Claude Code 使用的 MCP 服务,让 Agent 将任务委派给其他项目目录中的独立 Claude 会话。它支持并行与异步任务、会话恢复和跨项目分组,并继承目标项目的工具与配置。
主要功能
- 01向其他项目目录委派任务
- 02并行及异步调度
- 03恢复已分派会话
- 04按分组共享项目上下文
- 05检查 Agent 配置与能力
适用场景
- 01委派任务以读取其他仓库的代码或运行测试
- 02通过基础设施 Agent 查询容器日志
- 03并行处理跨项目工作
- 04超时后继续未完成的分派会话
MCP 相关
Agent Dispatch 是 MCP 服务,向 Claude Code 暴露 Agent 列表、检查和任务分派等工具。它在各自项目目录中启动 Claude Code 会话,并返回执行结果。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
agent-dispatch
MCP 服务,让 Claude Code Agent 可以将任务委派给其他项目目录中的 Agent。
每个 Agent 都会在自己的项目目录中作为独立的 claude -p 会话运行,并继承该项目的 MCP 服务、CLAUDE.md 和工具。调用方 Agent 只需接收结果。
相关项目可以打包成一个**组**——包含共享的任务说明和成员列表——让一个会话能够协调这些项目间的工作(例如,代码仓库 + infra/Portainer 网关 + analytics 网关)。
支持 OAuth、API 密钥和 Claude 订阅认证。
**AI Agents:**此 README 是使用该工具的权威文档——设置方法见快速入门(每一步都有确定性的验证方法);首次调用见
dispatch;工具选择见选择哪个工具;故障处理见错误恢复。如果你要参与开发此仓库,请参阅 AGENTS.md。
快速入门
**前置条件:**必须安装并完成身份验证的 Claude Code CLI。先检查:
claude --version # must print a version — if it fails, install Claude Code before continuing
然后:
pip install agent-dispatch # or: pipx install agent-dispatch
# 1. Create config + register the MCP server with Claude Code (user scope)
agent-dispatch init
# 2. Register project directories as agents — REPLACE the example paths with
# real directories on your machine; they must exist (~ is expanded, relative
# paths are resolved). Descriptions are auto-generated from project files.
# No second project handy? Use the zero-setup block below instead.
agent-dispatch add infra ~/projects/infra
agent-dispatch add backend ~/projects/backend
# 3. Smoke test — dispatches a real task to the agent added in step 2 and prints
# the answer; exit 0 on success. Default task when none given:
# "What project is this? Describe in one sentence."
agent-dispatch test infra
# 4. Verify the whole install — prints "All checks passed." and exits 0 on success
agent-dispatch doctor
步骤 2–3 的零配置替代方案(无需第二个项目——注册当前目录):
agent-dispatch add self . && agent-dispatch test self "Say hello"
现在,每个 Claude Code 会话都能使用 dispatch 工具。独立验证:claude mcp list 必须输出一行以 agent-dispatch: 开头的内容。在 Claude Code 会话中,首先调用 MCP 工具 list_agents(),然后调用 dispatch(...)。
如果 init 未能注册 MCP 服务(输出警告而非 Registered MCP server),请手动注册:
claude mcp add-json agent-dispatch "{\"type\":\"stdio\",\"command\":\"$(which agent-dispatch)\",\"args\":[\"serve\"]}" --scope user
如果 test 因权限错误而失败(error_type: "permission"),请授予工具访问权限并重新测试:
agent-dispatch update infra --allowed-tools "Bash,Read,Grep" # least privilege
# or, if the agent needs everything (see SECURITY.md for the trade-off):
agent-dispatch update infra --permission-mode bypassPermissions
何时委派
**适合委派的情况:**任务需要使用其他项目中的工具、文件或上下文:
- 通过 infra Agent 的 Portainer MCP 查看容器日志
- 通过 db Agent 的 postgres MCP 查询数据库
- 在其他代码仓库中读取代码或运行测试
**不适合委派的情况:**你能够自行完成任务——委派会启动一个完整的 Claude 会话。
MCP 工具参考
list_agents
列出所有已配置的 Agent。请先调用此工具,查看可用项。
// Response (capability + permission fields shown only when populated)
[
{
"name": "infra",
"directory": "/home/user/projects/infra",
"description": "Infrastructure agent. MCP: portainer. Stack: Python, Docker",
"healthy": true,
"has_claude_md": true,
"has_mcp_config": true,
"mcp_servers": ["portainer", "postgres"],
"stacks": ["Python", "Docker"],
"dbs": ["Alembic"],
"capabilities": ["docker_logs", "deploy_debug"],
"risky_capabilities": ["restart_services"],
"permission_mode": "bypassPermissions",
"allowed_tools": ["Bash", "Read", "Grep"],
"typical": {"median_seconds": 143.3, "p90_seconds": 178.3, "median_cost_usd": 0.2666}
}
]
mcp_servers、stacks 和 dbs 会根据 Agent 的项目文件(.mcp.json、Dockerfile、pyproject.toml、Cargo.toml、prisma/、alembic.ini 等)自动检测,以便调用方无需先委派探测任务,就能选择合适的 Agent。
typical 是实测值,而非预先声明的值:它取自此 Agent 最近的委派记录,保存在使用日志——了解你的 Agent 群组实际花费中,并且只会在运行几次后显示。可用它来设置 timeout_seconds,并在调用前了解调用成本。如果日志功能关闭,或 Agent 的历史记录太少,则不会显示该值——与其根据两次运行推算出典型耗时,不如不提供数据。
inspect_agent
低成本的详细查询——读取 Agent 的文件,而不启动 claude 会话。返回完整配置(超时、模型、预算、权限模式、允许/禁止使用的工具)、检测到的 MCP/技术栈/数据库,以及存在时 CLAUDE.md 和 README.md 的简短预览。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
name | string | 是 | 来自 list_agents 的 Agent 名称 |
preview_lines | int | 否 | CLAUDE.md/README.md 的最大行数(默认值为 40,最大值为 200,设为 0 可禁用) |
在调用 dispatch_async/dispatch之前使用此工具,确认 Agent 具备完成任务所需的工具和上下文——这比探测式分派便宜得多。
它还会包含 Agent 的 instructions(长期指令)和完整的 typical 区块——即 list_agents 中经过精简的内容,以及 dispatches、failed 和 outcomes 分类:
"typical": {
"dispatches": 24, "median_seconds": 143.3, "p90_seconds": 178.3,
"median_cost_usd": 0.2666, "failed": 1, "outcomes": {"done": 21, "partial": 2}
}
分组
分组会将相关 Agent 组合成跨项目工作集——通常包含几个代码仓库和能力网关(例如,带有 Portainer MCP 的 infra Agent,以及配备浏览器和 Yandex Metrica 的 analytics Agent)。它能让一个编排会话协调跨代码、部署和验证的工作。
分组是一个描述层,而非执行引擎——没有路由器,也没有状态机。你需要查看 Agent 的提示信息来选择成员,并使用常规分派工具进行协调。有两个面向不同受众的文本字段:
description——面向编排者:如何协调该分组(步骤顺序、各成员的职责)。通过list_groups/inspect_group显示,绝不会注入成员的提示词。shared_context——面向成员的事实信息(技术栈名称、计数器 ID、约定),无论由哪个成员读取都适用。传入group=时,会自动添加到成员的context前面。
成员通过名称引用 Agent;成员关系为多对多(共享网关可以属于多个分组)。使用 agent-dispatch group CLI(add/list/inspect/update/remove)管理分组,也可以编辑 agents.yaml。
list_groups()——低成本读取所有分组的信息,不会启动子进程:显示描述、成员数量,以及每个成员的 use_for 提示和健康状态。若成员对应的 Agent 已被移除,则会标记为 "unknown": true,而不会报错。
inspect_group(name)——查看一个分组的完整简介:description、完整的 shared_context 和成员列表。若要深入查看特定成员,请调用 inspect_agent(member)——inspect_group 有意保持为低成本的成员关系查询。
使用分组——将 group= 传给 dispatch(或在 dispatch_parallel 中为每个项目单独传入)。该 Agent 必须是分组成员;所属分组的 shared_context 会自动随请求传递:
# From the shop-web codebase, hand the deploy to the infra gateway.
# The "shop" group's facts (stack name, counter id) are auto-attached.
dispatch(
agent="infra",
task="Redeploy the shop-web container",
caller="shop-web",
goal="ship the checkout fix",
group="shop",
)
group=""(默认值)与普通分派逐字节完全相同——共享事实会并入 context 字符串,因此结果缓存会自动区分不同分组,不带分组的调用也不受影响。
dispatch
一次性任务委派。结果会被缓存——在 TTL 内发出相同请求会立即返回。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
agent | string | 是 | 来自 list_agents 的 Agent 名称 |
task | string | 是 | 要执行的操作——请具体说明,Agent 不会获得你们对话中的上下文 |
context | string | 否 | 额外上下文:错误消息、代码片段、堆栈跟踪 |
caller | string | 否 | 你的项目/角色——帮助 Agent 理解请求者 |
goal | string | 否 | 更广泛的目标——帮助 Agent 做出更好的权衡 |
response_format | string | 否 | "json" 表示请求单个 JSON 值;解析后的结果存入 parsed_result。留空表示自由格式文本。 |
return_ref | bool | 否 | 为 true 时,仅返回 ref 和摘要预览,而不返回完整结果文本。使用 fetch_result(ref) 可按需加载完整文本。 |
summary_chars | int | 否 | ref 响应中包含的结果文本最大字符数(默认值为 500)。 |
timeout_seconds | int | 否 | 此次调用的一次性超时设置(0 = Agent 配置的超时时间;限制在 10–7200 之间)。无需修改配置即可处理已知的长任务。 |
group | string | 否 | 组名(来自 list_groups)。Agent 必须是该组成员;组的 shared_context(面向成员的事实信息)会自动添加到 context 前。留空表示直接分派。参见 Groups。 |
# Call — recommended form (always include caller and goal)
dispatch(
agent="infra", # must exist in list_agents()
task="Check container logs for errors related to the scheduler service",
context="Error: TypeError at scheduler.py:42",
caller="backend", # your project/role
goal="debug production crash" # the broader objective
)
// Response (success)
{
"agent": "infra",
"success": true,
"result": "Found 3 errors in container logs: TypeError in scheduler.py:42...",
"session_id": "sess-abc-123",
"cost_usd": 0.02,
"duration_ms": 5000,
"num_turns": 2,
"outcome": "done"
}
// Response (failure — error_type helps you handle programmatically)
{
"agent": "infra",
"success": false,
"result": "",
"error": "Tool_use is not allowed in this permission mode\n\nHint: ...",
"error_type": "permission"
}
error_type 的取值: permission(工具/操作被拒绝)、timeout、recursion(超过分派深度)、not_found(目录或 CLI 不存在)、budget(claude CLI 在达到 max_budget_usd 时停止会话)、usage_limit(Claude 账户 达到速率/会话限制)、cli_error(其他故障)。权限、预算、使用量限制和超时错误均包含可执行的提示。
可恢复的超时: 每次新的分派都会预先分配一个会话 UUID(--session-id),因此超时的分派仍会返回 session_id——部分对话记录会在终止后保留。超时错误会明确说明如何恢复:使用 dispatch_session(agent, "Continue where you left off", session_id=...) 继续,使用更大的 timeout_seconds 重试,或使用 dispatch_async。
被拒绝工具的可见性: 在非交互模式下,claude CLI 会自动拒绝 Agent 无权使用的工具——此时 Agent 往往会“成功”给出类似 “我需要你授权一次只读查询” 的回答。发生这种情况时,响应会包含确定性信号:denied_tools(从 CLI 的 permission_denials 中解析)以及解释结果可能不完整及如何授予访问权限的 hint。success 仍为 true——这是软性信号,并非失败。
// Response (success, but a tool was blocked)
{
"agent": "analysis",
"success": true,
"result": "Here is the offline mapping. To finish I'd need to run one read-only query...",
"denied_tools": ["Bash"],
"hint": "1 tool call(s) were denied by permissions: Bash. The result may be incomplete..."
}
结构化 JSON 输出: 传入 response_format="json",即可要求 Agent 返回单个 JSON 值。运行器会附加一段指令尾注(“仅回复单个有效 JSON 值,不要使用代码围栏或添加说明文字”),并在成功时解析响应——解析后的值存入 parsed_result。原始文本始终保存在 result 中。解析失败时,parsed_result=None,但不会导致分派失败(软模式)。
// Response with response_format="json"
{
"agent": "infra",
"success": true,
"result": "{\"errors\": 3, \"first_at\": \"14:02\"}",
"parsed_result": {"errors": 3, "first_at": "14:02"}
}
始终传入 caller 和 goal——被分派的 Agent 会看到结构化提示词:
## Goal
debug production crash
## Dispatched by
backend
## Context
Error: TypeError at scheduler.py:42
## Task
Check container logs for recent errors related to the scheduler service
分派协议与 outcome。 单独使用 claude -p 时,它并不知道自己正由另一个 Agent 驱动:面对含糊的任务,它很可能会以 “你指的是哪个服务?” 结束——这次调用既计费又没有回答问题,而且普通缓存随后会在整个 TTL 期间再次返回这个结果。因此,每次分派还会向 Agent 的系统提示词追加一段简短的分派协议(使用 --append-system-prompt,绝不会混入任务文本):
- 它以非交互方式运行,由
caller派发,不会有人回答问题或批准操作——说明假设并继续;如果两种理解有差异,则采用更安全的理解; - 被拒绝的工具或缺失的内容是需要报告的事项,而不是停止的理由——完成所有其他可行的工作;
- 它的时间预算约为 Agent 的超时时间(以及设定的支出上限)——将工作范围控制在预算内;完整的部分答案胜过未完成的完美答案;
- 先给出结果,再提供依据;列出无法完成的事项及原因(这正是让
return_ref摘要——文本的开头——值得一读的原因); - 最后单独一行写上
STATUS: done、STATUS: partial或STATUS: blocked。
最后一行会从 result 提取到 outcome 中——这是 Agent 自己给出的判定,可确定性地检查:"done" 表示已完成;"partial" / "blocked" 表示 Agent 认为工作尚未完成,结果会说明缺少什么,并由 hint 指明后续操作(通常是 dispatch_session(..., session_id=...))。阅读正文前先查看 outcome。它不会改变 success,而且 partial / blocked 结果不会被缓存。dispatch_parallel(..., aggregate=...) 会为聚合器标记这类成员,避免将受阻报告合成为已完成的结果。如果 Agent 未报告 outcome,则该项不存在——协议已关闭(settings.dispatch_protocol: false)、请求了 response_format="json"(此时 JSON 尾部决定回复格式),或者 Agent 直接省略了它。
常设指令。 每次派发时,都会在同一条系统提示词中遵循每个 Agent 的 instructions(通过 add_agent / update_agent 或 agent-dispatch update <name> --instructions "..." 设置)——“只允许只读 SQL”“除非任务明确要求,否则绝不重启服务栈”“使用精确的日志行作答”。这些指令不同于每次调用单独提供的 context,也不同于面向交互式会话编写的项目自身 CLAUDE.md;它们是被派发执行时必须遵守的规则。
// Response (success, but the agent says it did not finish)
{
"agent": "infra",
"success": true,
"result": "Restarted horizon. Could not verify the queue drained: the redis container is not reachable from here.",
"session_id": "sess-abc-123",
"outcome": "partial",
"hint": "The agent reports its work is PARTIAL — read the result for what is missing, then continue in the same session via dispatch_session(agent='infra', task='Continue where you left off', session_id='sess-abc-123') or re-dispatch with what it needed."
}
dispatch_session
多轮对话:继续与 Agent 的对话。首次调用会启动会话,后续调用传入 session_id 即可继续。结果永不缓存。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
agent | string | 是 | Agent 名称 |
task | string | 是 | 任务或后续消息 |
session_id | string | 否 | 来自先前响应;新会话留空 |
context | string | 否 | 额外上下文 |
caller | string | 否 | 派发者 |
goal | string | 否 | 更广泛的目标 |
timeout_seconds | int | 否 | 单次超时覆盖值(0 = Agent 默认值;限制在 10–7200 之间) |
dispatch_session 也是超时恢复途径:超时的 dispatch 会返回 session_id——将其传入此处,并设置 task="Continue where you left off",即可挽救未完成的工作,而不必从头开始。
Turn 1: dispatch_session("infra", "List running containers")
→ session_id: "sess-abc"
Turn 2: dispatch_session("infra", "Restart the nginx one", session_id="sess-abc")
→ agent remembers previous context
dispatch_parallel
并发运行多个任务。比按顺序调用 dispatch 快得多。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
dispatches | string (JSON) | 是 | JSON 数组,包含 {"agent", "task", "context?", "caller?", "goal?", "response_format?", "return_ref?", "summary_chars?", "timeout_seconds?", "group?"}(每项中的 group 会预先验证成员资格,并自动注入其 shared_context) |
aggregate | string | 否 | 用于将所有结果综合为一个答案的 Agent 名称 |
重要: dispatches 是 JSON 字符串,不是列表。
// Input
[
{"agent": "infra", "task": "check pod logs for errors", "caller": "backend", "goal": "debug crash"},
{"agent": "db", "task": "are all migrations applied?", "caller": "backend", "goal": "debug crash"}
]
// Response (without aggregate)
[
{"agent": "infra", "success": true, "result": "No errors in pod logs", ...},
{"agent": "db", "success": true, "result": "All migrations applied", ...}
]
// Response (with aggregate="backend")
{
"individual_results": [
{"agent": "infra", "success": true, "result": "No errors in pod logs", ...},
{"agent": "db", "success": true, "result": "All migrations applied", ...}
],
"aggregated": {
"agent": "backend",
"success": true,
"result": "Summary: all systems nominal. No pod errors, all migrations applied."
}
}
dispatch_stream
与 dispatch 相同,但会显示 Agent 工作时的实时进度。适用于长时间运行的任务。不缓存。
进度通知会保留最新的 100 条待处理消息,每条最多 300 个字符。如果输出速度快于客户端接收速度,较早的通知会被省略,并显示省略数量;最终结果不受影响。 断开连接或取消工具调用会丢弃待处理通知,并停止缓冲新通知。工作进程仍会占用并发槽位,直到任务完成。
参数与 dispatch 相同,但不包括 return_ref/summary_chars(流式传输与 ref 模式不兼容)和 group(仅 dispatch 和 dispatch_parallel 中的每个项目支持注入组上下文)。
dispatch_dialogue
两个 Agent 通过多轮对话协作。不会缓存。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
requester | string | 是 | 提出问题或提供上下文的 Agent |
responder | string | 是 | 具备相关专业知识或工具的 Agent |
topic | string | 是 | 要讨论的问题或疑问 |
max_rounds | int | 否 | 最多往返轮数(默认:3,最多:10) |
每轮最多消耗 2 次调度。Agent 使用 [RESOLVED] 表示已完成。
// Response
{
"resolved": true,
"rounds": 2,
"total_cost_usd": 0.04,
"total_duration_ms": 12000,
"final_answer": "Staging had 1 pending migration. Applied successfully.",
"conversation": [
{"agent": "db", "role": "responder", "round": 1, "message": "Which environment?", "cost_usd": 0.01},
{"agent": "backend", "role": "requester", "round": 1, "message": "Staging", "cost_usd": 0.01},
{"agent": "db", "role": "responder", "round": 2, "message": "Applied. [RESOLVED]", "cost_usd": 0.01}
]
}
add_agent
将新的项目目录注册为 Agent。如果未提供描述,则根据项目文件自动生成。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
name | string | 是 | Agent 名称(字母、数字、连字符、下划线) |
directory | string | 是 | 现有项目目录的路径(会展开 ~,相对路径会解析为绝对路径) |
description | string | 否 | 此 Agent 的功能 — 为空时自动生成 |
timeout | int | 否 | 超时时间,单位为秒(0 = 300;这是一个字面默认值,不是 settings.default_timeout) |
max_budget_usd | float | 否 | 每次调度的最高费用(美元),必须为有限值(0 = 继承 settings.default_max_budget_usd;只有该设置也未设定时才不设上限) |
permission_mode | string | 否 | 权限模式(例如 default、plan、bypassPermissions) |
allowed_tools | string | 否 | 以逗号分隔的允许使用的工具(例如 "Bash,Read,Edit") |
disallowed_tools | string | 否 | 以逗号分隔的禁止使用的工具 |
capabilities | string | 否 | 以逗号分隔的能力标签(例如 "docker_logs,deploy_debug") |
risky_capabilities | string | 否 | 以逗号分隔的高风险标签(例如 "restart_services") |
instructions | string | 否 | 每次调度时附加到 Agent 系统提示词中的常驻指令(参见调度协议) |
update_agent
更新现有 Agent 的配置。只有非空字段会被更改。传入 "none" 可清除字段。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | 要更新的 Agent 名称 |
description | string | 否 | 新描述 |
timeout | int | 否 | 新超时时间(0 = 不更改) |
max_budget_usd | float | 否 | 新预算上限(0 = 不更改;负数会清除 单个 Agent 的上限,之后应用 settings.default_max_budget_usd) |
model | string | 否 | 模型覆盖值。传入 "none" 可清除 |
permission_mode | string | 否 | 权限模式。传入 "none" 可清除 |
allowed_tools | string | 否 | 逗号分隔。传入 "none" 可清除 |
disallowed_tools | string | 否 | 逗号分隔。传入 "none" 可清除 |
capabilities | string | 否 | 逗号分隔。传入 "none" 可清除 |
risky_capabilities | string | 否 | 逗号分隔。传入 "none" 可清除 |
instructions | string | 否 | 每次派发都适用的常设指令(替换原有文本)。传入 "none" 可清除 |
通过 MCP 修改工具更改 Agent 配置时,会立即丢弃其缓存结果。缓存键还包含 Agent 配置和派发默认值,因此通过 CLI、其他服务或 YAML 进行的编辑会在下次请求时生效。使用旧配置完成工作的执行器无法覆盖新配置的答案。未更改的 Agent 会保留其缓存。
remove_agent
从配置中移除 Agent。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | 要移除的 Agent 名称 |
cache_stats / cache_clear
查看缓存命中率和大小,或清除所有缓存结果。
结果引用 — return_ref + fetch_result
对于结果文本较大的派发任务(审计、日志转储、代码搜索),返回完整文本会增大调用方 Agent 的上下文。使用 return_ref=True 可仅获取简短引用:
dispatch(agent="infra", task="audit every container", return_ref=True, summary_chars=200)
-> {"ref": "8f3a...e1", "agent": "infra", "success": true,
"size": 14823, "summary_chars": 200,
"summary": "Inspected 32 containers. Found 3 OOM kills in the last hour:\n- worker-3...",
"cost_usd": 0.08, "duration_ms": 9200}
// Later, when you actually need to read the result:
fetch_result(ref="8f3a...e1") -> full DispatchResult JSON
fetch_result(ref="8f3a...e1", max_chars=2000) -> truncated, plus {"truncated": true, "full_size": 14823}
引用使用与 dispatch_async 任务相同的存储空间(位于 ~/.config/agent-dispatch/jobs/),因此 dispatch_async 返回的任何 job_id 也都是 fetch_result 的有效 ref。parsed_result(设置了 response_format="json" 时)内容较少,始终会直接内嵌在引用响应中,无需再次获取。
异步调度 — dispatch_async、dispatch_status、dispatch_wait、dispatch_cancel、dispatch_jobs、dispatch_gc
当调度的任务需要较长时间时,你不会想让自己的工具槽位空等几分钟。异步调度会立即返回 job_id,让你在准备好时再来查看。
// 1. fire and forget (timeout_seconds= works here too for known-long tasks)
dispatch_async(agent="infra", task="audit every container log for OOM kills today")
-> {"job_id": "8f3a...e1", "status": "pending", "agent": "infra"}
// 2. do other work, then check progress (non-blocking)
// `progress` is a rolling tail of what the agent is doing right now
dispatch_status(job_id="8f3a...e1")
-> {"id": "8f3a...e1", "status": "running", "started_at": 1730000123.4,
"progress": ["Using tool: Bash", "Scanning container logs for OOM events..."], ...}
// 3. or block until done (timeout_seconds default: 60, capped at 3600)
dispatch_wait(job_id="8f3a...e1", timeout_seconds=120)
-> {"id": "8f3a...e1", "status": "done", "result": {"agent": "infra", "success": true, ...}}
// If the timeout fires, the job keeps running:
-> {"id": "...", "status": "running", "timed_out_waiting": true}
dispatch_cancel(job_id) 会取消待处理的任务;如果运行中的任务由同一个服务实例启动,也会终止其 claude 进程组(任务会先标记为 cancelled,因此工作进程最后的写入不会撤销该状态;部分工作会丢失,但进度尾记录会保留)。在 POSIX 上,该进程组中的子进程也会被终止,从而释放继承的管道和并发槽位。如果任务已被认领、但进程尚未启动,此时收到的取消请求也会生效:进程一启动就会被终止。与取消请求发生竞争的兼容性重试会在注册时被终止。由之前的服务运行实例启动的运行中任务无法安全终止,会继续运行直至完成。响应会包含一个 outcome,其值为 cancelled、cancelled_running、running(不归当前服务所有)、already_terminal 或 not_found。
异步工作进程在底层以流式方式运行:任务文件会保留一段滚动尾记录(最近 20 行,约每秒写入一次),其中包含助手文本和工具使用事件。任务运行期间,dispatch_status 会通过 progress 显示该记录,并在任务结束后继续保留,作为事后追踪记录;dispatch_jobs 会为运行中的任务显示 last_progress。
dispatch_jobs(status?, limit=50, agent?) 会以摘要形式列出最近的任务(可按 pending / running / done / failed / cancelled 筛选)。agent 会匹配确切名称,包括已从配置中移除的 Agent;两个筛选条件都会在限制数量之前生效。dispatch_gc(max_age_days=7) 会清除超过阈值的终态任务——待处理和运行中的任务永远不会被删除。使用 dry_run=True 可预览符合条件的任务 ID,而不实际删除。
任务状态会持久化到磁盘上的 ~/.config/agent-dispatch/jobs/(可通过 AGENT_DISPATCH_JOBS_DIR 覆盖)。每个任务对应一个 JSON 文件,采用仅所有者可读写的权限(0o600)和原子写入,因此任务运行期间也可以安全读取或执行 ls。调用者提供的 job_id 会在访问任何文件之前验证是否为 32 位十六进制字符串(防止路径遍历)。服务启动时会恢复崩溃实例遗留的任务:卡住超过一小时的 running 任务和超过 24 小时的 pending 任务会被标记为 failed,这样它们就不会永远被轮询,并且可由 dispatch_gc 清除。(pending 的阈值特意设得较长——任务目录由所有正在运行的服务共享,因此不能清除排在另一台服务的并发限制之后的任务。)
| 适合使用异步调度的情况 | 适合使用 dispatch 的情况 |
|---|---|
| 长任务(几分钟)——你想继续处理其他工作 | 短任务——你需要立即得到答案 |
| 有多个长任务,打算稍后统一收集结果 | 有多个短任务 → dispatch_parallel |
| 不需要缓存(每次调用都会创建新任务) | 默认启用缓存——相同请求无需付费 |
使用哪个工具
| 场景 | 工具 |
|---|---|
| 向另一个项目快速提问一次 | dispatch |
| 需要多步工作流并进行后续跟进 | dispatch_session |
| 需要同时获取多个 Agent 的回答 | dispatch_parallel |
| 任务耗时较长,需要查看进度 | dispatch_stream |
| 两个 Agent 需要协作 | dispatch_dialogue |
| 需要汇总多个 Agent 的回答 | 使用 aggregate 的 dispatch_parallel |
| 任务耗时较长,不想占用工具时段 | dispatch_async + dispatch_wait |
| 查看进度而不阻塞 | dispatch_status |
| 已知耗时较长的一次性任务 | 任意 dispatch 工具,并设置 timeout_seconds=... |
dispatch 超时 | 使用错误信息中的 session_id 调用 dispatch_session |
| 协调一组相关项目 | 定义一个 组,然后调用 dispatch(..., group=name) |
| 查看哪些 Agent 组成了一个工作集 | list_groups / inspect_group |
错误恢复
失败是确定性的:先检查 success,然后根据 error_type 分支处理。
error_type | 含义 | 恢复方法 |
|---|---|---|
permission | 工具调用遭到拒绝 | 使用 update_agent(name, allowed_tools="Bash,Read")(遵循最小权限原则),或使用 update_agent(name, permission_mode="bypassPermissions"),然后重新分发。error 文本中包含具体修复方法的提示。 |
timeout | 进程组在超时时被终止 | 继续未完成的工作:dispatch_session(agent, "Continue where you left off", session_id=<from the error text>)。也可以使用更大的 timeout_seconds= 重试——日志记录有历史后,错误会指出足以覆盖该 Agent 第 90 百分位耗时的值——或者使用 dispatch_async。普通分发和流式分发都会保留终止前收到的完整 CLI 结果;成功结果会包含清理用的 hint。部分输出仍属于超时。 |
not_found | 找不到 Agent 目录或 claude CLI | 运行 list_agents() → 检查 healthy。使用现有路径重新添加 Agent,或运行 agent-dispatch doctor 查找缺失项。 |
recursion | 分发嵌套层数超过 max_dispatch_depth(默认值为 3) | 不要从被分发的 Agent 再发起分发;如果确实需要嵌套,请在设置中提高 max_dispatch_depth。 |
budget | claude CLI 在达到 max_budget_usd 支出上限时结束了会话——答案不完整 | 提高上限(update_agent(name, max_budget_usd=2.0))、切换到更便宜的 model,或拆分任务。可以恢复部分会话:dispatch_session(agent, "Continue where you left off", session_id=<from the result>)。 |
usage_limit | Claude 账号触及使用量/速率限制——并非此 Agent 的限制,也未产生任何费用 | 等待 error 文本中注明的重置时间。分发给其他 Agent 并不能绕过限制:所有 Agent 都使用同一个账号。如果负载较高时此问题反复出现,请降低 settings.max_concurrency。 |
cli_error | claude 子进程返回的其他错误 | 阅读 error 文本;如果是环境问题,运行 agent-dispatch doctor;如果是暂时性错误,重试一次。 |
以下三种软信号会与 success: true 一起返回:
denied_tools+hint— Agent 已完成,但部分工具调用遭到拦截;结果可能不完整。授予访问权限(参见permission行)并重新分发。outcome: "partial"/"blocked"— Agent 判定自身未能完成;结果会指出缺少的内容,hint会提供在同一会话中继续执行的dispatch_session(...)调用。结果不会缓存,因此修复原因后重新分发会从头运行。parsed_result: null且response_format="json"— 回复不是有效的 JSON;原始文本仍在result中。注意:无法按要求执行的 Agent 会返回{"error": "<reason>"}——该内容可以成功解析——因此还要检查parsed_result中是否有"error"键。budget_exceeded: true—cost_usd超过了 Agent 的max_budget_usd(或设置中的默认值),但 CLI 并未停止运行(最后一轮可能超出上限)。此次分发并未失败——费用已经产生——但超支的 Agent 现在已可见。请缩紧任务范围、选择更便宜的模型,或提高预算。如果 CLI 确实停止了运行,则会以error_type: "budget"标记失败。
工具级错误(未知 Agent、输入格式错误)会返回普通封装,而非 DispatchResult:
{"error": "Unknown agent: 'foo'. Available: infra, db, monitoring"}
配置
配置文件位于 ~/.config/agent-dispatch/agents.yaml(覆盖项:AGENT_DISPATCH_CONFIG 环境变量):
agents:
infra:
directory: ~/projects/infra
description: "Infrastructure agent. MCP: portainer."
timeout: 300 # seconds, default: 300
capabilities: # capability labels, shown in list_agents
- docker_logs
- deploy_debug
risky_capabilities: # high-risk labels, surfaced for visibility
- restart_services
# instructions: | # standing orders, appended to the system prompt on every dispatch
# Read-only: never restart or redeploy unless the task says so.
# Quote exact log lines with timestamps.
# model: sonnet # optional model override
# max_budget_usd: 1.0 # cost limit per dispatch
# permission_mode: bypassPermissions # one of: default | plan | bypassPermissions
# allowed_tools: # restrict which tools the agent can use
# - Read
# - Grep
# disallowed_tools: # block specific tools
# - Write
# Optional: bundle related agents into a cross-project working set.
# A descriptive layer — no router; the orchestrating session coordinates
# with the normal dispatch tools. See the Groups section above.
groups:
shop:
# ORCHESTRATOR-facing: how to coordinate the group. Surfaced by
# list_groups/inspect_group, NEVER injected into a member's prompt.
description: "After a code change: deploy via infra, then verify via analytics."
# MEMBER-facing facts, auto-prepended to dispatch(..., group="shop").
shared_context: |
Prod runs in Portainer stack "shop". Metrica counter 12345.
members: # reference agents above (many-to-many)
- agent: infra
use_for: deploy, restart, container logs
# - agent: backend
# use_for: orders/payments endpoints
settings:
default_timeout: 300
# default_permission_mode: bypassPermissions # inherited by all agents
# default_allowed_tools: # inherited when agent has none
# - Bash
# - Read
# - Edit
max_dispatch_depth: 3 # recursion protection
max_concurrency: 5 # max parallel claude -p processes per server, across all tools
# dispatch_protocol: true # send every agent the dispatch protocol (non-interactive,
# # time budget, STATUS line → `outcome`). false = raw claude -p.
# usage_log: true # record every dispatch in usage.jsonl (cost, duration, outcome).
# # Powers `agent-dispatch stats`, the `typical` block and the
# # measured timeout suggestion. false = record nothing.
# job_retention_days: 30 # 0 (default) = never prune. See "Job retention" below.
cache:
enabled: true
ttl: 300 # seconds
max_size: 1000 # max cached entries; oldest evicted first (FIFO)
每次调用工具时都会重新加载配置——添加 Agent 无需重启。
使用日志——实际了解你的 Agent 集群成本
每次调度都会向 ~/.config/agent-dispatch/usage.jsonl
追加一行(可通过 AGENT_DISPATCH_USAGE_LOG 覆盖):Agent、成功状态、成本、耗时、
轮次、outcome、错误类型、调用方。缓存命中也会记录,并标记为
cached,因此报告可以显示缓存节省了多少。
$ agent-dispatch stats --days 7
Usage (last 7 day(s))
dispatches: 33, 1 served from cache
spend: $13.0390
duration: median 87s, p90 2m40s, max 10m00s
outcomes: blocked 1, done 27, partial 3
failures: timeout 1, usage_limit 1
Per agent
analytic 18 runs $ 8.9441 median 2m04s p90 2m51s
done 15, partial 3
gitlab 12 runs $ 3.7949 median 60s p90 86s
done 12 | 1 cached
--agent NAME 可缩小范围,--json 会以 JSON 格式输出相同的报告。
日志让另外三项功能得以实现:list_agents / inspect_agent 中的 typical 区块、
超时错误会根据 Agent 实际的 p90 值命名,而不是采用翻倍猜测值;以及回答
“哪个 Agent 成本高”。在设置中将 usage_log: false 即可关闭日志。
值得了解的属性:文件仅所有者可访问(0o600);每条记录通过一次 O_APPEND 写入,
大小远低于 PIPE_BUF,因此 CLI 和所有运行中的服务可以无锁共享,不会产生不完整行;
文件约为 2 MB 时会轮转为 usage.jsonl.1,并保留两代,因此总大小永久限制在约 4 MB。
写入失败绝不会导致调度失败。
作业保留
每次 dispatch_async 以及每次 dispatch(..., return_ref=True) 都会在
~/.config/agent-dispatch/jobs/ 中写入一条记录,且不会自动删除任何记录——
必须手动运行 dispatch_gc。因此,该目录会无限增长,而 dispatch_jobs 以及每次服务
启动时运行的过期作业恢复流程,都会读取并解析其中的每个文件。
将 job_retention_days 设为 N 天,即可在服务启动时清理早于该期限的终态(已完成/失败/已取消)记录:
settings:
job_retention_days: 30
默认值为 0——关闭——因为这些记录是你过去调度的历史记录,删除后无法恢复。
待处理和运行中的作业永远不会被清理。
agent-dispatch gc --days N 和 dispatch_gc 工具可按同一规则执行一次性清理。删除前可预览符合条件的记录:
agent-dispatch gc --days 30 --dry-run
agent-dispatch gc --days 30
--dry-run 也可与 --all 搭配使用。MCP 对应的调用为
dispatch_gc(max_age_days=30, dry_run=True),会返回 dry_run: true、
purged: 0、would_purge、job_ids 和 max_age_days。
预览只是一个快照;删除时会再次检查是否符合条件,因此作业完成后计数可能发生变化。
无法读取的记录、时间戳无效的记录,以及内部 ID 与文件名不一致的记录都会跳过并保留,以便检查。
自动描述
不带 --description 的 agent-dispatch add 会根据以下内容生成描述:
CLAUDE.md——第一个有实质内容的段落(优先)README.md——第一行有实质内容的文字(备用)pyproject.toml/package.json——项目描述.mcp.json——列出 MCP 服务名称- 技术栈指标——Docker、Rust、Go、Python、Node.js
- 数据库指标——Prisma、Alembic、迁移
显式能力
自动描述很有用,但显式设置 capabilities 能更清楚地说明各 Agent 的用途。为 Agent 添加简短的 snake_case 任务标签:
agent-dispatch update infra \
--capabilities docker_logs,deploy_debug \
--risky-capabilities restart_services
list_agents 和 inspect_agent 会显示 capabilities 和 risky_capabilities,让调用方一眼选出合适的 Agent——risky_capabilities 会标记风险较高的能力(例如重启服务),以便重点审查。
工作原理
Your Claude Code session
│
├─ dispatch("infra", "find errors", caller="backend", goal="debug crash")
│
▼
agent-dispatch MCP server
├─ cache check → hit? return cached result
├─ shared process limit → bound concurrency across all dispatch tools
└─ subprocess.Popen(["claude", "-p", ...], cwd=~/projects/infra/)
│
▼
New Claude Code session in ~/projects/infra/
├─ Inherits: CLAUDE.md, .mcp.json, project tools
├─ System prompt += dispatch protocol + the agent's standing `instructions`
├─ Receives structured prompt with goal/caller/context/task
└─ Returns result (+ its own STATUS → `outcome`) → cached when complete
│
└─ one line appended to usage.jsonl (cost, duration, outcome)
安全性
- 递归保护 —
AGENT_DISPATCH_DEPTH环境变量用于跟踪嵌套层级。默认上限:3。跨子进程边界时尽力保证(参见 SECURITY.md)。 - 参数注入防护 — 以
-开头的结构化 CLI 字段(session_id、model、permission_mode、工具名称)会被拒绝,防止它们偷偷注入额外的claude标志。 - 路径遍历防护 — 调用方提供的
job_id/ref值会先验证是否为 32 位十六进制字符串,然后才进行任何文件系统访问。 - 仅所有者可访问的状态文件 — 作业文件、
agents.yaml、用量日志和服务注册表的权限均设为0o600;其目录权限均设为0o700。 - 成本控制 — 每个 Agent 或全局的
max_budget_usd会作为--max-budget-usd传给claudeCLI,因此失控的派发会在达到上限时停止,并返回带有可恢复session_id的error_type: "budget"。如果未能及时停止而超出预算,则会在事后标记budget_exceeded: true并附带提示。 - 并发 —
max_concurrency(默认值:5)限制单个服务进程中所有派发工具可并行运行的claude -p进程数。普通、流式和后台派发共用同一上限;等待中的调用按到达顺序处理,因此后台作业队列不会让交互式调用无法获得处理机会。取消等待中的调用不会启动其进程;取消已启动的普通或流式调用后,其占用的并发名额会一直保留到工作进程结束。重新加载已更改的上限时,已占用的名额会保留:降低上限会让现有任务完成,并在容量可用前阻止启动新任务。每个独立的服务进程都有自己的上限。 - 超时 — 可按 Agent 设置或全局设置(默认值:300 秒)。普通和流式派发都会在独立的进程组中运行 Agent。在 POSIX 系统上,达到截止时间后会终止该进程组,包括继承了输出管道的子进程。如果某个子进程故意脱离进程组,普通派发还会将清理时间限制在额外 1 秒内;该脱离的进程不受进程组控制。完整的 CLI 结果不会因清理超时而丢失;成功结果会附带提示。
- 缓存 — 在相同的 Agent 配置和派发默认设置下,完全相同的
(agent, task, context, caller, goal, response_format)请求会返回缓存结果,缓存大小受cache.max_size限制(优先淘汰最旧的条目)。仅缓存无异常的成功结果:失败结果、含有denied_tools的结果、标记了budget_exceeded的结果,以及 Agent 自身报告为partial/blocked的结果均不会缓存,因此文档所述的“授予访问权限 / 提高上限,然后重新派发”恢复方式绝不会返回过时的不完整答案。更改 Agent 配置会使其缓存条目失效。会话和对话绝不缓存。group=派发会将组的shared_context合并到context,因此不同组会分别缓存,普通派发则不受影响。 - 持久化配置 —
agents.yaml会以原子方式写入(临时文件 + 重命名),因此中断的写入绝不会截断该文件。CLI 和 MCP 服务的每种变更路径都会获取跨进程咨询锁,因此并发编辑不会导致彼此的 Agent 配置丢失。该锁按设计尽力保证:等待 10 秒后会记录警告并继续执行,因为卡住的锁持有者不应冻结 MCP 服务——因此,在配置竞争激烈时可能发生更新丢失,但不会发生文件截断。
完整的威胁模型(包括 bypassPermissions 权限提升风险和磁盘上的作业文件)请参见 SECURITY.md。
CLI
| Command | Description |
|---|---|
agent-dispatch init | 创建配置,并向 Claude Code 注册 MCP 服务 |
agent-dispatch add <name> <dir> | 添加 Agent(自动生成描述) |
agent-dispatch update <name> | 更新 Agent 配置(权限、超时、模型、--instructions 等) |
agent-dispatch remove <name> | 移除 Agent |
agent-dispatch list | 列出 Agent 及其运行状况和权限 |
agent-dispatch group <add|list|inspect|update|remove> | 管理群组——跨项目的 Agent 工作集 |
agent-dispatch describe <name> | 显示单个 Agent 的完整配置(三态工具、项目文件) |
agent-dispatch test <name> [task] [--stream] | 通过一次调度测试 Agent(使用 --stream 查看实时进度) |
agent-dispatch stats [--days N --agent X --json] | 查看调度开销:各 Agent 的费用、耗时、结果和失败情况 |
agent-dispatch doctor [--json --strict] | 诊断安装、过时的服务、MCP 注册、Agent 运行状况和群组成员;--strict 还会在有警告时返回失败 |
agent-dispatch jobs [--status STATUS --agent NAME --limit N --json] | 列出最近的作业摘要,包括以 return_ref 存储的结果 |
agent-dispatch job <id> [--json] | 显示单个作业;--json 可导出未截断的完整记录 |
agent-dispatch cancel <id> | 取消待处理的作业(对于运行中的作业,请使用 dispatch_cancel MCP 工具) |
agent-dispatch gc [--days N | --all] [--dry-run] | 清除早于 N 天的终态作业(默认 7 天;--all 会清除所有时长的作业);--dry-run 可预览而不删除 |
agent-dispatch serve | 启动 MCP 服务(stdio,由 Claude Code 使用) |
若要通过脚本检查安装情况:
agent-dispatch doctor --json --strict > diagnostics.json
报告包含 schema_version: 1、总体 status(ok、warn 或
fail)、issues 和 warnings 计数,以及一个 checks 数组。每项检查都有
section、status、message,以及包含补救步骤或支持信息的 details 数组。
即使检查失败,也会输出 JSON。
失败时退出代码为 1;除非设置了 --strict,否则有警告时退出代码为 0。
常规的人类可读输出也采用相同的检查和退出策略。
此命令检查安装和配置;不会执行会产生费用的调度。
若要通过脚本检查历史记录或保存完整结果:
agent-dispatch jobs --agent infra --status failed --limit 10 --json
agent-dispatch job <job_id> --json > job.json
jobs --json 会返回与 dispatch_jobs 格式相同的精简摘要数组;如果没有匹配的作业,则返回 []。每个摘要都包含 ID、Agent、状态、任务的前 120 个字符和时间戳;如果有可用信息,还会包含结果费用、成功状态、结果、错误类型和最新的运行进度。
job --json 会返回存储的完整作业,包括任务/上下文、进度、结果文本、结构化结果、会话 ID,以及可用时的恢复提示。
这两种格式都会保留 Unicode。读取失败的作业仍会成功退出:
请检查其 status 和 result.success 以确定调度结果。
缺失或无效的作业 ID,以及存储打开失败时,会返回 {"error": "..."},
退出代码为 1。无效的命令行选项由 Click 输出到 stderr。
无效或非有限的费用元数据会被视为未知,并从结果导出中省略;不会因此隐藏已存储的结果文本。使用情况摘要会忽略不可用的数值测量;如果服务的启动时间戳无法读取,doctor 会省略运行时长。
Requirements
- Python >= 3.10
- 已安装、完成身份验证并位于
PATH中的 Claude Code CLI(验证方法:claude --version)
License
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。