Sandbox · AI agents

Agent execution, under host control.#

Provide AI-agent scripts with explicit data, modules, capabilities, and limits. Filesystem workflows use GocciaRunner's sandbox mode: host paths are copied into a virtual filesystem, sandbox writes stay in that filesystem, and the host receives structured output plus explicit diffs.

AI agent

LLM emits GocciaScript via a tool call

Goccia sandbox

copied inputs · command-line modules · capability gates · limits

Structured result

JSON

copied inputssandbox-only fsnested runScriptexplicit diffs
Virtual filesystem runner

Copied files, not host mounts.#

GocciaRunner's sandbox mode runs the entry inside an isolated virtual filesystem. --copy and the sandbox section of goccia.json copy files in before execution; they are snapshots, not live mounts, and only --copy-rw inputs are written back after a successful run. Source can import "fs" for sandbox filesystem operations and "goccia" for shell commands or nested execution.

run sandbox
# run from the directory holding goccia.json and main.js
# once: review and trust the sandbox section
./build/GocciaRunner --trust goccia.json

./build/GocciaRunner main.js \
  --mode=bytecode \
  --diff=unified
goccia.json
{
  "sandbox": {
    "copy": ["project=/", "tools"],
    "copy-rw": ["out"]
  },
  "max-fs-bytes": "32MiB"
}
main.js
import fs from "fs";
import { $, runScript } from "goccia";

fs.mkdirSync("/out", { recursive: true });
fs.writeFileSync("/out/summary.txt", "ready\n");

const audit = runScript("/tools/audit.js", {
  sandbox: true,
  copy: ["/tools/audit.js", "/out=/input"],
  diff: true,
});

console.log(await $`cat /out/summary.txt`.text());
console.log(audit.diff);
Tool-call comparison

One sandboxed call vs five.#

The same agent task — “Summarize the latest transaction batch and find outliers.” — solved with a typical tool stack and again with a single GocciaScript call. This comparison assumes each tool call depends on data discovered or produced by the previous step, so the calls cannot be parallelized without changing the task shape.

Each step expands to the request body for that turn — instructions, tool definitions, prior function calls, and function-call outputs. Token counts via gpt-tokenizer on GPT-5-family o200k_base tokenization.
Bash + jq

5 tool calls ↓ 1422 in ↑ 241 out

  1. 1
    bashdiscover↓ 108 ↑ 39
    ls /tmp/agent/transactions/
  2. 2
    bashload↓ 166 ↑ 42
    cat /tmp/agent/transactions/transactions.current.json
  3. 3
    bashsum↓ 313 ↑ 50
    jq '[.[].amount] | add' /tmp/agent/transactions/transactions.current.json
  4. 4
    bashaverage↓ 382 ↑ 52
    jq '[.[].amount] | add / length' /tmp/agent/transactions/transactions.current.json
  5. 5
    bashoutliers↓ 453 ↑ 58
    jq '[.[] | select((.amount | abs) > 280)]' /tmp/agent/transactions/transactions.current.json
  • · values cross 5 process boundaries — possible to lose precision or quoting
  • · 5 round-trips ≈ 5× the prompt overhead and 5× the chance of a misstep
GocciaScript (single call)

1 tool call ↓ 223 in ↑ 138 out

  1. 1
    run_codeeverything↓ 223 ↑ 138
    const total = transactions.reduce((s, t) => s + t.amount, 0);
    const avg = total / transactions.length;
    const stdev = Math.sqrt(
      transactions.reduce((s, t) => s + (t.amount - avg) ** 2, 0) / transactions.length
    );
    const outliers = transactions.filter((t) => Math.abs(t.amount - avg) > 2 * stdev);
    ({ total, avg, outliers });
  • ✓ all values stay in one sandbox — no serialization between steps
  • ✓ host gets a single structured JSON result back

Sandbox preview

Edit the script or globals and hit Run — the code runs in the same sandboxed runtime used by the server preview and returns a structured host result.

sandboxed script · script.js
globals · context.json
host result
press Run or ⌘+Enter