agent-dispatch 0.6.0__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_dispatch-0.8.0/AGENTS.md +57 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/CHANGELOG.md +61 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/PKG-INFO +110 -26
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/README.md +108 -24
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/agents.example.yaml +10 -1
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/pyproject.toml +15 -2
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/src/agent_dispatch/__init__.py +1 -1
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/src/agent_dispatch/cli.py +249 -40
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/src/agent_dispatch/config.py +8 -3
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/src/agent_dispatch/jobs.py +50 -16
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/src/agent_dispatch/models.py +13 -3
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/src/agent_dispatch/runner.py +169 -74
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/src/agent_dispatch/server.py +171 -86
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/tests/test_cli.py +420 -84
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/tests/test_config.py +30 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/tests/test_jobs.py +81 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/tests/test_models.py +18 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/tests/test_runner.py +160 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/tests/test_server.py +413 -144
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/.github/dependabot.yml +0 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/.github/workflows/ci.yml +0 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/.github/workflows/publish.yml +0 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/.gitignore +0 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/LICENSE +0 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/SECURITY.md +0 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/assets/mascot.png +0 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/src/agent_dispatch/cache.py +0 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/tests/__init__.py +0 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/tests/conftest.py +0 -0
- {agent_dispatch-0.6.0 → agent_dispatch-0.8.0}/tests/test_cache.py +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding agents working on this repository.
|
|
4
|
+
|
|
5
|
+
> **Using agent-dispatch** (not developing it)? Read [README.md](README.md) — it has the full setup path with verify steps and the complete MCP tool reference. This file is for contributing to the codebase.
|
|
6
|
+
|
|
7
|
+
## What this project is
|
|
8
|
+
|
|
9
|
+
MCP server + CLI that lets Claude Code agents delegate tasks to agents in other project directories. One sync core, two surfaces:
|
|
10
|
+
|
|
11
|
+
| File | Role |
|
|
12
|
+
|------|------|
|
|
13
|
+
| `src/agent_dispatch/runner.py` | Sync subprocess wrapper around `claude -p` — the actual work |
|
|
14
|
+
| `src/agent_dispatch/server.py` | Async FastMCP interface (19 MCP tools), wraps runner in `asyncio.to_thread` + semaphore |
|
|
15
|
+
| `src/agent_dispatch/cli.py` | Click CLI: `init`, `add`, `update`, `remove`, `list`, `describe`, `test`, `doctor`, `jobs`, `job`, `cancel`, `gc`, `serve` |
|
|
16
|
+
| `src/agent_dispatch/models.py` | Pydantic v2 models (`AgentConfig`, `Settings`, `DispatchResult`) |
|
|
17
|
+
| `src/agent_dispatch/config.py` | YAML config load/save + project auto-description |
|
|
18
|
+
| `src/agent_dispatch/cache.py` | Thread-safe in-memory TTL cache |
|
|
19
|
+
| `src/agent_dispatch/jobs.py` | Persistent per-job JSON files for async dispatch |
|
|
20
|
+
|
|
21
|
+
## Dev setup
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install -e ".[dev]"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Gates — both must pass before a change is done (CI rejects otherwise)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
ruff check src/ tests/
|
|
31
|
+
python3 -m pytest tests/ -v # 428 tests, ~2s — all subprocess calls are mocked
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Tests must **never** invoke the real `claude` CLI. Runner tests mock `shutil.which` + `subprocess.run`/`Popen`; server tests mock `_get_config` + `runner.dispatch`.
|
|
35
|
+
|
|
36
|
+
## Non-obvious invariants (violating these breaks real behavior)
|
|
37
|
+
|
|
38
|
+
- `allowed_tools` / `disallowed_tools` are **tri-state**: `None` = inherit settings defaults, `[]` = explicitly no tools, `[...]` = exactly these. Check with `is not None`, never `or` — `[]` is falsy but semantically distinct.
|
|
39
|
+
- `denied_tools` non-empty + `is_error` ⇒ `error_type="permission"`, regardless of what the error text matches.
|
|
40
|
+
- On failure, callers read `DispatchResult.error` + `error_type` — `result` holds the raw agent output even on errors.
|
|
41
|
+
- `--session-id` and `--resume` conflict — never pass both to `claude`.
|
|
42
|
+
- Valid permission modes: `default`, `plan`, `bypassPermissions` (`models.py: KNOWN_PERMISSION_MODES`).
|
|
43
|
+
- `JobStore.finish`/`fail` refuse already-terminal jobs (returns `None`) — this closes the race with force-cancel; never "fix" it by overwriting.
|
|
44
|
+
- Cancelling a *running* job requires the in-memory `_running_procs` registry (server.py) — the job is marked `cancelled` **before** the subprocess is killed. Don't persist PIDs to disk (PID reuse after restart could kill an unrelated process).
|
|
45
|
+
- `max_budget_usd` is **post-hoc**: `_apply_budget` (runner.py) sets `budget_exceeded` + `hint` after the cost is known; it never fails the dispatch.
|
|
46
|
+
|
|
47
|
+
## Conventions
|
|
48
|
+
|
|
49
|
+
Python ≥ 3.10 · `from __future__ import annotations` everywhere · Pydantic v2 · Click (CLI) + FastMCP (server) · ruff, line length 100 · all MCP tools return JSON strings, errors as `{"error": "..."}`.
|
|
50
|
+
|
|
51
|
+
## When adding a feature, check every layer
|
|
52
|
+
|
|
53
|
+
`models.py` (data shape) → `runner.py` (dispatch mechanics) → `server.py` (MCP tool) → `cli.py` (CLI flag) → tests for each → `README.md` + `agents.example.yaml` (user docs).
|
|
54
|
+
|
|
55
|
+
## More detail
|
|
56
|
+
|
|
57
|
+
[README.md](README.md) documents every MCP tool with parameter tables, response shapes, and the error-recovery map — it doubles as the behavioral spec. The test suite (`tests/`, 428 tests) encodes the exact expected behavior of every layer: when in doubt, read the tests for the module you're touching (`test_runner.py`, `test_server.py`, `test_cli.py`, ...).
|
|
@@ -7,6 +7,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.0] - 2026-06-17
|
|
11
|
+
|
|
12
|
+
Let agents declare what they are good at, so callers can pick the right one.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **Declared capabilities.** `AgentConfig` gains `capabilities` and
|
|
16
|
+
`risky_capabilities` — short snake_case labels describing what an agent is
|
|
17
|
+
for (e.g. `docker_logs`, `restart_services`). They are descriptive metadata
|
|
18
|
+
only (never passed to the `claude` CLI): settable via `add_agent` /
|
|
19
|
+
`update_agent` (MCP) and `add` / `update` (CLI, `--capabilities` /
|
|
20
|
+
`--risky-capabilities`, `none` clears), and surfaced in `list_agents` /
|
|
21
|
+
`inspect_agent` so the calling agent can choose a target at a glance.
|
|
22
|
+
`risky_capabilities` flags higher-risk abilities for extra scrutiny.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- `save_config` no longer writes empty `capabilities` / `risky_capabilities`
|
|
26
|
+
keys for agents that don't declare them, keeping `agents.yaml` clean.
|
|
27
|
+
|
|
28
|
+
### Note
|
|
29
|
+
- A keyword-scoring router (`recommend_agent` / `dispatch_auto` MCP tools and
|
|
30
|
+
`recommend` / `auto` CLI commands) was prototyped during this cycle and then
|
|
31
|
+
removed before release: a deterministic keyword scorer adds little over the
|
|
32
|
+
calling LLM's own judgment when there are only a handful of agents, and the
|
|
33
|
+
capability labels above cover the "what is this agent for" need without the
|
|
34
|
+
extra surface area or the risk of auto-dispatching to a wrong guess.
|
|
35
|
+
|
|
36
|
+
## [0.7.0] - 2026-06-10
|
|
37
|
+
|
|
38
|
+
Job control release: running jobs become cancellable, the budget field stops
|
|
39
|
+
being decorative, and async jobs get a CLI.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- **Cancel running jobs.** `dispatch_cancel(job_id)` now kills a *running*
|
|
43
|
+
job's `claude` subprocess when the job was started by the same server
|
|
44
|
+
instance (in-memory process registry — no PID files, no risk of killing an
|
|
45
|
+
unrelated process after a restart). The job is marked `cancelled` *before*
|
|
46
|
+
the kill, and `JobStore.finish`/`fail` now refuse already-terminal jobs, so
|
|
47
|
+
the worker's trailing write can't resurrect it. New outcome:
|
|
48
|
+
`cancelled_running`. Jobs from a previous server run still report
|
|
49
|
+
`running` (cannot be killed safely).
|
|
50
|
+
- **Budget visibility (post-hoc).** `max_budget_usd` was stored and displayed
|
|
51
|
+
but never checked. A dispatch whose `cost_usd` exceeds the agent's
|
|
52
|
+
`max_budget_usd` (or `settings.default_max_budget_usd`) now returns
|
|
53
|
+
`budget_exceeded: true` plus a `hint`. The dispatch is *not* failed — the
|
|
54
|
+
`claude` CLI has no spend cap, so by the time the cost is known the money is
|
|
55
|
+
spent; the flag makes runaway agents visible instead of silent.
|
|
56
|
+
- **CLI for async jobs.** New commands: `agent-dispatch jobs [--status
|
|
57
|
+
--limit]` (list), `agent-dispatch job <id>` (detail with progress tail and
|
|
58
|
+
result preview), `agent-dispatch cancel <id>` (pending jobs; running jobs
|
|
59
|
+
belong to the MCP server process), `agent-dispatch gc [--days]` (purge old
|
|
60
|
+
terminal jobs).
|
|
61
|
+
- **PyPI discoverability:** expanded package keywords (5 → 12).
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
- `runner.dispatch_stream` accepts an `on_proc` callback (receives the Popen
|
|
65
|
+
handle right after spawn) — used by the async worker to register the
|
|
66
|
+
process for cancellation.
|
|
67
|
+
- `JobStore.cancel` accepts `force=True` to cancel running jobs (callers must
|
|
68
|
+
kill the subprocess themselves); `finish`/`fail` return `None` for terminal
|
|
69
|
+
jobs instead of overwriting them.
|
|
70
|
+
|
|
10
71
|
## [0.6.0] - 2026-06-04
|
|
11
72
|
|
|
12
73
|
Reliability release: timeouts stop being fatal, permission-blocked "successes"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-dispatch
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: MCP server that lets Claude Code agents delegate tasks to agents in other project directories
|
|
5
5
|
Project-URL: Homepage, https://github.com/ginkida/agent-dispatch
|
|
6
6
|
Project-URL: Repository, https://github.com/ginkida/agent-dispatch
|
|
@@ -8,7 +8,7 @@ Project-URL: Issues, https://github.com/ginkida/agent-dispatch/issues
|
|
|
8
8
|
Author: ginkida
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Keywords: agent,claude,dispatch,mcp,multi-agent
|
|
11
|
+
Keywords: agent,agent-orchestration,ai-agents,anthropic,claude,claude-code,delegation,dispatch,mcp,mcp-server,multi-agent,subagents
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -45,29 +45,61 @@ Each agent runs as a separate `claude -p` session in its own project directory
|
|
|
45
45
|
|
|
46
46
|
Works with OAuth, API key, and Claude subscription authentication.
|
|
47
47
|
|
|
48
|
+
> **AI agents:** this README is the canonical doc for *using* the tool — setup: [Quick Start](#quick-start) (every step has a deterministic verify), first call: [`dispatch`](#dispatch), tool selection: [Which Tool to Use](#which-tool-to-use), failure handling: [Error Recovery](#error-recovery). Working *on* this repo instead? See [AGENTS.md](AGENTS.md).
|
|
49
|
+
|
|
48
50
|
## Quick Start
|
|
49
51
|
|
|
52
|
+
**Prerequisite:** the [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) must be installed and authenticated. Check first:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
claude --version # must print a version — if it fails, install Claude Code before continuing
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then:
|
|
59
|
+
|
|
50
60
|
```bash
|
|
51
|
-
pip install agent-dispatch
|
|
61
|
+
pip install agent-dispatch # or: pipx install agent-dispatch
|
|
52
62
|
|
|
53
|
-
#
|
|
63
|
+
# 1. Create config + register the MCP server with Claude Code (user scope)
|
|
54
64
|
agent-dispatch init
|
|
55
65
|
|
|
56
|
-
#
|
|
66
|
+
# 2. Register project directories as agents — REPLACE the example paths with
|
|
67
|
+
# real directories on your machine; they must exist (~ is expanded, relative
|
|
68
|
+
# paths are resolved). Descriptions are auto-generated from project files.
|
|
69
|
+
# No second project handy? Use the zero-setup block below instead.
|
|
57
70
|
agent-dispatch add infra ~/projects/infra
|
|
58
71
|
agent-dispatch add backend ~/projects/backend
|
|
59
72
|
|
|
60
|
-
#
|
|
73
|
+
# 3. Smoke test — dispatches a real task to the agent added in step 2 and prints
|
|
74
|
+
# the answer; exit 0 on success. Default task when none given:
|
|
75
|
+
# "What project is this? Describe in one sentence."
|
|
61
76
|
agent-dispatch test infra
|
|
62
77
|
|
|
63
|
-
#
|
|
64
|
-
agent-dispatch update infra --permission-mode bypassPermissions
|
|
65
|
-
|
|
66
|
-
# If something doesn't work, run the diagnostic:
|
|
78
|
+
# 4. Verify the whole install — prints "All checks passed." and exits 0 on success
|
|
67
79
|
agent-dispatch doctor
|
|
68
80
|
```
|
|
69
81
|
|
|
70
|
-
|
|
82
|
+
**Zero-setup alternative** for steps 2–3 (no second project needed — registers the current directory):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
agent-dispatch add self . && agent-dispatch test self "Say hello"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Every Claude Code session now has the dispatch tools. Independent check: `claude mcp list` must print a line starting with `agent-dispatch:`. From inside a Claude Code session, the first MCP calls are `list_agents()`, then [`dispatch(...)`](#dispatch).
|
|
89
|
+
|
|
90
|
+
**If `init` fails to register the MCP server** (prints a warning instead of `Registered MCP server`), register manually:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
claude mcp add-json agent-dispatch "{\"type\":\"stdio\",\"command\":\"$(which agent-dispatch)\",\"args\":[\"serve\"]}" --scope user
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**If `test` fails with a permission error** (`error_type: "permission"`), grant tool access and re-test:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
agent-dispatch update infra --allowed-tools "Bash,Read,Grep" # least privilege
|
|
100
|
+
# or, if the agent needs everything (see SECURITY.md for the trade-off):
|
|
101
|
+
agent-dispatch update infra --permission-mode bypassPermissions
|
|
102
|
+
```
|
|
71
103
|
|
|
72
104
|
## When to Dispatch
|
|
73
105
|
|
|
@@ -97,6 +129,8 @@ Lists all configured agents. **Call this first** to see what's available.
|
|
|
97
129
|
"mcp_servers": ["portainer", "postgres"],
|
|
98
130
|
"stacks": ["Python", "Docker"],
|
|
99
131
|
"dbs": ["Alembic"],
|
|
132
|
+
"capabilities": ["docker_logs", "deploy_debug"],
|
|
133
|
+
"risky_capabilities": ["restart_services"],
|
|
100
134
|
"permission_mode": "bypassPermissions",
|
|
101
135
|
"allowed_tools": ["Bash", "Read", "Grep"]
|
|
102
136
|
}
|
|
@@ -132,6 +166,17 @@ One-shot task delegation. Results are cached — identical requests within TTL r
|
|
|
132
166
|
| `summary_chars` | int | no | Max chars of result text to include in the ref response (default 500). |
|
|
133
167
|
| `timeout_seconds` | int | no | One-off timeout override for this call (0 = agent's configured timeout; clamped to 10–7200). No config edit needed for known-long tasks. |
|
|
134
168
|
|
|
169
|
+
```python
|
|
170
|
+
# Call — recommended form (always include caller and goal)
|
|
171
|
+
dispatch(
|
|
172
|
+
agent="infra", # must exist in list_agents()
|
|
173
|
+
task="Check container logs for errors related to the scheduler service",
|
|
174
|
+
context="Error: TypeError at scheduler.py:42",
|
|
175
|
+
caller="backend", # your project/role
|
|
176
|
+
goal="debug production crash" # the broader objective
|
|
177
|
+
)
|
|
178
|
+
```
|
|
179
|
+
|
|
135
180
|
```json
|
|
136
181
|
// Response (success)
|
|
137
182
|
{
|
|
@@ -307,9 +352,10 @@ Register a new project directory as an agent. Description is auto-generated from
|
|
|
307
352
|
| Parameter | Type | Required | Description |
|
|
308
353
|
|-----------|------|----------|-------------|
|
|
309
354
|
| `name` | string | yes | Agent name (letters, digits, hyphens, underscores) |
|
|
310
|
-
| `directory` | string | yes |
|
|
355
|
+
| `directory` | string | yes | Path to an existing project directory (`~` is expanded, relative paths resolved) |
|
|
311
356
|
| `description` | string | no | What this agent can do — auto-generated if empty |
|
|
312
357
|
| `timeout` | int | no | Timeout in seconds (0 = use global default) |
|
|
358
|
+
| `max_budget_usd` | float | no | Max cost in USD per dispatch (0 = no limit) |
|
|
313
359
|
| `permission_mode` | string | no | Permission mode (e.g. `default`, `plan`, `bypassPermissions`) |
|
|
314
360
|
| `allowed_tools` | string | no | Comma-separated allowed tools (e.g. `"Bash,Read,Edit"`) |
|
|
315
361
|
| `disallowed_tools` | string | no | Comma-separated disallowed tools |
|
|
@@ -323,6 +369,7 @@ Update an existing agent's configuration. Only non-empty fields are changed. Pas
|
|
|
323
369
|
| `name` | string | yes | Agent name to update |
|
|
324
370
|
| `description` | string | no | New description |
|
|
325
371
|
| `timeout` | int | no | New timeout (0 = don't change) |
|
|
372
|
+
| `max_budget_usd` | float | no | New budget limit (0 = don't change, negative = clear the limit) |
|
|
326
373
|
| `model` | string | no | Model override. `"none"` to clear |
|
|
327
374
|
| `permission_mode` | string | no | Permission mode. `"none"` to clear |
|
|
328
375
|
| `allowed_tools` | string | no | Comma-separated. `"none"` to clear |
|
|
@@ -373,7 +420,7 @@ dispatch_status(job_id="8f3a...e1")
|
|
|
373
420
|
-> {"id": "8f3a...e1", "status": "running", "started_at": 1730000123.4,
|
|
374
421
|
"progress": ["Using tool: Bash", "Scanning container logs for OOM events..."], ...}
|
|
375
422
|
|
|
376
|
-
// 3. or block until done (
|
|
423
|
+
// 3. or block until done (timeout_seconds default: 60, capped at 3600)
|
|
377
424
|
dispatch_wait(job_id="8f3a...e1", timeout_seconds=120)
|
|
378
425
|
-> {"id": "8f3a...e1", "status": "done", "result": {"agent": "infra", "success": true, ...}}
|
|
379
426
|
|
|
@@ -381,13 +428,13 @@ dispatch_wait(job_id="8f3a...e1", timeout_seconds=120)
|
|
|
381
428
|
-> {"id": "...", "status": "running", "timed_out_waiting": true}
|
|
382
429
|
```
|
|
383
430
|
|
|
384
|
-
`dispatch_cancel(job_id)` cancels a job
|
|
431
|
+
`dispatch_cancel(job_id)` cancels a **pending** job, and also kills a **running** job's `claude` subprocess when the job was started by the same server instance (the job is marked `cancelled` first, so the worker's trailing write can't undo it; partial work is lost but the progress tail is preserved). A running job started by a *previous* server run can't be killed safely and is left to finish. The response carries an `outcome` of `cancelled`, `cancelled_running`, `running` (not owned by this server), `already_terminal`, or `not_found`.
|
|
385
432
|
|
|
386
433
|
Async workers run with streaming under the hood: the job file keeps a rolling tail (last 20 lines, ~1 write/sec) of assistant text and tool-use events. `dispatch_status` shows it as `progress` while the job runs and keeps it afterwards as a post-mortem trace; `dispatch_jobs` shows `last_progress` for running jobs.
|
|
387
434
|
|
|
388
435
|
`dispatch_jobs(status?)` lists recent jobs as summaries (filter by `pending` / `running` / `done` / `failed` / `cancelled`). `dispatch_gc(max_age_days=7)` purges terminal jobs older than the threshold — pending and running jobs are never deleted.
|
|
389
436
|
|
|
390
|
-
Job state persists to disk at `~/.config/agent-dispatch/jobs/` (override with `AGENT_DISPATCH_JOBS_DIR`). One JSON file per job, written owner-only (`0o600`) with atomic writes — safe to read or `ls` while jobs are in flight. Caller-supplied `job_id`s are validated as 32-char hex before any file access (no path traversal). On startup the server marks jobs
|
|
437
|
+
Job state persists to disk at `~/.config/agent-dispatch/jobs/` (override with `AGENT_DISPATCH_JOBS_DIR`). One JSON file per job, written owner-only (`0o600`) with atomic writes — safe to read or `ls` while jobs are in flight. Caller-supplied `job_id`s are validated as 32-char hex before any file access (no path traversal). On startup the server marks jobs left in `running` by a crashed instance as `failed` once they are stale (stuck for over an hour).
|
|
391
438
|
|
|
392
439
|
| When to use async | When to use `dispatch` |
|
|
393
440
|
|-------------------|------------------------|
|
|
@@ -395,14 +442,6 @@ Job state persists to disk at `~/.config/agent-dispatch/jobs/` (override with `A
|
|
|
395
442
|
| Several long tasks you'll collect later | Several short tasks → `dispatch_parallel` |
|
|
396
443
|
| Don't care about caching (each call is a fresh job) | Cached by default — identical requests are free |
|
|
397
444
|
|
|
398
|
-
### Error Responses
|
|
399
|
-
|
|
400
|
-
All tools return errors as:
|
|
401
|
-
|
|
402
|
-
```json
|
|
403
|
-
{"error": "Unknown agent: 'foo'. Available: infra, db, monitoring"}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
445
|
## Which Tool to Use
|
|
407
446
|
|
|
408
447
|
| Scenario | Tool |
|
|
@@ -418,6 +457,30 @@ All tools return errors as:
|
|
|
418
457
|
| Known-long task, one-off | any dispatch tool with `timeout_seconds=...` |
|
|
419
458
|
| A dispatch timed out | `dispatch_session` with the `session_id` from the error |
|
|
420
459
|
|
|
460
|
+
## Error Recovery
|
|
461
|
+
|
|
462
|
+
Failures are deterministic: check `success`, then branch on `error_type`.
|
|
463
|
+
|
|
464
|
+
| `error_type` | Meaning | Recovery |
|
|
465
|
+
|--------------|---------|----------|
|
|
466
|
+
| `permission` | A tool call was denied | `update_agent(name, allowed_tools="Bash,Read")` (least privilege) or `update_agent(name, permission_mode="bypassPermissions")`, then re-dispatch. The `error` text includes a hint with the exact fix. |
|
|
467
|
+
| `timeout` | Process killed at the timeout | Resume the partial work: `dispatch_session(agent, "Continue where you left off", session_id=<from the error text>)`. Or retry with a bigger `timeout_seconds=`, or use `dispatch_async`. |
|
|
468
|
+
| `not_found` | Agent directory or `claude` CLI missing | `list_agents()` → check `healthy`. Re-add the agent with an existing path, or run `agent-dispatch doctor` to find what's missing. |
|
|
469
|
+
| `recursion` | Dispatch nesting exceeded `max_dispatch_depth` (default 3) | Don't dispatch from dispatched agents; if the nesting is intentional, raise `max_dispatch_depth` in settings. |
|
|
470
|
+
| `cli_error` | Anything else from the `claude` subprocess | Read the `error` text; run `agent-dispatch doctor` for environment issues; retry once if transient. |
|
|
471
|
+
|
|
472
|
+
Three soft signals that arrive with `success: true`:
|
|
473
|
+
|
|
474
|
+
- **`denied_tools` + `hint`** — the agent finished but some tool calls were blocked; the result may be incomplete. Grant access (see the `permission` row) and re-dispatch.
|
|
475
|
+
- **`parsed_result: null` with `response_format="json"`** — the reply wasn't valid JSON; the raw text is still in `result`. Caveat: an agent that *can't* comply returns `{"error": "<reason>"}` — which parses successfully — so also check `parsed_result` for an `"error"` key.
|
|
476
|
+
- **`budget_exceeded: true`** — `cost_usd` exceeded the agent's `max_budget_usd` (or the settings default). The dispatch is not failed — the money is already spent — but a runaway agent is now visible. Tighten the task, pick a cheaper model, or raise the budget.
|
|
477
|
+
|
|
478
|
+
Tool-level errors (unknown agent, malformed input) return a plain envelope instead of a `DispatchResult`:
|
|
479
|
+
|
|
480
|
+
```json
|
|
481
|
+
{"error": "Unknown agent: 'foo'. Available: infra, db, monitoring"}
|
|
482
|
+
```
|
|
483
|
+
|
|
421
484
|
## Configuration
|
|
422
485
|
|
|
423
486
|
Config at `~/.config/agent-dispatch/agents.yaml` (override: `AGENT_DISPATCH_CONFIG` env var):
|
|
@@ -428,9 +491,14 @@ agents:
|
|
|
428
491
|
directory: ~/projects/infra
|
|
429
492
|
description: "Infrastructure agent. MCP: portainer."
|
|
430
493
|
timeout: 300 # seconds, default: 300
|
|
494
|
+
capabilities: # capability labels, shown in list_agents
|
|
495
|
+
- docker_logs
|
|
496
|
+
- deploy_debug
|
|
497
|
+
risky_capabilities: # high-risk labels, surfaced for visibility
|
|
498
|
+
- restart_services
|
|
431
499
|
# model: sonnet # optional model override
|
|
432
500
|
# max_budget_usd: 1.0 # cost limit per dispatch
|
|
433
|
-
# permission_mode:
|
|
501
|
+
# permission_mode: bypassPermissions # one of: default | plan | bypassPermissions
|
|
434
502
|
# allowed_tools: # restrict which tools the agent can use
|
|
435
503
|
# - Read
|
|
436
504
|
# - Grep
|
|
@@ -465,6 +533,18 @@ Config is reloaded on every tool call — add agents without restarting.
|
|
|
465
533
|
- Stack indicators — Docker, Rust, Go, Python, Node.js
|
|
466
534
|
- DB indicators — Prisma, Alembic, migrations
|
|
467
535
|
|
|
536
|
+
### Explicit Capabilities
|
|
537
|
+
|
|
538
|
+
Auto-description is useful, but explicit `capabilities` make it clearer what each agent is for. Add short snake_case task labels to agents:
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
agent-dispatch update infra \
|
|
542
|
+
--capabilities docker_logs,deploy_debug \
|
|
543
|
+
--risky-capabilities restart_services
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
`list_agents` and `inspect_agent` surface `capabilities` and `risky_capabilities` so the caller can pick the right agent at a glance — `risky_capabilities` flags higher-risk abilities (e.g. restarting services) for extra scrutiny.
|
|
547
|
+
|
|
468
548
|
## How It Works
|
|
469
549
|
|
|
470
550
|
```
|
|
@@ -491,7 +571,7 @@ agent-dispatch MCP server
|
|
|
491
571
|
- **Argument-injection guard** — structured CLI fields (`session_id`, `model`, `permission_mode`, tool names) that start with `-` are rejected so they can't smuggle extra `claude` flags.
|
|
492
572
|
- **Path-traversal guard** — caller-supplied `job_id`/`ref` values are validated as 32-char hex before any filesystem access.
|
|
493
573
|
- **Owner-only state** — job files (`0o600`) and `agents.yaml` (`0o600`) are written for the owner only; their directories are `0o700`.
|
|
494
|
-
- **Cost
|
|
574
|
+
- **Cost visibility** — `max_budget_usd` per agent or globally; a dispatch whose cost exceeds it returns `budget_exceeded: true` + a hint (post-hoc — the `claude` CLI has no spend cap, so the overage can be flagged but not prevented).
|
|
495
575
|
- **Concurrency** — `max_concurrency` (default: 5) caps parallel `claude -p` processes. Note: the sync and async dispatch paths use separate semaphores, so the worst-case total is `2 × max_concurrency`.
|
|
496
576
|
- **Timeout** — per-agent or global (default: 300s). Orphaned processes are cleaned up.
|
|
497
577
|
- **Caching** — identical `(agent, task, context, caller, goal, response_format)` requests return cached results, bounded by `cache.max_size` (oldest entry evicted first). Only successes are cached. Sessions and dialogues are never cached.
|
|
@@ -510,12 +590,16 @@ See [SECURITY.md](SECURITY.md) for the full threat model (including the `bypassP
|
|
|
510
590
|
| `agent-dispatch describe <name>` | Show full configuration for one agent (tri-state tools, project files) |
|
|
511
591
|
| `agent-dispatch test <name> [task] [--stream]` | Test an agent with a dispatch (`--stream` for live progress) |
|
|
512
592
|
| `agent-dispatch doctor` | Diagnose installation: claude CLI, MCP registration, agent health |
|
|
593
|
+
| `agent-dispatch jobs [--status --limit]` | List async dispatch jobs (most recent first) |
|
|
594
|
+
| `agent-dispatch job <id>` | Show one job: status, progress tail, result preview |
|
|
595
|
+
| `agent-dispatch cancel <id>` | Cancel a pending job (running jobs: use the `dispatch_cancel` MCP tool) |
|
|
596
|
+
| `agent-dispatch gc [--days]` | Purge terminal jobs older than N days (default 7) |
|
|
513
597
|
| `agent-dispatch serve` | Start MCP server (stdio, used by Claude Code) |
|
|
514
598
|
|
|
515
599
|
## Requirements
|
|
516
600
|
|
|
517
601
|
- Python >= 3.10
|
|
518
|
-
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and
|
|
602
|
+
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed, authenticated, and on `PATH` (verify: `claude --version`)
|
|
519
603
|
|
|
520
604
|
## License
|
|
521
605
|
|
|
@@ -15,29 +15,61 @@ Each agent runs as a separate `claude -p` session in its own project directory
|
|
|
15
15
|
|
|
16
16
|
Works with OAuth, API key, and Claude subscription authentication.
|
|
17
17
|
|
|
18
|
+
> **AI agents:** this README is the canonical doc for *using* the tool — setup: [Quick Start](#quick-start) (every step has a deterministic verify), first call: [`dispatch`](#dispatch), tool selection: [Which Tool to Use](#which-tool-to-use), failure handling: [Error Recovery](#error-recovery). Working *on* this repo instead? See [AGENTS.md](AGENTS.md).
|
|
19
|
+
|
|
18
20
|
## Quick Start
|
|
19
21
|
|
|
22
|
+
**Prerequisite:** the [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) must be installed and authenticated. Check first:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
claude --version # must print a version — if it fails, install Claude Code before continuing
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then:
|
|
29
|
+
|
|
20
30
|
```bash
|
|
21
|
-
pip install agent-dispatch
|
|
31
|
+
pip install agent-dispatch # or: pipx install agent-dispatch
|
|
22
32
|
|
|
23
|
-
#
|
|
33
|
+
# 1. Create config + register the MCP server with Claude Code (user scope)
|
|
24
34
|
agent-dispatch init
|
|
25
35
|
|
|
26
|
-
#
|
|
36
|
+
# 2. Register project directories as agents — REPLACE the example paths with
|
|
37
|
+
# real directories on your machine; they must exist (~ is expanded, relative
|
|
38
|
+
# paths are resolved). Descriptions are auto-generated from project files.
|
|
39
|
+
# No second project handy? Use the zero-setup block below instead.
|
|
27
40
|
agent-dispatch add infra ~/projects/infra
|
|
28
41
|
agent-dispatch add backend ~/projects/backend
|
|
29
42
|
|
|
30
|
-
#
|
|
43
|
+
# 3. Smoke test — dispatches a real task to the agent added in step 2 and prints
|
|
44
|
+
# the answer; exit 0 on success. Default task when none given:
|
|
45
|
+
# "What project is this? Describe in one sentence."
|
|
31
46
|
agent-dispatch test infra
|
|
32
47
|
|
|
33
|
-
#
|
|
34
|
-
agent-dispatch update infra --permission-mode bypassPermissions
|
|
35
|
-
|
|
36
|
-
# If something doesn't work, run the diagnostic:
|
|
48
|
+
# 4. Verify the whole install — prints "All checks passed." and exits 0 on success
|
|
37
49
|
agent-dispatch doctor
|
|
38
50
|
```
|
|
39
51
|
|
|
40
|
-
|
|
52
|
+
**Zero-setup alternative** for steps 2–3 (no second project needed — registers the current directory):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
agent-dispatch add self . && agent-dispatch test self "Say hello"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Every Claude Code session now has the dispatch tools. Independent check: `claude mcp list` must print a line starting with `agent-dispatch:`. From inside a Claude Code session, the first MCP calls are `list_agents()`, then [`dispatch(...)`](#dispatch).
|
|
59
|
+
|
|
60
|
+
**If `init` fails to register the MCP server** (prints a warning instead of `Registered MCP server`), register manually:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
claude mcp add-json agent-dispatch "{\"type\":\"stdio\",\"command\":\"$(which agent-dispatch)\",\"args\":[\"serve\"]}" --scope user
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**If `test` fails with a permission error** (`error_type: "permission"`), grant tool access and re-test:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
agent-dispatch update infra --allowed-tools "Bash,Read,Grep" # least privilege
|
|
70
|
+
# or, if the agent needs everything (see SECURITY.md for the trade-off):
|
|
71
|
+
agent-dispatch update infra --permission-mode bypassPermissions
|
|
72
|
+
```
|
|
41
73
|
|
|
42
74
|
## When to Dispatch
|
|
43
75
|
|
|
@@ -67,6 +99,8 @@ Lists all configured agents. **Call this first** to see what's available.
|
|
|
67
99
|
"mcp_servers": ["portainer", "postgres"],
|
|
68
100
|
"stacks": ["Python", "Docker"],
|
|
69
101
|
"dbs": ["Alembic"],
|
|
102
|
+
"capabilities": ["docker_logs", "deploy_debug"],
|
|
103
|
+
"risky_capabilities": ["restart_services"],
|
|
70
104
|
"permission_mode": "bypassPermissions",
|
|
71
105
|
"allowed_tools": ["Bash", "Read", "Grep"]
|
|
72
106
|
}
|
|
@@ -102,6 +136,17 @@ One-shot task delegation. Results are cached — identical requests within TTL r
|
|
|
102
136
|
| `summary_chars` | int | no | Max chars of result text to include in the ref response (default 500). |
|
|
103
137
|
| `timeout_seconds` | int | no | One-off timeout override for this call (0 = agent's configured timeout; clamped to 10–7200). No config edit needed for known-long tasks. |
|
|
104
138
|
|
|
139
|
+
```python
|
|
140
|
+
# Call — recommended form (always include caller and goal)
|
|
141
|
+
dispatch(
|
|
142
|
+
agent="infra", # must exist in list_agents()
|
|
143
|
+
task="Check container logs for errors related to the scheduler service",
|
|
144
|
+
context="Error: TypeError at scheduler.py:42",
|
|
145
|
+
caller="backend", # your project/role
|
|
146
|
+
goal="debug production crash" # the broader objective
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
105
150
|
```json
|
|
106
151
|
// Response (success)
|
|
107
152
|
{
|
|
@@ -277,9 +322,10 @@ Register a new project directory as an agent. Description is auto-generated from
|
|
|
277
322
|
| Parameter | Type | Required | Description |
|
|
278
323
|
|-----------|------|----------|-------------|
|
|
279
324
|
| `name` | string | yes | Agent name (letters, digits, hyphens, underscores) |
|
|
280
|
-
| `directory` | string | yes |
|
|
325
|
+
| `directory` | string | yes | Path to an existing project directory (`~` is expanded, relative paths resolved) |
|
|
281
326
|
| `description` | string | no | What this agent can do — auto-generated if empty |
|
|
282
327
|
| `timeout` | int | no | Timeout in seconds (0 = use global default) |
|
|
328
|
+
| `max_budget_usd` | float | no | Max cost in USD per dispatch (0 = no limit) |
|
|
283
329
|
| `permission_mode` | string | no | Permission mode (e.g. `default`, `plan`, `bypassPermissions`) |
|
|
284
330
|
| `allowed_tools` | string | no | Comma-separated allowed tools (e.g. `"Bash,Read,Edit"`) |
|
|
285
331
|
| `disallowed_tools` | string | no | Comma-separated disallowed tools |
|
|
@@ -293,6 +339,7 @@ Update an existing agent's configuration. Only non-empty fields are changed. Pas
|
|
|
293
339
|
| `name` | string | yes | Agent name to update |
|
|
294
340
|
| `description` | string | no | New description |
|
|
295
341
|
| `timeout` | int | no | New timeout (0 = don't change) |
|
|
342
|
+
| `max_budget_usd` | float | no | New budget limit (0 = don't change, negative = clear the limit) |
|
|
296
343
|
| `model` | string | no | Model override. `"none"` to clear |
|
|
297
344
|
| `permission_mode` | string | no | Permission mode. `"none"` to clear |
|
|
298
345
|
| `allowed_tools` | string | no | Comma-separated. `"none"` to clear |
|
|
@@ -343,7 +390,7 @@ dispatch_status(job_id="8f3a...e1")
|
|
|
343
390
|
-> {"id": "8f3a...e1", "status": "running", "started_at": 1730000123.4,
|
|
344
391
|
"progress": ["Using tool: Bash", "Scanning container logs for OOM events..."], ...}
|
|
345
392
|
|
|
346
|
-
// 3. or block until done (
|
|
393
|
+
// 3. or block until done (timeout_seconds default: 60, capped at 3600)
|
|
347
394
|
dispatch_wait(job_id="8f3a...e1", timeout_seconds=120)
|
|
348
395
|
-> {"id": "8f3a...e1", "status": "done", "result": {"agent": "infra", "success": true, ...}}
|
|
349
396
|
|
|
@@ -351,13 +398,13 @@ dispatch_wait(job_id="8f3a...e1", timeout_seconds=120)
|
|
|
351
398
|
-> {"id": "...", "status": "running", "timed_out_waiting": true}
|
|
352
399
|
```
|
|
353
400
|
|
|
354
|
-
`dispatch_cancel(job_id)` cancels a job
|
|
401
|
+
`dispatch_cancel(job_id)` cancels a **pending** job, and also kills a **running** job's `claude` subprocess when the job was started by the same server instance (the job is marked `cancelled` first, so the worker's trailing write can't undo it; partial work is lost but the progress tail is preserved). A running job started by a *previous* server run can't be killed safely and is left to finish. The response carries an `outcome` of `cancelled`, `cancelled_running`, `running` (not owned by this server), `already_terminal`, or `not_found`.
|
|
355
402
|
|
|
356
403
|
Async workers run with streaming under the hood: the job file keeps a rolling tail (last 20 lines, ~1 write/sec) of assistant text and tool-use events. `dispatch_status` shows it as `progress` while the job runs and keeps it afterwards as a post-mortem trace; `dispatch_jobs` shows `last_progress` for running jobs.
|
|
357
404
|
|
|
358
405
|
`dispatch_jobs(status?)` lists recent jobs as summaries (filter by `pending` / `running` / `done` / `failed` / `cancelled`). `dispatch_gc(max_age_days=7)` purges terminal jobs older than the threshold — pending and running jobs are never deleted.
|
|
359
406
|
|
|
360
|
-
Job state persists to disk at `~/.config/agent-dispatch/jobs/` (override with `AGENT_DISPATCH_JOBS_DIR`). One JSON file per job, written owner-only (`0o600`) with atomic writes — safe to read or `ls` while jobs are in flight. Caller-supplied `job_id`s are validated as 32-char hex before any file access (no path traversal). On startup the server marks jobs
|
|
407
|
+
Job state persists to disk at `~/.config/agent-dispatch/jobs/` (override with `AGENT_DISPATCH_JOBS_DIR`). One JSON file per job, written owner-only (`0o600`) with atomic writes — safe to read or `ls` while jobs are in flight. Caller-supplied `job_id`s are validated as 32-char hex before any file access (no path traversal). On startup the server marks jobs left in `running` by a crashed instance as `failed` once they are stale (stuck for over an hour).
|
|
361
408
|
|
|
362
409
|
| When to use async | When to use `dispatch` |
|
|
363
410
|
|-------------------|------------------------|
|
|
@@ -365,14 +412,6 @@ Job state persists to disk at `~/.config/agent-dispatch/jobs/` (override with `A
|
|
|
365
412
|
| Several long tasks you'll collect later | Several short tasks → `dispatch_parallel` |
|
|
366
413
|
| Don't care about caching (each call is a fresh job) | Cached by default — identical requests are free |
|
|
367
414
|
|
|
368
|
-
### Error Responses
|
|
369
|
-
|
|
370
|
-
All tools return errors as:
|
|
371
|
-
|
|
372
|
-
```json
|
|
373
|
-
{"error": "Unknown agent: 'foo'. Available: infra, db, monitoring"}
|
|
374
|
-
```
|
|
375
|
-
|
|
376
415
|
## Which Tool to Use
|
|
377
416
|
|
|
378
417
|
| Scenario | Tool |
|
|
@@ -388,6 +427,30 @@ All tools return errors as:
|
|
|
388
427
|
| Known-long task, one-off | any dispatch tool with `timeout_seconds=...` |
|
|
389
428
|
| A dispatch timed out | `dispatch_session` with the `session_id` from the error |
|
|
390
429
|
|
|
430
|
+
## Error Recovery
|
|
431
|
+
|
|
432
|
+
Failures are deterministic: check `success`, then branch on `error_type`.
|
|
433
|
+
|
|
434
|
+
| `error_type` | Meaning | Recovery |
|
|
435
|
+
|--------------|---------|----------|
|
|
436
|
+
| `permission` | A tool call was denied | `update_agent(name, allowed_tools="Bash,Read")` (least privilege) or `update_agent(name, permission_mode="bypassPermissions")`, then re-dispatch. The `error` text includes a hint with the exact fix. |
|
|
437
|
+
| `timeout` | Process killed at the timeout | Resume the partial work: `dispatch_session(agent, "Continue where you left off", session_id=<from the error text>)`. Or retry with a bigger `timeout_seconds=`, or use `dispatch_async`. |
|
|
438
|
+
| `not_found` | Agent directory or `claude` CLI missing | `list_agents()` → check `healthy`. Re-add the agent with an existing path, or run `agent-dispatch doctor` to find what's missing. |
|
|
439
|
+
| `recursion` | Dispatch nesting exceeded `max_dispatch_depth` (default 3) | Don't dispatch from dispatched agents; if the nesting is intentional, raise `max_dispatch_depth` in settings. |
|
|
440
|
+
| `cli_error` | Anything else from the `claude` subprocess | Read the `error` text; run `agent-dispatch doctor` for environment issues; retry once if transient. |
|
|
441
|
+
|
|
442
|
+
Three soft signals that arrive with `success: true`:
|
|
443
|
+
|
|
444
|
+
- **`denied_tools` + `hint`** — the agent finished but some tool calls were blocked; the result may be incomplete. Grant access (see the `permission` row) and re-dispatch.
|
|
445
|
+
- **`parsed_result: null` with `response_format="json"`** — the reply wasn't valid JSON; the raw text is still in `result`. Caveat: an agent that *can't* comply returns `{"error": "<reason>"}` — which parses successfully — so also check `parsed_result` for an `"error"` key.
|
|
446
|
+
- **`budget_exceeded: true`** — `cost_usd` exceeded the agent's `max_budget_usd` (or the settings default). The dispatch is not failed — the money is already spent — but a runaway agent is now visible. Tighten the task, pick a cheaper model, or raise the budget.
|
|
447
|
+
|
|
448
|
+
Tool-level errors (unknown agent, malformed input) return a plain envelope instead of a `DispatchResult`:
|
|
449
|
+
|
|
450
|
+
```json
|
|
451
|
+
{"error": "Unknown agent: 'foo'. Available: infra, db, monitoring"}
|
|
452
|
+
```
|
|
453
|
+
|
|
391
454
|
## Configuration
|
|
392
455
|
|
|
393
456
|
Config at `~/.config/agent-dispatch/agents.yaml` (override: `AGENT_DISPATCH_CONFIG` env var):
|
|
@@ -398,9 +461,14 @@ agents:
|
|
|
398
461
|
directory: ~/projects/infra
|
|
399
462
|
description: "Infrastructure agent. MCP: portainer."
|
|
400
463
|
timeout: 300 # seconds, default: 300
|
|
464
|
+
capabilities: # capability labels, shown in list_agents
|
|
465
|
+
- docker_logs
|
|
466
|
+
- deploy_debug
|
|
467
|
+
risky_capabilities: # high-risk labels, surfaced for visibility
|
|
468
|
+
- restart_services
|
|
401
469
|
# model: sonnet # optional model override
|
|
402
470
|
# max_budget_usd: 1.0 # cost limit per dispatch
|
|
403
|
-
# permission_mode:
|
|
471
|
+
# permission_mode: bypassPermissions # one of: default | plan | bypassPermissions
|
|
404
472
|
# allowed_tools: # restrict which tools the agent can use
|
|
405
473
|
# - Read
|
|
406
474
|
# - Grep
|
|
@@ -435,6 +503,18 @@ Config is reloaded on every tool call — add agents without restarting.
|
|
|
435
503
|
- Stack indicators — Docker, Rust, Go, Python, Node.js
|
|
436
504
|
- DB indicators — Prisma, Alembic, migrations
|
|
437
505
|
|
|
506
|
+
### Explicit Capabilities
|
|
507
|
+
|
|
508
|
+
Auto-description is useful, but explicit `capabilities` make it clearer what each agent is for. Add short snake_case task labels to agents:
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
agent-dispatch update infra \
|
|
512
|
+
--capabilities docker_logs,deploy_debug \
|
|
513
|
+
--risky-capabilities restart_services
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
`list_agents` and `inspect_agent` surface `capabilities` and `risky_capabilities` so the caller can pick the right agent at a glance — `risky_capabilities` flags higher-risk abilities (e.g. restarting services) for extra scrutiny.
|
|
517
|
+
|
|
438
518
|
## How It Works
|
|
439
519
|
|
|
440
520
|
```
|
|
@@ -461,7 +541,7 @@ agent-dispatch MCP server
|
|
|
461
541
|
- **Argument-injection guard** — structured CLI fields (`session_id`, `model`, `permission_mode`, tool names) that start with `-` are rejected so they can't smuggle extra `claude` flags.
|
|
462
542
|
- **Path-traversal guard** — caller-supplied `job_id`/`ref` values are validated as 32-char hex before any filesystem access.
|
|
463
543
|
- **Owner-only state** — job files (`0o600`) and `agents.yaml` (`0o600`) are written for the owner only; their directories are `0o700`.
|
|
464
|
-
- **Cost
|
|
544
|
+
- **Cost visibility** — `max_budget_usd` per agent or globally; a dispatch whose cost exceeds it returns `budget_exceeded: true` + a hint (post-hoc — the `claude` CLI has no spend cap, so the overage can be flagged but not prevented).
|
|
465
545
|
- **Concurrency** — `max_concurrency` (default: 5) caps parallel `claude -p` processes. Note: the sync and async dispatch paths use separate semaphores, so the worst-case total is `2 × max_concurrency`.
|
|
466
546
|
- **Timeout** — per-agent or global (default: 300s). Orphaned processes are cleaned up.
|
|
467
547
|
- **Caching** — identical `(agent, task, context, caller, goal, response_format)` requests return cached results, bounded by `cache.max_size` (oldest entry evicted first). Only successes are cached. Sessions and dialogues are never cached.
|
|
@@ -480,12 +560,16 @@ See [SECURITY.md](SECURITY.md) for the full threat model (including the `bypassP
|
|
|
480
560
|
| `agent-dispatch describe <name>` | Show full configuration for one agent (tri-state tools, project files) |
|
|
481
561
|
| `agent-dispatch test <name> [task] [--stream]` | Test an agent with a dispatch (`--stream` for live progress) |
|
|
482
562
|
| `agent-dispatch doctor` | Diagnose installation: claude CLI, MCP registration, agent health |
|
|
563
|
+
| `agent-dispatch jobs [--status --limit]` | List async dispatch jobs (most recent first) |
|
|
564
|
+
| `agent-dispatch job <id>` | Show one job: status, progress tail, result preview |
|
|
565
|
+
| `agent-dispatch cancel <id>` | Cancel a pending job (running jobs: use the `dispatch_cancel` MCP tool) |
|
|
566
|
+
| `agent-dispatch gc [--days]` | Purge terminal jobs older than N days (default 7) |
|
|
483
567
|
| `agent-dispatch serve` | Start MCP server (stdio, used by Claude Code) |
|
|
484
568
|
|
|
485
569
|
## Requirements
|
|
486
570
|
|
|
487
571
|
- Python >= 3.10
|
|
488
|
-
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and
|
|
572
|
+
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed, authenticated, and on `PATH` (verify: `claude --version`)
|
|
489
573
|
|
|
490
574
|
## License
|
|
491
575
|
|