Ephemeral sandboxes
Swarms
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.
At a glance
- What it is
- One shell command on many ephemeral 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: Ephemeral Sandbox. State that must stay: Persistent Sandbox.
- Start it
POST /api/swarms,maritime swarms run,maritime.swarms.startin the SDK,start_swarmover MCP, or the Swarms page in the dashboard.- Lifecycle
- Each sandbox goes queued, booting, running, then succeeded or failed, and is ended after its job. The run completes when every sandbox is done.
- Price
- Each sandbox: $0.0045 per vCPU-hour + $0.00225 per GB-hour while running, per second (small, 2 vCPU and 4 GB, is $0.018 an hour). Swarm sandboxes never sleep, so there is no disk line.
- Limits
- Up to 100 sandboxes per run on Starter and the free trial, 1,000 on Growth, 5,000 on Scale, 10,000 on Dedicated. Running at once follows the awake limit. 10 seconds to 6 hours per sandbox; results up to 64 KB.
- Related
- Ephemeral Sandboxes, REST API: Swarms, SDK.
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
- You start a run. Every sandbox is queued. As many start at once as the run's
concurrencyallows, which is never more than your plan's machines awake at once. - Each sandbox is a fresh Linux micro-VM on the sandbox engine (the
basetemplate unless you pick another). Once it has booted, your files are written and the command runs withMARITIME_SWARM_INDEX(0, 1, 2, ...),MARITIME_SWARM_COUNTandMARITIME_SWARM_IDset. - 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 isfailed, with the reason. - The run is
completedwhen 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
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 giveinputs.size:vcpus1, 2 or 4 andmemoryGb1 to 8. Default 2 vCPU, 4 GB. The run uses the smallest sandbox size that fits:small(2 vCPU, 4 GB) ormedium(4 vCPU, 8 GB). The run'ssizeanswers with that size'sname.timeoutSeconds: how long each sandbox's command may run, 10 to 21,600. Default 600.concurrency: the most sandboxes at once. Default and maximum: your plan's machines awake at once.templateorimage: what each sandbox runs.templateis a stock template (base,python,code-interpreter,desktop-linux) or a saved snapshot name;imageis an OCI image reference. Defaultbase.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.
{
"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.
{
"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
}waitingReasonsays why queued sandboxes are not starting, for example your plan's awake limit or no host with room. 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.erroris 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.estimatedCostCentsis the time each sandbox existed at list price. Your bill comes from the usage meter and applies your plan's discount.
Download results
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.
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)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
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
- Each sandbox is billed per second at the sandbox engine rates, $0.0045 per vCPU-hour plus $0.00225 per GB-hour of memory for its size (small, 2 vCPU and 4 GB, is $0.018 an hour; medium, 4 vCPU and 8 GB, is $0.036), from your plan's usage credit, from the moment it is created until it is ended. It is ended when its job ends, so it has no asleep disk charge. See Billing & plans.
- Sandboxes running at once count toward your plan's machines awake at once: Starter and the free trial 5, Growth 25, Scale 60, Dedicated 500. Other awake machines share the same limit.
- Sandboxes in one run: up to 100 on Starter and the free trial, 1,000 on Growth, 5,000 on Scale, 10,000 on Dedicated. Larger runs return 403
swarm_too_large; split the job or write to contact@maritime.sh. - 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
provisionscope.