llmbox runs an agentic loop where an LLM (Claude or Gemini) writes JavaScript, executes it in a sandboxed V8 environment, observes the results, and iterates until the task is complete.
- You provide a natural language prompt.
- llmbox sends it to Claude along with a description of the available sandbox capabilities.
- Claude responds with a JavaScript code block.
- The code runs inside a fresh V8 isolate.
- The output is fed back to Claude as context.
- Steps 3–5 repeat (up to 10 rounds) until Claude produces a plain-text answer with no code.
The build requires two environment variables that point to the prebuilt V8 static library checked in under third_party/v8/. If direnv is active they are already exported via .envrc; otherwise set them manually:
export RUSTY_V8_ARCHIVE="$PWD/third_party/v8/librusty_v8_release_x86_64-unknown-linux-gnu.a.gz"
export RUSTY_V8_SRC_BINDING_PATH="$PWD/third_party/v8/src_binding_release_x86_64-unknown-linux-gnu.rs"Then build and run with Bazel:
bazel build //:llmbox
bazel run //:llmbox -- "<your prompt>"See CLAUDE.md for additional network/proxy setup required in restricted environments.
llmbox <PROMPT> [OPTIONS]
Options:
--provider <PROVIDER> LLM provider: anthropic (default) or gemini
--model <MODEL> Model name (defaults to claude-sonnet-4-6 or gemini-2.0-flash)
--mount <PATH:MODE> Expose a directory to the sandbox (ro = read-only, rw = read-write)
--http Enable HTTP requests from the sandbox
--env <VAR> Expose an environment variable by name (repeatable)
The required API key environment variable depends on the provider:
| Provider | Environment variable |
|---|---|
anthropic (default) |
ANTHROPIC_API_KEY |
gemini |
GEMINI_API_KEY |
# Summarise a local log file (Anthropic, default)
llmbox "count ERROR lines per hour in app.log" --mount /var/log/myapp:ro
# Same task using Gemini
GEMINI_API_KEY=AIza... llmbox --provider gemini \
"count ERROR lines per hour in app.log" --mount /var/log/myapp:ro
# Fetch and transform remote data
llmbox "fetch the GitHub Zen API and reverse the words" --http
# Use a specific model
llmbox --provider gemini --model gemini-2.5-pro "explain the code in src/" --mount src/:ro
# Use a secret already in the environment
llmbox "call the internal API and parse the response" --http --env INTERNAL_API_KEYEach --mount, --http, and --env flag extends the JavaScript API available inside the sandbox. Only explicitly enabled capabilities are exposed; the system prompt sent to Claude reflects exactly what the sandbox can do.
| Flag | JavaScript API |
|---|---|
--mount /path:ro |
fs.read(path) → string |
--mount /path:rw |
fs.read(path), fs.write(path, content) |
--http |
http.get(url[, headers]), http.post(url, body[, headers]) → string |
--env VAR |
env.get("VAR") → string | null |
console.log(...) is always available and prints to stderr.
- Each JavaScript execution runs in a fresh V8 isolate — no state carries over between rounds.
- Filesystem access is path-validated; the sandbox cannot escape a mounted directory.
- Environment variable access is allowlist-only; only variables named with
--envare visible. - HTTP responses that return non-2xx status throw a JavaScript error.
| Crate | Purpose |
|---|---|
| v8 | V8 JavaScript engine bindings |
| ureq | HTTP client |
| clap | CLI argument parsing |
| serde_json | JSON handling |
| thiserror | Error type derivation |