Banata

Core Concepts

Execution

Learn how to run commands, snippets, interactive terminals, and AI-agent tasks inside one sandbox, and how to choose the right surface for each kind of work.

Banata gives you four main ways to do work inside one sandbox:

  • exec() for known commands
  • runCode() for short code snippets
  • terminal() for a live shell
  • prompt() / promptAsync() for AI-agent-led work

The important idea is that these are not separate environments.

They all operate in the same sandbox:

  • the same /workspace
  • the same browser state
  • the same files
  • the same long-running job context

That means you can mix them.

For example:

  1. write an input file with fs.write()
  2. transform it with runCode()
  3. inspect the result with exec()
  4. ask the AI agent to open a site and continue the workflow
  5. collect the outputs from /workspace

A simple mental model

Use the lightest execution surface that fits the job.

  • use exec() when you already know the exact command
  • use runCode() when you want to run a short snippet directly
  • use terminal() when a person needs a live shell
  • use promptAsync() when the task is multi-step, open-ended, or may take a while

This keeps your workflows easier to debug and easier to reason about.

exec() for deterministic command execution

Use exec() when you already know what you want to run.

ts
const result = await sandbox.exec("sh", [
  "-lc",
  "python --version && node --version && git --version",
]);
 
console.log(result.stdout);

exec() is best when you want:

  • a known command
  • a known tool
  • a short deterministic operation
  • a quick environment check

Common uses:

  • check installed tools
  • run one script
  • read a file through shell tools
  • call python, node, go, git, curl, or other known binaries

If you already know the command, prefer exec() over the AI agent.

It is simpler, faster, and easier to debug.

runCode() for short snippets

Use runCode() when the task is still deterministic, but writing a full file first would just add friction.

ts
const result = await sandbox.runCode(`
import { readFileSync, writeFileSync } from "node:fs";
const input = readFileSync("/workspace/input.txt", "utf8");
writeFileSync("/workspace/output.txt", input.toUpperCase());
console.log(input);
`, {
  language: "typescript",
});

This is useful for:

  • lightweight parsing
  • short transforms
  • validation
  • generating one derived file
  • quick data cleanup

Why runCode() exists if exec() already exists

You could always create a file yourself and then call it through exec().

But runCode() removes that boilerplate for short snippets.

Instead of:

  1. create a temporary file
  2. choose a file name and extension
  3. execute it manually

you just:

  1. send the snippet
  2. choose the language
  3. get the result back

That makes runCode() a good middle ground between:

  • low-level command execution
  • full AI-agent orchestration

Language support in runCode()

Banata sandboxes currently support these runCode() languages:

  • javascript
  • typescript
  • node
  • bun
  • python
  • bash
  • sh
  • go
  • ruby
  • rust
  • elixir
  • java
  • deno

Example:

ts
await sandbox.runCode('print("hello from python")', {
  language: "python",
});

If you do not pass a language, Banata falls back to the sandbox’s default code runner.

For predictable behavior, it is better to pass the language explicitly.

Language examples

TypeScript with Bun:

ts
await sandbox.runCode(`
const values = [1, 2, 3];
console.log(values.map((v) => v * 2).join(","));
`, {
  language: "typescript",
});

JavaScript with Node:

ts
await sandbox.runCode(`
const values = ["a", "b", "c"];
console.log(values.join("-"));
`, {
  language: "node",
});

Python:

ts
await sandbox.runCode(`
from pathlib import Path
text = Path("/workspace/input.txt").read_text()
Path("/workspace/output.txt").write_text(text.upper())
print(text)
`, {
  language: "python",
});

Go:

ts
await sandbox.runCode(`
package main
 
import "fmt"
 
func main() {
  fmt.Println("hello from go")
}
`, {
  language: "go",
});

Ruby:

ts
await sandbox.runCode(`
puts "hello from ruby"
`, {
  language: "ruby",
});

Rust:

ts
await sandbox.runCode(`
fn main() {
    println!("hello from rust");
}
`, {
  language: "rust",
});

Elixir:

ts
await sandbox.runCode("""
IO.puts("hello from elixir")
""", {
  language: "elixir",
});

Java:

ts
await sandbox.runCode(`
public class Main {
  public static void main(String[] args) {
    System.out.println("hello from java");
  }
}
`, {
  language: "java",
});

Important for Java:

  • define class Main
  • Banata compiles and runs that class automatically

Deno:

ts
await sandbox.runCode(`
console.log("hello from deno");
`, {
  language: "deno",
});

Shell:

ts
await sandbox.runCode(`
echo "hello from shell"
ls -la /workspace
`, {
  language: "bash",
});

Verified sandbox tool availability

I verified these directly in a live sandbox on April 2, 2026.

Verified languages and runtimes:

  • Node.js
  • Python
  • Go
  • Ruby
  • Rust
  • Elixir
  • Java
  • Bun
  • Deno

Verified common tools:

  • Git
  • curl
  • wget
  • vim

Verified AI and coding CLIs:

  • Claude CLI
  • Gemini CLI
  • Codex

One important exception from the live check:

  • cursor was not installed in the sandbox I tested

So you should not currently rely on cursor being present.

terminal() for a live shell

Use the terminal when a person or another external tool needs an interactive shell session.

ts
const terminal = await sandbox.terminal();
console.log(terminal.terminalUrl);

Use it when:

  • a human operator needs to inspect the environment
  • you want a real shell instead of one command at a time
  • you are debugging a workflow interactively

terminal() is a human tool.

For backend automation, exec() and runCode() are usually better.

The AI agent for multi-step work

Use the AI agent when the workflow is not fully deterministic.

Examples:

  • browse several pages and collect findings
  • log in, wait for human approval, then continue
  • read files, transform them, and decide what to do next
  • drive the browser and filesystem together in one workflow

For quick synchronous work:

ts
const reply = await sandbox.prompt(
  "Open https://example.com and reply with the page title.",
);

For production work, prefer promptAsync():

ts
const queued = await sandbox.promptAsync(
  "Research the target site, save findings to /workspace/report.md, and finish with RESULT_READY.",
  {
    metadata: {
      userId: "user_123",
      jobId: "job_456",
      source: "admin-panel",
    },
  },
);
 
console.log(queued.taskId);

Use promptAsync() when:

  • the task may run for a while
  • you do not want one request open
  • you want webhook-driven completion
  • the workflow spans browser, files, and multiple steps

How these surfaces work together

A strong Banata workflow often combines multiple surfaces.

Example pattern:

  1. launch a sandbox
  2. write inputs into /workspace
  3. run a deterministic transform with runCode()
  4. inspect the result with exec()
  5. ask the AI agent to continue the larger workflow
  6. read final outputs from /workspace
  7. save durable outputs as artifacts if needed

That pattern works well because each surface does one job well:

  • fs.* manages files
  • runCode() handles short deterministic code
  • exec() handles exact commands
  • promptAsync() handles reasoning and multi-step automation

Same sandbox, same workspace

This matters a lot.

If you:

  • write a file with fs.write()
  • then call runCode()
  • then call exec()

all of that happens in the same sandbox and the same /workspace.

That means a file created by one step is available to the next step immediately.

This is how you should think about Banata:

  • one sandbox is one working environment
  • execution methods are different ways of operating inside that environment

Choosing the right method

NeedBest method
run a known commandexec()
run a short deterministic snippetrunCode()
use a specific language directlyrunCode(..., { language })
open a live shellterminal()
do multi-step reasoning across toolspromptAsync()

Practical advice

A few rules will keep integrations clean:

  • prefer exec() when you already know the exact command
  • prefer runCode() for short deterministic transforms
  • pass language to runCode() explicitly
  • prefer promptAsync() for long or open-ended jobs
  • keep working files in /workspace
  • use artifacts for durable outputs you want another system to download later