agent-dispatch 0.8.0__tar.gz → 0.10.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.
Files changed (31) hide show
  1. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/AGENTS.md +7 -6
  2. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/CHANGELOG.md +58 -0
  3. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/PKG-INFO +58 -4
  4. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/README.md +57 -3
  5. agent_dispatch-0.10.0/agents.example.yaml +105 -0
  6. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/pyproject.toml +1 -1
  7. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/src/agent_dispatch/__init__.py +1 -1
  8. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/src/agent_dispatch/cli.py +206 -1
  9. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/src/agent_dispatch/config.py +43 -9
  10. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/src/agent_dispatch/jobs.py +7 -6
  11. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/src/agent_dispatch/models.py +61 -2
  12. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/src/agent_dispatch/server.py +201 -10
  13. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/tests/test_cli.py +171 -0
  14. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/tests/test_config.py +84 -2
  15. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/tests/test_jobs.py +14 -0
  16. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/tests/test_models.py +59 -1
  17. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/tests/test_server.py +252 -0
  18. agent_dispatch-0.8.0/agents.example.yaml +0 -57
  19. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/.github/dependabot.yml +0 -0
  20. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/.github/workflows/ci.yml +0 -0
  21. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/.github/workflows/publish.yml +0 -0
  22. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/.gitignore +0 -0
  23. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/LICENSE +0 -0
  24. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/SECURITY.md +0 -0
  25. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/assets/mascot.png +0 -0
  26. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/src/agent_dispatch/cache.py +0 -0
  27. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/src/agent_dispatch/runner.py +0 -0
  28. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/tests/__init__.py +0 -0
  29. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/tests/conftest.py +0 -0
  30. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/tests/test_cache.py +0 -0
  31. {agent_dispatch-0.8.0 → agent_dispatch-0.10.0}/tests/test_runner.py +0 -0
@@ -11,9 +11,9 @@ MCP server + CLI that lets Claude Code agents delegate tasks to agents in other
11
11
  | File | Role |
12
12
  |------|------|
13
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`) |
14
+ | `src/agent_dispatch/server.py` | Async FastMCP interface (21 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`, `group` (add/list/inspect/update/remove), `serve` |
16
+ | `src/agent_dispatch/models.py` | Pydantic v2 models (`AgentConfig`, `DispatchGroup`/`GroupMember`, `Settings`, `DispatchResult`) |
17
17
  | `src/agent_dispatch/config.py` | YAML config load/save + project auto-description |
18
18
  | `src/agent_dispatch/cache.py` | Thread-safe in-memory TTL cache |
19
19
  | `src/agent_dispatch/jobs.py` | Persistent per-job JSON files for async dispatch |
@@ -28,7 +28,7 @@ pip install -e ".[dev]"
28
28
 
29
29
  ```bash
30
30
  ruff check src/ tests/
31
- python3 -m pytest tests/ -v # 428 tests, ~2s — all subprocess calls are mocked
31
+ python3 -m pytest tests/ -v # 466 tests, ~2s — all subprocess calls are mocked
32
32
  ```
33
33
 
34
34
  Tests must **never** invoke the real `claude` CLI. Runner tests mock `shutil.which` + `subprocess.run`/`Popen`; server tests mock `_get_config` + `runner.dispatch`.
@@ -37,6 +37,7 @@ Tests must **never** invoke the real `claude` CLI. Runner tests mock `shutil.whi
37
37
 
38
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
39
  - `denied_tools` non-empty + `is_error` ⇒ `error_type="permission"`, regardless of what the error text matches.
40
+ - **Groups**: a group's `shared_context` is folded into the `context` *string* before the cache/runner calls (`_merge_group_context` in server.py) — runner.py and cache.py are untouched, the cache key disambiguates groups for free, and `group=""` is byte-identical to a plain dispatch. Membership is validated up front (`_validate_group_member`, separate from the pure merge so `dispatch_parallel`'s all-or-nothing pre-check holds). `DispatchConfig` validates only group *keys*, never member existence — a hard cross-ref check would brick config load when a shared gateway agent is removed; dangling refs are flagged (`unknown:true`) at read time instead.
40
41
  - On failure, callers read `DispatchResult.error` + `error_type` — `result` holds the raw agent output even on errors.
41
42
  - `--session-id` and `--resume` conflict — never pass both to `claude`.
42
43
  - Valid permission modes: `default`, `plan`, `bypassPermissions` (`models.py: KNOWN_PERMISSION_MODES`).
@@ -50,8 +51,8 @@ Python ≥ 3.10 · `from __future__ import annotations` everywhere · Pydantic v
50
51
 
51
52
  ## When adding a feature, check every layer
52
53
 
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
+ `models.py` (data shape) → `config.py` (YAML round-trip + empty-collection pruning) → `runner.py` (dispatch mechanics) → `server.py` (MCP tool) → `cli.py` (CLI flag) → tests for each → `README.md` + `agents.example.yaml` (user docs).
54
55
 
55
56
  ## More detail
56
57
 
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`, ...).
58
+ [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/`, 466 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,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.10.0] - 2026-07-14
11
+
12
+ `doctor` learns to check groups; three correctness fixes found in review.
13
+
14
+ ### Added
15
+ - **`agent-dispatch doctor` diagnoses group health.** A new "Groups" section
16
+ reports each group's member count, flags dangling members (agents removed
17
+ from config but still referenced) as a failure, and empty groups as a
18
+ warning — each with a concrete remediation command.
19
+
20
+ ### Fixed
21
+ - `auto_describe()`: an empty `"description"` field in `package.json` (a
22
+ common `npm init` placeholder) was no longer being filtered out, producing
23
+ malformed generated descriptions like `" | Stack: Node.js"`. Empty/blank
24
+ descriptions are ignored again.
25
+ - `doctor`'s group remediation hints pointed at `group update --members`,
26
+ a flag that doesn't exist (`group update` only edits `description` /
27
+ `shared_context`; membership is set via `group add --member`). Hints now
28
+ point at the working `group remove` + `group add --member` recreation path.
29
+ - `JobStore.mark_running()` now refuses to start any job that isn't
30
+ `pending` (previously only refused `cancelled`), closing a race where a
31
+ stale/duplicate worker could resurrect an already-finished or failed job.
32
+
33
+ ### Changed
34
+ - Consolidated the "unknown group member" check — previously duplicated
35
+ across five call sites in `cli.py` and `server.py` — into a single
36
+ `DispatchConfig.unknown_group_members()` helper.
37
+
38
+ ## [0.9.0] - 2026-06-30
39
+
40
+ Coordinate a group of related projects from one session.
41
+
42
+ ### Added
43
+ - **Project groups.** A new `groups` mapping in the config bundles related
44
+ agents — code repos plus capability gateways like infra (Portainer) or
45
+ analytics (browser / Yandex Metrica) — into a cross-project working set.
46
+ Each group has an orchestrator-facing `description` (how to coordinate, never
47
+ sent to members) and a member-facing `shared_context` of facts (stack names,
48
+ ids, conventions). Members reference agents by name; membership is
49
+ many-to-many (a shared gateway can belong to several groups). A group is a
50
+ *descriptive layer*, not an execution engine — there is no router; the
51
+ orchestrating LLM coordinates with the existing dispatch tools.
52
+ - **`list_groups()` / `inspect_group(name)` MCP tools** — cheap, no-subprocess
53
+ readouts of groups, their briefs, and members (dangling member refs are
54
+ flagged, never crash). For a deep dive on a member, use `inspect_agent`.
55
+ - **`group=` on `dispatch` and per-item in `dispatch_parallel`** — when set,
56
+ the agent must be a member of the group and the group's `shared_context` is
57
+ auto-prepended to the call's `context`. Folded into the context string, so
58
+ the cache key disambiguates groups automatically and `group=""` is byte-for-
59
+ byte identical to a plain dispatch. Parallel validates membership up front
60
+ (one bad item rejects the whole call before any subprocess runs).
61
+ - **`agent-dispatch group` CLI** — `add` / `list` / `inspect` / `update` /
62
+ `remove` for managing groups, mirroring the agent commands.
63
+
64
+ ### Changed
65
+ - `save_config` prunes an empty `groups` block and empty member lists so
66
+ group-less configs stay clean in YAML (same idiom as capabilities).
67
+
10
68
  ## [0.8.0] - 2026-06-17
11
69
 
12
70
  Let agents declare what they are good at, so callers can pick the right one.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-dispatch
3
- Version: 0.8.0
3
+ Version: 0.10.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
@@ -43,6 +43,8 @@ Description-Content-Type: text/markdown
43
43
 
44
44
  Each agent runs as a separate `claude -p` session in its own project directory — inheriting that project's MCP servers, CLAUDE.md, and tools. The calling agent just gets the result back.
45
45
 
46
+ Related projects can be bundled into a **[group](#groups)** — a shared brief plus a member list — so one session can coordinate work across them (e.g. code repos + an `infra`/Portainer gateway + an `analytics` gateway).
47
+
46
48
  Works with OAuth, API key, and Claude subscription authentication.
47
49
 
48
50
  > **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).
@@ -150,6 +152,37 @@ Cheap detailed lookup — reads the agent's files without spawning a `claude` se
150
152
 
151
153
  Use this **before** `dispatch_async`/`dispatch` to confirm an agent has the tools and context for your task — much cheaper than a probe dispatch.
152
154
 
155
+ ### Groups
156
+
157
+ A **group** bundles related agents into a cross-project working set — typically a few code repos plus capability gateways (an `infra` agent with a Portainer MCP, an `analytics` agent with a browser + Yandex Metrica). It lets one orchestrating session coordinate work that spans code, deploy, and verification.
158
+
159
+ A group is a **descriptive layer, not an execution engine** — there is no router and no state machine. You pick members by reading their hints and coordinate with the normal dispatch tools. Two text fields target two audiences:
160
+
161
+ - `description` — **orchestrator-facing**: how to coordinate the group (the order of steps, who to call for what). Surfaced by `list_groups`/`inspect_group`, **never** injected into a member's prompt.
162
+ - `shared_context` — **member-facing facts** (stack names, counter ids, conventions) that hold regardless of which member reads them. Auto-prepended to a member's `context` when you pass `group=`.
163
+
164
+ Members reference agents by name; membership is many-to-many (a shared gateway can belong to several groups). Manage groups with the `agent-dispatch group` CLI (`add`/`list`/`inspect`/`update`/`remove`) or by editing `agents.yaml`.
165
+
166
+ **`list_groups()`** — cheap, no-subprocess readout of every group: description, member count, and each member's `use_for` hint + health. A member whose agent was removed is flagged `"unknown": true` rather than crashing.
167
+
168
+ **`inspect_group(name)`** — one group's full brief: `description`, the complete `shared_context`, and the member list. For a deep dive on a specific member, call `inspect_agent(member)` — `inspect_group` deliberately stays a cheap membership readout.
169
+
170
+ **Using a group** — pass `group=` to `dispatch` (or per-item in `dispatch_parallel`). The agent must be a member; its group's `shared_context` rides along automatically:
171
+
172
+ ```python
173
+ # From the shop-web codebase, hand the deploy to the infra gateway.
174
+ # The "shop" group's facts (stack name, counter id) are auto-attached.
175
+ dispatch(
176
+ agent="infra",
177
+ task="Redeploy the shop-web container",
178
+ caller="shop-web",
179
+ goal="ship the checkout fix",
180
+ group="shop",
181
+ )
182
+ ```
183
+
184
+ `group=""` (the default) is byte-for-byte identical to a plain dispatch — the shared facts are folded into the `context` string, so the result cache disambiguates groups automatically and group-less calls are unaffected.
185
+
153
186
  ### `dispatch`
154
187
 
155
188
  One-shot task delegation. Results are cached — identical requests within TTL return instantly.
@@ -165,6 +198,7 @@ One-shot task delegation. Results are cached — identical requests within TTL r
165
198
  | `return_ref` | bool | no | When `true`, returns just a `ref` + summary preview instead of the full result text. Use `fetch_result(ref)` to load the full text on demand. |
166
199
  | `summary_chars` | int | no | Max chars of result text to include in the ref response (default 500). |
167
200
  | `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. |
201
+ | `group` | string | no | Group name (from `list_groups`). The agent must be a member; the group's `shared_context` (member-facing facts) is auto-prepended to `context`. Empty = plain dispatch. See [Groups](#groups). |
168
202
 
169
203
  ```python
170
204
  # Call — recommended form (always include caller and goal)
@@ -274,7 +308,7 @@ Run multiple tasks concurrently. Much faster than sequential `dispatch` calls.
274
308
 
275
309
  | Parameter | Type | Required | Description |
276
310
  |-----------|------|----------|-------------|
277
- | `dispatches` | string (JSON) | yes | JSON array of `{"agent", "task", "context?", "caller?", "goal?", "response_format?", "return_ref?", "summary_chars?", "timeout_seconds?"}` |
311
+ | `dispatches` | string (JSON) | yes | JSON array of `{"agent", "task", "context?", "caller?", "goal?", "response_format?", "return_ref?", "summary_chars?", "timeout_seconds?", "group?"}` (a per-item `group` validates membership up front and auto-injects its `shared_context`) |
278
312
  | `aggregate` | string | no | Agent name to synthesize all results into one answer |
279
313
 
280
314
  **Important:** `dispatches` is a JSON string, not a list.
@@ -456,6 +490,8 @@ Job state persists to disk at `~/.config/agent-dispatch/jobs/` (override with `A
456
490
  | Check progress without blocking | `dispatch_status` |
457
491
  | Known-long task, one-off | any dispatch tool with `timeout_seconds=...` |
458
492
  | A dispatch timed out | `dispatch_session` with the `session_id` from the error |
493
+ | Coordinating a set of related projects | define a [group](#groups), then `dispatch(..., group=name)` |
494
+ | See which agents form a working set | `list_groups` / `inspect_group` |
459
495
 
460
496
  ## Error Recovery
461
497
 
@@ -505,6 +541,23 @@ agents:
505
541
  # disallowed_tools: # block specific tools
506
542
  # - Write
507
543
 
544
+ # Optional: bundle related agents into a cross-project working set.
545
+ # A descriptive layer — no router; the orchestrating session coordinates
546
+ # with the normal dispatch tools. See the Groups section above.
547
+ groups:
548
+ shop:
549
+ # ORCHESTRATOR-facing: how to coordinate the group. Surfaced by
550
+ # list_groups/inspect_group, NEVER injected into a member's prompt.
551
+ description: "After a code change: deploy via infra, then verify via analytics."
552
+ # MEMBER-facing facts, auto-prepended to dispatch(..., group="shop").
553
+ shared_context: |
554
+ Prod runs in Portainer stack "shop". Metrica counter 12345.
555
+ members: # reference agents above (many-to-many)
556
+ - agent: infra
557
+ use_for: deploy, restart, container logs
558
+ # - agent: backend
559
+ # use_for: orders/payments endpoints
560
+
508
561
  settings:
509
562
  default_timeout: 300
510
563
  # default_permission_mode: bypassPermissions # inherited by all agents
@@ -574,7 +627,7 @@ agent-dispatch MCP server
574
627
  - **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).
575
628
  - **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`.
576
629
  - **Timeout** — per-agent or global (default: 300s). Orphaned processes are cleaned up.
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.
630
+ - **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. A `group=` dispatch folds the group's `shared_context` into `context`, so different groups cache separately and a plain dispatch is unaffected.
578
631
 
579
632
  See [SECURITY.md](SECURITY.md) for the full threat model (including the `bypassPermissions` escalation risk and on-disk job files).
580
633
 
@@ -587,9 +640,10 @@ See [SECURITY.md](SECURITY.md) for the full threat model (including the `bypassP
587
640
  | `agent-dispatch update <name>` | Update agent config (permissions, timeout, model, etc.) |
588
641
  | `agent-dispatch remove <name>` | Remove an agent |
589
642
  | `agent-dispatch list` | List agents with health status and permissions |
643
+ | `agent-dispatch group <add\|list\|inspect\|update\|remove>` | Manage [groups](#groups) — cross-project working sets of agents |
590
644
  | `agent-dispatch describe <name>` | Show full configuration for one agent (tri-state tools, project files) |
591
645
  | `agent-dispatch test <name> [task] [--stream]` | Test an agent with a dispatch (`--stream` for live progress) |
592
- | `agent-dispatch doctor` | Diagnose installation: claude CLI, MCP registration, agent health |
646
+ | `agent-dispatch doctor` | Diagnose installation: Claude CLI, MCP registration, agent health, and group membership |
593
647
  | `agent-dispatch jobs [--status --limit]` | List async dispatch jobs (most recent first) |
594
648
  | `agent-dispatch job <id>` | Show one job: status, progress tail, result preview |
595
649
  | `agent-dispatch cancel <id>` | Cancel a pending job (running jobs: use the `dispatch_cancel` MCP tool) |
@@ -13,6 +13,8 @@
13
13
 
14
14
  Each agent runs as a separate `claude -p` session in its own project directory — inheriting that project's MCP servers, CLAUDE.md, and tools. The calling agent just gets the result back.
15
15
 
16
+ Related projects can be bundled into a **[group](#groups)** — a shared brief plus a member list — so one session can coordinate work across them (e.g. code repos + an `infra`/Portainer gateway + an `analytics` gateway).
17
+
16
18
  Works with OAuth, API key, and Claude subscription authentication.
17
19
 
18
20
  > **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).
@@ -120,6 +122,37 @@ Cheap detailed lookup — reads the agent's files without spawning a `claude` se
120
122
 
121
123
  Use this **before** `dispatch_async`/`dispatch` to confirm an agent has the tools and context for your task — much cheaper than a probe dispatch.
122
124
 
125
+ ### Groups
126
+
127
+ A **group** bundles related agents into a cross-project working set — typically a few code repos plus capability gateways (an `infra` agent with a Portainer MCP, an `analytics` agent with a browser + Yandex Metrica). It lets one orchestrating session coordinate work that spans code, deploy, and verification.
128
+
129
+ A group is a **descriptive layer, not an execution engine** — there is no router and no state machine. You pick members by reading their hints and coordinate with the normal dispatch tools. Two text fields target two audiences:
130
+
131
+ - `description` — **orchestrator-facing**: how to coordinate the group (the order of steps, who to call for what). Surfaced by `list_groups`/`inspect_group`, **never** injected into a member's prompt.
132
+ - `shared_context` — **member-facing facts** (stack names, counter ids, conventions) that hold regardless of which member reads them. Auto-prepended to a member's `context` when you pass `group=`.
133
+
134
+ Members reference agents by name; membership is many-to-many (a shared gateway can belong to several groups). Manage groups with the `agent-dispatch group` CLI (`add`/`list`/`inspect`/`update`/`remove`) or by editing `agents.yaml`.
135
+
136
+ **`list_groups()`** — cheap, no-subprocess readout of every group: description, member count, and each member's `use_for` hint + health. A member whose agent was removed is flagged `"unknown": true` rather than crashing.
137
+
138
+ **`inspect_group(name)`** — one group's full brief: `description`, the complete `shared_context`, and the member list. For a deep dive on a specific member, call `inspect_agent(member)` — `inspect_group` deliberately stays a cheap membership readout.
139
+
140
+ **Using a group** — pass `group=` to `dispatch` (or per-item in `dispatch_parallel`). The agent must be a member; its group's `shared_context` rides along automatically:
141
+
142
+ ```python
143
+ # From the shop-web codebase, hand the deploy to the infra gateway.
144
+ # The "shop" group's facts (stack name, counter id) are auto-attached.
145
+ dispatch(
146
+ agent="infra",
147
+ task="Redeploy the shop-web container",
148
+ caller="shop-web",
149
+ goal="ship the checkout fix",
150
+ group="shop",
151
+ )
152
+ ```
153
+
154
+ `group=""` (the default) is byte-for-byte identical to a plain dispatch — the shared facts are folded into the `context` string, so the result cache disambiguates groups automatically and group-less calls are unaffected.
155
+
123
156
  ### `dispatch`
124
157
 
125
158
  One-shot task delegation. Results are cached — identical requests within TTL return instantly.
@@ -135,6 +168,7 @@ One-shot task delegation. Results are cached — identical requests within TTL r
135
168
  | `return_ref` | bool | no | When `true`, returns just a `ref` + summary preview instead of the full result text. Use `fetch_result(ref)` to load the full text on demand. |
136
169
  | `summary_chars` | int | no | Max chars of result text to include in the ref response (default 500). |
137
170
  | `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. |
171
+ | `group` | string | no | Group name (from `list_groups`). The agent must be a member; the group's `shared_context` (member-facing facts) is auto-prepended to `context`. Empty = plain dispatch. See [Groups](#groups). |
138
172
 
139
173
  ```python
140
174
  # Call — recommended form (always include caller and goal)
@@ -244,7 +278,7 @@ Run multiple tasks concurrently. Much faster than sequential `dispatch` calls.
244
278
 
245
279
  | Parameter | Type | Required | Description |
246
280
  |-----------|------|----------|-------------|
247
- | `dispatches` | string (JSON) | yes | JSON array of `{"agent", "task", "context?", "caller?", "goal?", "response_format?", "return_ref?", "summary_chars?", "timeout_seconds?"}` |
281
+ | `dispatches` | string (JSON) | yes | JSON array of `{"agent", "task", "context?", "caller?", "goal?", "response_format?", "return_ref?", "summary_chars?", "timeout_seconds?", "group?"}` (a per-item `group` validates membership up front and auto-injects its `shared_context`) |
248
282
  | `aggregate` | string | no | Agent name to synthesize all results into one answer |
249
283
 
250
284
  **Important:** `dispatches` is a JSON string, not a list.
@@ -426,6 +460,8 @@ Job state persists to disk at `~/.config/agent-dispatch/jobs/` (override with `A
426
460
  | Check progress without blocking | `dispatch_status` |
427
461
  | Known-long task, one-off | any dispatch tool with `timeout_seconds=...` |
428
462
  | A dispatch timed out | `dispatch_session` with the `session_id` from the error |
463
+ | Coordinating a set of related projects | define a [group](#groups), then `dispatch(..., group=name)` |
464
+ | See which agents form a working set | `list_groups` / `inspect_group` |
429
465
 
430
466
  ## Error Recovery
431
467
 
@@ -475,6 +511,23 @@ agents:
475
511
  # disallowed_tools: # block specific tools
476
512
  # - Write
477
513
 
514
+ # Optional: bundle related agents into a cross-project working set.
515
+ # A descriptive layer — no router; the orchestrating session coordinates
516
+ # with the normal dispatch tools. See the Groups section above.
517
+ groups:
518
+ shop:
519
+ # ORCHESTRATOR-facing: how to coordinate the group. Surfaced by
520
+ # list_groups/inspect_group, NEVER injected into a member's prompt.
521
+ description: "After a code change: deploy via infra, then verify via analytics."
522
+ # MEMBER-facing facts, auto-prepended to dispatch(..., group="shop").
523
+ shared_context: |
524
+ Prod runs in Portainer stack "shop". Metrica counter 12345.
525
+ members: # reference agents above (many-to-many)
526
+ - agent: infra
527
+ use_for: deploy, restart, container logs
528
+ # - agent: backend
529
+ # use_for: orders/payments endpoints
530
+
478
531
  settings:
479
532
  default_timeout: 300
480
533
  # default_permission_mode: bypassPermissions # inherited by all agents
@@ -544,7 +597,7 @@ agent-dispatch MCP server
544
597
  - **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).
545
598
  - **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`.
546
599
  - **Timeout** — per-agent or global (default: 300s). Orphaned processes are cleaned up.
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.
600
+ - **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. A `group=` dispatch folds the group's `shared_context` into `context`, so different groups cache separately and a plain dispatch is unaffected.
548
601
 
549
602
  See [SECURITY.md](SECURITY.md) for the full threat model (including the `bypassPermissions` escalation risk and on-disk job files).
550
603
 
@@ -557,9 +610,10 @@ See [SECURITY.md](SECURITY.md) for the full threat model (including the `bypassP
557
610
  | `agent-dispatch update <name>` | Update agent config (permissions, timeout, model, etc.) |
558
611
  | `agent-dispatch remove <name>` | Remove an agent |
559
612
  | `agent-dispatch list` | List agents with health status and permissions |
613
+ | `agent-dispatch group <add\|list\|inspect\|update\|remove>` | Manage [groups](#groups) — cross-project working sets of agents |
560
614
  | `agent-dispatch describe <name>` | Show full configuration for one agent (tri-state tools, project files) |
561
615
  | `agent-dispatch test <name> [task] [--stream]` | Test an agent with a dispatch (`--stream` for live progress) |
562
- | `agent-dispatch doctor` | Diagnose installation: claude CLI, MCP registration, agent health |
616
+ | `agent-dispatch doctor` | Diagnose installation: Claude CLI, MCP registration, agent health, and group membership |
563
617
  | `agent-dispatch jobs [--status --limit]` | List async dispatch jobs (most recent first) |
564
618
  | `agent-dispatch job <id>` | Show one job: status, progress tail, result preview |
565
619
  | `agent-dispatch cancel <id>` | Cancel a pending job (running jobs: use the `dispatch_cancel` MCP tool) |
@@ -0,0 +1,105 @@
1
+ agents:
2
+ # Infrastructure agent — has Portainer MCP for container management
3
+ infra:
4
+ directory: ~/projects/infra
5
+ description: "Infrastructure agent. MCP servers: portainer. Can check container logs, restart services, inspect Docker state."
6
+ capabilities:
7
+ - docker_logs
8
+ - deploy_debug
9
+ risky_capabilities:
10
+ - restart_services
11
+ timeout: 300
12
+
13
+ # Analytics gateway — a browser + Yandex Metrica agent, no codebase of its own.
14
+ # Its value is its MCP servers + access, not its source. Shared across groups.
15
+ analytics:
16
+ directory: ~/projects/analytics
17
+ description: "Analytics gateway. MCP servers: browser, yandex-metrica. Pulls funnels, conversion, traffic. Read-only."
18
+ capabilities:
19
+ - funnel_report
20
+ - conversion_metrics
21
+ permission_mode: bypassPermissions # runs non-interactively against read-only sources
22
+ timeout: 300
23
+
24
+ # Backend agent — source code, tests, database
25
+ backend:
26
+ directory: ~/projects/backend
27
+ description: "Backend API agent. Python, FastAPI, PostgreSQL. Can modify code, run tests, check migrations."
28
+ capabilities:
29
+ - code_debug
30
+ - run_tests
31
+ - inspect_migrations
32
+ timeout: 300
33
+ model: null # use default model
34
+
35
+ # Agent with permission controls — useful when you want to restrict tool access
36
+ staging-db:
37
+ directory: ~/projects/staging-db
38
+ description: "Read-only database agent. Can query staging DB but not modify data."
39
+ timeout: 120
40
+ permission_mode: bypassPermissions # skip permission prompts (agent runs non-interactively)
41
+ allowed_tools: # only these tools are available
42
+ - Read
43
+ - Grep
44
+ - Bash
45
+ # disallowed_tools: # alternatively, block specific tools
46
+ # - Write
47
+ # - Edit
48
+
49
+ # Frontend agent
50
+ # frontend:
51
+ # directory: ~/projects/frontend
52
+ # description: "Frontend React app. Can modify components, run build, check TypeScript errors."
53
+ # timeout: 180
54
+
55
+ # Groups bundle related agents (code repos + capability gateways like infra)
56
+ # into a cross-project working set. A group is a *descriptive layer* — there is
57
+ # no router and no execution engine; the orchestrating session coordinates with
58
+ # the normal dispatch tools. `members` reference agents above (many-to-many: a
59
+ # shared gateway can belong to several groups). Manage via `agent-dispatch
60
+ # group add/list/inspect/update/remove`; read via the list_groups /
61
+ # inspect_group MCP tools; auto-attach the brief via dispatch(..., group="shop").
62
+ groups:
63
+ shop:
64
+ # description = ORCHESTRATOR-facing: how to coordinate the group.
65
+ # Surfaced by list_groups/inspect_group, never injected into a member.
66
+ description: "E-commerce team. After a code change: deploy via infra, then verify the checkout funnel via the analytics gateway before reporting done."
67
+ # shared_context = MEMBER-facing FACTS, auto-prepended to group dispatches.
68
+ shared_context: |
69
+ Production runs in Portainer stack "shop".
70
+ Conversion is tracked in Yandex Metrica counter 12345.
71
+ members:
72
+ - agent: backend
73
+ use_for: orders/payments endpoints, migrations
74
+ - agent: infra
75
+ use_for: deploy, restart, container logs
76
+ - agent: analytics
77
+ use_for: funnel + conversion verification
78
+
79
+ # A second product reusing the SHARED infra/analytics gateways. Membership is
80
+ # many-to-many — gateways are referenced, not owned, so one analytics/infra
81
+ # agent serves every product.
82
+ blog:
83
+ description: "Content site. Same deploy-then-verify loop via the shared gateways."
84
+ shared_context: |
85
+ Production runs in Portainer stack "blog". Metrica counter 67890.
86
+ members:
87
+ - agent: infra
88
+ use_for: deploy, logs
89
+ - agent: analytics
90
+ use_for: pageview + bounce-rate checks
91
+
92
+ settings:
93
+ default_timeout: 300
94
+ max_dispatch_depth: 3 # recursion protection: A -> B -> A
95
+ max_concurrency: 5 # max parallel claude -p processes
96
+ # default_max_budget_usd: 1.0 # flags results over this cost (budget_exceeded + hint; post-hoc, can't prevent the spend)
97
+ # default_permission_mode: bypassPermissions # inherited by agents without override
98
+ # default_allowed_tools: # inherited by agents without override
99
+ # - Bash
100
+ # - Read
101
+ # - Edit
102
+ cache:
103
+ enabled: true
104
+ ttl: 300 # seconds; identical (agent, task, context) requests are cached
105
+ max_size: 1000 # max cached entries; oldest is evicted first (FIFO)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-dispatch"
3
- version = "0.8.0"
3
+ version = "0.10.0"
4
4
  description = "MCP server that lets Claude Code agents delegate tasks to agents in other project directories"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """agent-dispatch: Delegate tasks between Claude Code agents across projects."""
2
2
 
3
- __version__ = "0.8.0"
3
+ __version__ = "0.10.0"