Skip to content

The sandbox API

Nimbus.fromEnv uses a colocated DO binding. Nimbus.connect uses the authenticated remote API. Both return the same typed sandbox.

Terminal window
const box = nimbus.sandbox('job-123', {
profile: 'default', // sandbox profile from your NimbusConfig
tenant: 'acme', // namespaces the underlying DO: tenant:subject:id
subject: 'agent',
});
await box.ready(); // headless boot + profile preinstalls; memoized
await box.destroy({ reason: 'job done' });

Every call awaits ready(); call it directly to pay the boot cost first.

Run one command, run source code, or start a process.

Terminal window
const result = await box.exec('npm test', {
cwd: '/home/user/app', env: { CI: 'true' }, timeoutMs: 60_000, stdin: 'y\n',
});
// { command, exitCode, success, stdout, stderr, duration, timestamp }
await box.runCode('print(2 + 2)', { language: 'python', install: 'ifMissing' });
const proc = await box.startProcess('node --watch server.js');
// NimbusExecResult & { pid, process, ports }

Read and change files through the sandbox VFS.

Terminal window
await box.files.write('/home/user/app/server.js', code); // string | Uint8Array
const text = await box.files.read('/home/user/app/server.js');
const bytes = await box.files.readBytes('/home/user/data.bin');
const list = await box.files.list('/home/user'); // { name, type }[]
await box.files.stat(path); await box.files.mkdir(path);
await box.files.exists(path); await box.files.delete(path, { recursive: true });

Install allowed runtimes when a task needs them.

Terminal window
await box.runtimes.ensure(['python', 'clang']); // install if missing
await box.runtimes.list(); // { installed, available }

Nimbus checks runtimes.allow before the install RPC.

List processes and control their input, signals, size, and logs.

Terminal window
const procs = await box.processes.list(); // pid, command, state, exitCode…
await box.processes.kill(pid);
await box.processes.write(pid, 'input\n'); // stdin
await box.processes.signal(pid, 'SIGINT');
await box.processes.resize(pid, { columns: 120, rows: 32 });
const logs = await box.processes.logs(pid, { lines: 100 });

attach returns an async iterable for live input and output.

Terminal window
const attachment = box.processes.attach(pid);
await attachment.write('q');
for await (const chunk of attachment) {
// { seq, ts, stream: 'stdout' | 'stderr', data, binary? }
}

Expose an app through a path on the Nimbus deployment.

Terminal window
const port = await box.ports.expose(3000); // → { …, url: '/s/<id>/port/3000/' }
await box.ports.list();
await box.ports.unexpose(3000);

The caller can use the URL directly with browser or session auth.

Wrap the sandbox as a Proteus-style tool provider.

Terminal window
const provider = box.tools({ namespace: 'sandbox' });

It includes files, exec, processes, ports, and runtimes.

Remote calls throw NimbusRemoteError with status, optional code, and the response body. The client validates wire payloads with zod, so a drifted server fails instead of returning malformed shapes.