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
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()
}Choose between exec and execStream
| Method | Best for | Tradeoff |
|---|---|---|
sandbox.exec(cmd, timeoutSec) | Short bounded commands | No incremental output while running |
sandbox.execStream(cmd, { onStdout, onStderr }) | Long builds / tests / training | Slightly more integration code; resolves to the final result once the command exits |
await sandbox.execStream('python3 long_training_loop.py', {
timeoutSec: 600,
onStdout: (chunk) => process.stdout.write(chunk),
onStderr: (chunk) => process.stderr.write(chunk),
})Handle failures
A non-zero exit code is a normal result, not an exception, check exitCode yourself.
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
- Lifecycle and hibernation, pause a sandbox without paying for idle time.
- Checkpoints and rollback, snapshot expensive setup and fork it.
- TypeScript SDK, full method reference.