A swarm is a team of concurrently-running specialist agents that coordinate on related tasks. Swarms build on top of the specialist system (see [[05-specialists|Specialists]]) — each swarm member is a specialist child process — but add team management, inter-agent communication (mailboxes), task handoffs, and recurring scheduling.
A swarm is not created explicitly. There is no swarm init command. Instead, the swarm materializes the first time the agent calls the swarm_spawn tool. A Swarm instance is constructed at agent startup and wired into the tool registry.
Parent Agent (Orchestrator)
│
├── swarm_spawn(team="backend", agent_type="worker", task="Implement API")
│ │
│ ▼
│ Backend Team (max 8 members)
│ ├── worker-1 (running)
│ ├── worker-2 (running)
│ └── worker-3 (queued)
│
├── swarm_spawn(team="frontend", agent_type="worker", task="Build UI")
│ │
│ ▼
│ Frontend Team (max 8 members)
│ ├── worker-4 (running)
│ └── worker-5 (running)
│
└── swarm_collect(team="backend") ← blocks until backend team finishes
Seven tools are available for swarm coordination:
swarm_spawnSpawn a member of a given agent type into a team:
{
"name": "swarm_spawn",
"arguments": {
"agent_type": "worker",
"task": "Implement the /health endpoint with database and cache checks",
"team": "backend",
"description": "Health endpoint implementation"
}
}
Returns a task_id immediately. The member runs concurrently — the orchestrator is not blocked.
| Parameter | Required | Description |
|---|---|---|
agent_type |
Yes | Roster agent type (teammate, subagent, or custom) |
task |
Yes | The briefing/prompt handed to the spawned member |
team |
No | Team name (defaults to "default") |
description |
No | Short label for the task |
swarm_sendSend a message to another member’s inbox:
{
"name": "swarm_send",
"arguments": {
"to": "worker-2",
"team": "backend",
"subject": "API schema ready",
"body": "The /health endpoint returns {status, db, cache}. You can start the frontend now.",
"type": "notification"
}
}
swarm_inboxRead a member’s inbox (marks messages as read):
{
"name": "swarm_inbox",
"arguments": {
"member": "worker-1",
"team": "backend"
}
}
Returns all messages, with their read flags as they were before this read.
swarm_statusOne-shot snapshot of all tasks (no waiting):
{
"name": "swarm_status",
"arguments": {
"team": "backend"
}
}
Returns each task’s id, agent type, status, description, and timestamps.
swarm_handoffTransfer a task to a fresh member of another agent type:
{
"name": "swarm_handoff",
"arguments": {
"task_id": "task-003",
"to_agent_type": "code-review",
"note": "Implementation is done, please review the changes"
}
}
The original task is marked handed-off; a new task is registered for the code-review member with the note delivered to its inbox before the new task is registered (so a mailbox failure can’t leave a phantom pending task).
swarm_collectBlock until a team’s tasks finish, then return all results:
{
"name": "swarm_collect",
"arguments": {
"team": "backend",
"timeout": "300s"
}
}
This replaces polling — instead of calling swarm_status in a loop, call swarm_collect once and wait for completion. Bounded by the timeout (user-cancel or deadline).
swarm_scheduleSchedule recurring spawns (interval or daily):
{
"name": "swarm_schedule",
"arguments": {
"action": "add",
"agent_type": "worker",
"task": "Run the test suite and report failures",
"team": "ci",
"every": "30m"
}
}
Or daily at a specific time:
{
"name": "swarm_schedule",
"arguments": {
"action": "add",
"agent_type": "worker",
"task": "Generate a daily progress summary",
"daily_at": "09:00"
}
}
Schedule management:
| Action | Parameters |
|---|---|
add |
agent_type, task, team, every OR daily_at, first_delay, max_runs |
list |
(none — lists all schedules) |
cancel |
job_id |
The roster defines the available agent types that can be spawned. Two built-in types:
| Agent Type | Description | Use case |
|---|---|---|
teammate |
In-process teammate for parallel task execution | General-purpose work within the team |
subagent |
General-purpose subagent for an isolated, delegated task | Tasks that need zero prior context |
You can register custom agent types (via the Registry.Register API in Go code). The agent_type parameter on swarm_spawn / swarm_handoff is constrained to registered types via a JSON Schema enum, so the model cannot spawn an unknown type.
A team is a named set of concurrently-running members with a bounded slot count:
| Property | Default | Config |
|---|---|---|
| Max team size | 8 | config.json → "swarm": {"maxTeamSize": 8} |
| Queue | FIFO | Spawns past the cap queue and launch as slots free up |
Teams are created lazily — the first swarm_spawn with team="backend" creates the “backend” team.
Team names are sanitized: lowercase, [a-z0-9-] only, no path separators. A name can never escape the swarm base directory.
Each team member has a per-agent inbox persisted as a JSON file on disk:
<workspace>/.pvyai/swarm/<team>/inboxes/<agent-name>.json
{
"from": "worker-1",
"to": "worker-2",
"subject": "API schema ready",
"body": "The /health endpoint returns {status, db, cache}. Start the frontend now.",
"type": "notification",
"read": false,
"time": "2026-07-07T15:30:00Z"
}
0600 / 0700)pending → running → done
→ failed
→ handed-off (transferred to another agent)
| Status | Meaning |
|---|---|
pending |
Registered but not started yet (queued) |
running |
The member is executing |
done |
Completed successfully |
failed |
Exited with an error |
handed-off |
Ownership transferred to another agent |
Terminal states (done, failed, handed-off) cannot transition out — fail closed.
If a member fails temporarily (e.g. transient error), it’s relaunched up to 2 times (maxMemberRestarts = 2) before being marked failed.
If a member crashes without completing (e.g. killed by the OS), its tasks can be adopted by AdoptOrphans, which re-parents orphaned tasks onto fresh members. Terminal tasks and tasks with a live owner are left alone.
Every member inherits the orchestrator’s model and permission mode at spawn time:
| Orchestrator mode | Member autonomy |
|---|---|
spec-draft |
(swarms not available) |
ask |
auto low (read-only auto-approve, prompts for writes) |
auto |
auto low (same) |
unsafe |
auto high (full auto-approve) |
A member never runs on a different model than requested and never has more permission than its parent.
Swarm tools are deferred-eligible — they’re hidden behind tool_search (loaded on demand) instead of shipping their full schemas in every request. This saves context tokens when swarms aren’t in use.
Once a swarm is active (any member spawned), the coordination tools (swarm_send, swarm_status, swarm_inbox, swarm_collect) un-defer — they become eagerly available so a weaker model can see them without needing tool_search. The entry-point tools (swarm_spawn, swarm_schedule, swarm_handoff) stay deferred.
User: "Investigate the auth module, the database layer, and the API handlers"
Agent:
swarm_spawn(team="exploration", agent_type="explorer", task="Map the auth module")
swarm_spawn(team="exploration", agent_type="explorer", task="Map the database layer")
swarm_spawn(team="exploration", agent_type="explorer", task="Map the API handlers")
results = swarm_collect(team="exploration")
→ Synthesize findings from all three
Agent:
swarm_spawn(team="pipeline", agent_type="worker", task="Implement the feature")
results = swarm_collect(team="pipeline")
swarm_handoff(task_id=<completed>, to_agent_type="code-review",
note="Review the implementation for correctness")
review = swarm_collect(team="pipeline")
Agent:
swarm_spawn(team="backend", agent_type="worker", task="Implement /api/users CRUD")
swarm_spawn(team="frontend", agent_type="worker", task="Build the users list page")
# Frontend waits for backend's API schema
swarm_send(to="frontend-worker", team="frontend",
subject="API ready",
body="GET /api/users returns {id, name, email}. POST /api/users expects {name, email}.")
backend_results = swarm_collect(team="backend")
frontend_results = swarm_collect(team="frontend")
Agent:
swarm_schedule(agent_type="worker",
task="Run go test ./... and report any failures with the failing test name and error",
team="ci",
every="30m")
{
"swarm": {
"maxTeamSize": 8
}
}
| Field | Default | Description |
|---|---|---|
maxTeamSize |
8 | Maximum concurrent members per team |
<workspace>/.pvyai/swarm/
└── <team>/
└── inboxes/
├── worker-1.json
├── worker-2.json
└── code-review-1.json
The swarm state lives inside the sandbox write root so the agent can always write to it.
3 specialists · 2 running · 1 completed · 4.5k tokens total/ps: Show all running tasks/specialistsClick a specialist card (or press Enter) to open the member’s child session. You can see its full conversation, tool calls, and results.
| Practice | Why |
|---|---|
Use swarm_collect instead of polling |
One call, no retry loops, bounded by timeout |
| Keep tasks self-contained | Members start with zero context — include everything they need |
| Use read-only agents for exploration | Auto-approved, no prompts, parallel |
Set maxTeamSize based on your model |
More members = more concurrent cost |
| Communicate via mailboxes | Members are separate processes — they can’t share memory |
Hand off to code-review after implementation |
Clean separation of concerns |