Dispatch & Agent Teams
Route tasks to isolated sandbox VMs, parallel agent teams, or the user's local machine — automatically.
Overview
The dispatch system lets the orchestrator classify any task and send it to the right execution environment. Instead of everything running in one shared sandbox, the agent chooses where work happens:
User: "Audit our codebase security, implement the fixes, and test them in isolation"
↓
dispatch tool → mode: team_multi_vm
↓
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Auditor VM │ │ Implementer │ │ Tester VM │
│ (isolated) │ │ VM (isolated)│ │ (isolated) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
└─────────────────┴──────────────────┘
↓
Synthesizer collects results
↓
Final response in parent session
Dispatch Modes
| Mode | Description | Best for |
|---|---|---|
local | Child session in the current sandbox | Quick lookups, stateless Q&A, tasks under 30s |
remote | Fresh isolated ACA container from the warm pool | Long-running tasks, file-heavy work, anything that should not affect the current environment |
team_single_vm | Multiple parallel agents sharing this sandbox | Parallel subtasks that need the same filesystem |
team_multi_vm | Each team member gets its own isolated sandbox | Parallel work where per-task isolation matters (e.g. competing implementations) |
user_pc | Routes to the user's local machine via agent-tunnel | Desktop automation, local file access |
How the Agent Classifies
The orchestrator agent calls the dispatch tool before starting any non-trivial task. The LLM fills in the mode and a one-sentence rationale — there are no hard-coded rules:
Agent calls: dispatch(
mode: "remote",
rationale: "Task involves cloning an external repo and running tests — should not affect current environment",
task_summary: "Clone github.com/acme/service, run full test suite, report failures"
)
The tool checks pool availability, writes an audit row to dispatch_decisions, and returns next-step instructions to the agent. For remote/multi-VM modes, the agent follows up with await_dispatch to collect the result once it arrives.
Explicit hints
Users can override the classifier by saying things like:
"run this in a clean environment"→ forcesremote"do this async"→ forcesremote"use a team for this"→ forcesteam_single_vmorteam_multi_vm
Agent Teams
Teams are the multi-agent coordination primitive. A lead agent spawns teammates, each running in their own opencode session, coordinating through message passing.
How messaging works
- A teammate sends a message (
team_message) — it's appended to the recipient's JSONL inbox and injected directly into their session as a synthetic user message. - The recipient's prompt loop restarts automatically (auto-wake) — no polling needed.
- After processing, the recipient sends a delivery receipt back to the sender.
- Any member can message any other member directly — not just the lead.
Team tools
| Tool | What it does |
|---|---|
team_create | Create a named team (does not spawn members) |
team_spawn | Fire-and-forget spawn a teammate with a role and initial prompt |
team_message | Send a message to a specific member |
team_broadcast | Send a message to all members simultaneously |
team_tasks | View the status of all team members |
team_claim | Atomically claim a task from the shared list |
team_approve_plan | Lead approves/rejects the team's proposed plan |
team_shutdown | Gracefully cancel all members |
team_cleanup | Delete inbox files and remove team from memory |
Sub-agent isolation
When a team member spawns its own helper session (for searching, reading files, etc.), that helper cannot access any team tools. This prevents sub-agent chatter from flooding the coordination channel.
Sandbox Pool
The sandbox pool keeps warm ACA containers ready so remote and team_multi_vm dispatches start in milliseconds rather than waiting 30+ seconds for a cold container.
Pool lifecycle
provisioning → warm → claimed → draining → warm (recycled)
↘ destroying (pool full or unhealthy)
- The pool manager maintains at least
POOL_MINwarm containers at all times. - After a job completes, the container is wiped (sessions deleted) and returned to
warm— unless the pool is already atPOOL_MAX, in which case it is destroyed. - A health-check loop (every 30s) evicts unhealthy warm containers and triggers replenishment.
- An autoscaler (every 15s) grows the pool when jobs are queuing and shrinks it when idle, with 2-minute hysteresis to prevent flapping.
Pool env vars (on the worker)
| Variable | Description |
|---|---|
ACA_SUBSCRIPTION_ID | Azure subscription ID |
ACA_RESOURCE_GROUP | Resource group for sandbox containers |
ACA_ENVIRONMENT | Full resource ID of the ACA managed environment |
ACA_SANDBOX_IMAGE | ACR image to use for each sandbox container |
ACA_LOCATION | Azure region (e.g. eastus) |
POOL_MIN | Minimum warm containers (default 2) |
POOL_MAX | Maximum total containers (default 8) |
POOL_AUTOSCALE_ENABLED | Set true to enable queue-depth autoscaling |
Result Delivery
Remote sandboxes return results via the agent-tunnel result.submit protocol:
Remote sandbox → result.submit (HMAC-signed) → tunnel relay → Postgres NOTIFY → dispatch handler
↓
Injects "[Dispatch result]: ..."
into parent session → auto-wake
For multi-VM teams, the synthesizer collects all member results and runs a synthesis pass before injecting the final answer.
Crash Recovery
If the worker restarts while a team is running:
- Busy team members are marked
readybut their prompt loops are not restarted automatically. - The lead session receives:
"[System]: Server was restarted. Teammates interrupted: researcher, implementer. Use team_message to resume." - The user re-engages the lead, which re-coordinates the team.
This is intentional — automatic restart would risk runaway agents burning API credits on stale tasks.
