> ## Documentation index
> Fetch the complete index at: https://maritime.sh/llms.txt
> Setup procedure for AI agents (install, sign in, deploy, verify): https://maritime.sh/agents.md
> This page as HTML: https://maritime.sh/docs/swarms

# Swarms

Sandboxes

A swarm is many sandboxes running one job in parallel. You send one command and a count (or one input per
sandbox); each sandbox boots, runs the command, reports its exit code and output, and is ended.

Swarms come with the Scale plan ($500 a month) and Dedicated. Each run rents a whole swarm server for your
account: 500 GB of memory, $1 a minute while it is rented, billed per second. Below Scale the API answers
402 `plan_required`, and `GET /api/swarms/access` tells you whether you have Swarms.

## At a glance

| | |
| --- | --- |
| **What it is** | One shell command on many sandboxes at once, each with its own input, with every result collected. |
| **Use it for** | Evals, reinforcement learning rollouts, batch jobs where each item needs its own machine. |
| **Use something else when** | One machine: [Sandbox](https://maritime.sh/docs/ephemeral-sandboxes). State that must stay: [Agent box](https://maritime.sh/docs/persistent-sandboxes). |
| **Start it** | On the Scale plan or Dedicated: `POST /api/swarms`, `maritime swarms run`, `maritime.swarms.start` in the SDK, `start_swarm` over MCP, or the Swarms page. |
| **Lifecycle** | The run rents a whole swarm server for your account. Each sandbox goes queued, booting, running, then succeeded or failed, and is ended after its job. The server is free again when you release it or after 10 minutes with no sandbox running. |
| **Price** | The swarm server: $1 a minute while you rent it, billed per second ($60 an hour). The sandboxes on it have no charge of their own. |
| **Limits** | Up to 5,000 sandboxes per run on Scale, 10,000 on Dedicated. Running at once: as many as fit in the server's 500 GB of memory, any mix of sizes. One account per server: while another rents it, a start returns 409 `server_busy`. 10 seconds to 6 hours per sandbox; results up to 64 KB. |
| **Related** | [Sandboxes](https://maritime.sh/docs/ephemeral-sandboxes), [REST API: Swarms](https://maritime.sh/docs/api#swarms), [SDK](https://maritime.sh/docs/sdk#swarms). |

## When to use it

- Eval suites: one case per sandbox, pass or fail by exit code.
- Reinforcement learning rollouts, one environment per sandbox.
- Batch jobs where each item needs its own clean, isolated machine.

## How a run works

1. You start a run. Maritime rents you a swarm server (or uses the one you already rent) and queues
   every sandbox. As many start at once as the run's `concurrency` allows, which is never more than
   fit in the server's 500 GB of memory.
2. Each sandbox is a fresh Linux micro-VM on the sandbox engine (the `base` template unless you
   pick another). Once it
   has booted, your files are written and the command runs with `MARITIME_SWARM_INDEX` (0, 1, 2,
   ...), `MARITIME_SWARM_COUNT` and `MARITIME_SWARM_ID` set.
3. When the command exits, the sandbox records its exit code, the last 16 KB of its output and
   errors, and the result file if you asked for one. Then it is ended, disk included. Exit code 0
   is `succeeded`; anything else, a timeout or a boot failure is `failed`, with the reason.
4. The run is `completed` when every sandbox has ended.

On the dashboard, the Swarms page does the same: write the job, pick how many sandboxes and their
size, and press Start. The run's page shows every sandbox as a square coloured by its state.

## Start a swarm

`start.sh`:

```bash
curl -X POST https://api.maritime.sh/api/swarms \
  -H "Authorization: Bearer $MARITIME_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: evals-2026-10-26" \
  -d '{
    "name": "evals",
    "command": "python eval.py --case $MARITIME_SWARM_INDEX",
    "count": 50,
    "size": { "vcpus": 2, "memoryGb": 4 },
    "timeoutSeconds": 600
  }'
```

The body:

- `command`: a shell command, or a list of arguments. Required.
- `count`: how many sandboxes. Leave it out when you give `inputs`.
- `size`: `vcpus` 1, 2 or 4 and `memoryGb` 1 to 8. Default 2 vCPU, 4 GB. Each sandbox runs
  at exactly that, with 12 GB of disk, and is billed at it. The run's `size` has a `name`:
  `small` for 2 vCPU and 4 GB, otherwise `custom`.
- `timeoutSeconds`: how long each sandbox's command may run, 10 to 21,600. Default 600.
- `concurrency`: the most sandboxes at once. Default and maximum: as many as fit in the swarm server's
  500 GB of memory, so 500 GB divided by the sandbox memory (125 at 4 GB, 500 at 1 GB).
- `template` or `image`: what each sandbox runs. `template` is a stock template (`base`,
  `python`, `code-interpreter`, `desktop-linux`) or a saved snapshot name; `image` is an OCI
  image reference. Default `base`.
- `resultPath`: an absolute path read back after the command ends (up to 64 KB).
- `name`: shown on the dashboard.

Send an `Idempotency-Key` header (or `idempotencyKey` in the body) to retry safely: the same key and
body return the run it started the first time, with status 200. The same key with a different body
returns 409 `idempotency_conflict`.

## One input per sandbox

Give `inputs` instead of `count` to vary each sandbox. Each input may add arguments to the command
(shell-quoted), set variables, and write files before the command runs. The count is the number of
inputs.

`body.json`:

```json
{
  "command": "python rollout.py",
  "inputs": [
    { "args": ["--seed", "1"] },
    { "args": ["--seed", "2"], "env": { "TEMPERATURE": "0.7" } },
    { "files": { "/work/task.json": "{\"id\": 3}" } }
  ],
  "resultPath": "/work/result.json"
}
```

## Follow a run

`GET /api/swarms/{id}` returns the run and every sandbox: its status (`queued`, `booting`,
`running`, `succeeded`, `failed` or `cancelled`), exit code, output tails, result, error and
`sandboxId`. The `sandboxId` (`sbx-...`) is kept after the job ends and stays readable with
`GET /v1/sandboxes/{id}` for 7 days. `GET /api/swarms` lists your runs, newest first.

`run.json`:

```json
{
  "id": "4f1c...",
  "name": "evals",
  "status": "running",
  "count": 50,
  "size": { "name": "small", "vcpus": 2, "memoryGb": 4 },
  "concurrency": 5,
  "counts": { "queued": 30, "booting": 2, "running": 3, "succeeded": 14, "failed": 1, "cancelled": 0 },
  "done": 15,
  "waitingReason": null,
  "error": null,
  "durationSeconds": 212.4,
  "estimatedCostCents": 1.52
}
```

- `waitingReason` says why queued sandboxes are not starting, for example the swarm server's memory
  is full. They start as soon as they can. If none of the run's sandboxes can start for 15
  minutes, the rest fail with that reason.
- `error` is set when billing stops the run, for example when the free trial's $20 is used up or a
  payment failed. Queued sandboxes fail with billing's message; sandboxes already running finish.
- `estimatedCostCents` is the time each sandbox existed at the sandbox list price, as a guide. Your
  bill is the swarm server's time (see below).

## Swarm servers

A swarm server is one whole machine with 500 GB of memory, rented by one account at a time.

- Starting a run rents a free server for you, or uses the one you already rent. You can also rent one
  yourself with `POST /v1/servers/lease`.
- While you rent it, the server is busy for every other account: their start returns 409
  `server_busy`. Try again in a few minutes.
- The limit is the server's memory: 500 GB booked across your sandboxes, in any mix of sizes (for
  example 500 sandboxes at 1 GB). No awake limit applies on the server.
- It is free again when you release it (`DELETE /v1/servers/lease`, or Release server on the
  dashboard) or after 10 minutes with no sandbox running. Releasing it ends every sandbox on it.

### Agents that start agents

Every sandbox on your rented server gets these variables:

- `MARITIME_API_KEY` and `E2B_API_KEY`: a server key that starts with `mlk_`.
- `MARITIME_API_URL` and `E2B_DOMAIN`.
- `MARITIME_SERVER_LEASE_ID`: the lease the sandbox runs under.

With that key, an agent inside creates more sandboxes with the Maritime SDK or CLI
(`POST /v1/sandboxes`) or the E2B SDK, unchanged, and they start on the same server. When the lease
ends, every sandbox on it ends and the key stops working.

`agent.py`:

```python
from e2b import Sandbox

# Reads E2B_API_KEY and E2B_DOMAIN from the environment; the new sandbox starts on this server.
child = Sandbox.create()
print(child.commands.run("python worker.py").stdout)
```

### The servers API

- `GET /v1/servers`: every swarm server and its state (`available`, `busy` or `yours`), your open
  lease, and `eligible`, whether your account may rent one.
- `POST /v1/servers/lease`: rents a free server (201), or returns the one you rent (200). 402
  `plan_required` below Scale, 409 `server_busy` when every server is rented.
- `DELETE /v1/servers/lease`: releases it. Billing stops and every sandbox on it ends.

The lease shows `memoryGib`, `memoryUsedGib`, `sandboxes`, `pricePerMinuteUsd`, `startedAt` and,
once it ends, `releasedAt` and `releaseReason` (`released`, `idle`, `plan` or `closed`). Your
own open lease also carries `apiKey` and `env`, the variables above.

## Download results

`results.sh`:

```bash
curl https://api.maritime.sh/api/swarms/$SWARM_ID/results \
  -H "Authorization: Bearer $MARITIME_API_KEY" -o results.jsonl

# One sandbox per line, in input order:
# {"index": 0, "status": "succeeded", "sandboxId": "sbx-k3m7q2ab", "exitCode": 0, "stdoutTail": "...", "result": "...", ...}
```

## Cancel a run

`POST /api/swarms/{id}/cancel`. Queued sandboxes never start; running commands are stopped and their
sandboxes ended within seconds. Sandboxes that already finished keep their results.

## From the SDK

Both SDKs have `swarms.start`, `list`, `get`, `cancel`, `results` and `wait`, which polls every 3
seconds until the run ends. `start` sends an Idempotency-Key for you, so a retried start returns the
same run.

`evals.ts`:

```typescript
import { Maritime } from 'maritime-sdk'

const maritime = new Maritime({ apiKey: process.env.MARITIME_API_KEY })

const run = await maritime.swarms.start({
  command: 'python eval.py --case $MARITIME_SWARM_INDEX',
  count: 50,
  timeoutSeconds: 600,
})

const done = await maritime.swarms.wait(run.id)
console.log(`${done.counts.succeeded} succeeded, ${done.counts.failed} failed`)
for (const sandbox of done.sandboxes) console.log(sandbox.index, sandbox.sandboxId, sandbox.exitCode)
```

`evals.py`:

```python
import os

from maritime import Maritime

client = Maritime(api_key=os.environ["MARITIME_API_KEY"])

run = client.swarms.start("python eval.py --case $MARITIME_SWARM_INDEX", count=50, timeout_seconds=600)
done = client.swarms.wait(run["id"])
for sandbox in done["sandboxes"]:
    print(sandbox["index"], sandbox["sandboxId"], sandbox["exitCode"])
```

## From the CLI and MCP

`terminal`:

```bash
maritime swarms run --count 50 --command 'python eval.py --case $MARITIME_SWARM_INDEX' --size 2x4 --timeout 10m --wait
maritime swarms list
maritime swarms get <swarm_id>
maritime swarms results <swarm_id> -o results.jsonl
maritime swarms cancel <swarm_id>
```

`--wait` follows the run and exits 0 only when every sandbox succeeded. With an `--inputs` file, each
line is one sandbox's `args`, `env` and `files`. The Maritime MCP server has the same calls as
tools: `start_swarm`, `list_swarms`, `get_swarm`, `cancel_swarm` and `get_swarm_results`. The CLI's own MCP
server (`maritime mcp`) names every tool after its command, so there they are `swarms_run`,
`swarms_list`, `swarms_get`, `swarms_cancel` and `swarms_results`.

## Limits and billing

- The swarm server is billed $1 a minute ($60 an hour) while you rent it, per second, as its own line,
  Swarm servers. The sandboxes on it have no charge of their own. Your plan credit applies as usual. See
  [Billing & plans](https://maritime.sh/docs/billing#rates).
- Sandboxes running at once are limited by the server's 500 GB of memory, not by your plan's machines
  awake at once.
- Sandboxes in one run: up to 5,000 on Scale and 10,000 on Dedicated.
  Larger runs return 403 `swarm_too_large`; split the job or write to
  [contact@maritime.sh](mailto:contact@maritime.sh).
- Swarms need the Scale plan or Dedicated, or a grant from us by hand; below that the API answers
  402 `plan_required`. When every swarm server is rented, a start returns 409 `server_busy`.
- Starting a run is refused with billing's own code when your account may not start machines (for
  example `trial_required`).
- Creating and cancelling runs need an API key with the `provision` scope.

## Related

- [Sandboxes](https://maritime.sh/docs/ephemeral-sandboxes)
- [Agent boxes](https://maritime.sh/docs/persistent-sandboxes)
- [Limits: resources and plans](https://maritime.sh/docs/limits#resource-and-plan-limits)
