Isorun Docs
Sandboxes

Create & Execute

The default runtime loop for agents and jobs, create, execute, destroy.

Create a sandbox, run commands inside it, then destroy it. This is the default runtime loop for agents and short jobs.

Use this pattern for one-shot tasks and short workflows. For anything that needs to outlive a single request, see Lifecycle and hibernation.

Quick example

TypeScript
import { Isorun } from 'isorun'

const isorun = new Isorun()
const sandbox = await isorun.create({ image: 'python:3.12-slim' })

try {
  const result = await sandbox.exec("python3 -c 'print(2**100)'")
  console.log(result.stdout.trim())
  console.log(result.exitCode)
} finally {
  await sandbox.destroy()
}

Streaming output while a command runs

sandbox.exec() buffers: it resolves once the command exits, so a long build prints nothing until the end. To watch output live, call the streaming endpoint directly — POST /v1/runs/{id}/exec/stream returns Server-Sent Events, one JSON frame per data: line.

ApproachBest forTradeoff
sandbox.exec(cmd, timeoutSec)Short bounded commandsNo incremental output while running
POST /v1/runs/{id}/exec/streamLong builds / tests / trainingA few lines of parsing; you handle the frames yourself

Each frame is { "type": "stdout" \| "stderr" \| "exit", "data": "..." }:

TypeScript
const res = await fetch(`${isorun.apiUrl}/v1/runs/${sandbox.id}/exec/stream`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.ISORUN_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ command: 'python3 long_training_loop.py', timeout: 600 }),
})

for await (const chunk of res.body) {
  for (const line of Buffer.from(chunk).toString().split('\n')) {
    if (!line.startsWith('data: ')) continue
    const frame = JSON.parse(line.slice(6))
    if (frame.type === 'stdout') process.stdout.write(frame.data)
    if (frame.type === 'stderr') process.stderr.write(frame.data)
  }
}

GET /v1/runs/{id}/exec/ws carries the identical frames over a WebSocket if you would rather not parse SSE.

Handle failures

A non-zero exit code is a normal result, not an exception, check exitCode yourself.

TypeScript
const result = await sandbox.exec('pytest -q', 180)
if (result.exitCode !== 0) {
  console.error(result.stderr)
  throw new Error('tests failed')
}

Clean up and control cost

Always destroy or close sandboxes in finally paths.

  • Prevents leaked runtime costs.
  • Keeps capacity available under load.
  • Makes behavior predictable in production.

A sandbox you forget to destroy keeps billing until its idle timeout fires. Put destroy() in a finally block so it runs even when the command throws.

Next steps

On this page