Run a coding agent in a sandboxed environment.
- Tiny standalone binary (<1MB). Runs on any recent GNU/Linux system1.
- Simple TOML-based configuration.
- JSON schema for config assistance.
- Use one of the installation methods below.
- Write your configuration.
- Run your agent:
agent-run <pi|opencode|codex|claude>.
That's it!
Note
agent-run uses bwrap so it only works on a Linux system. Additionally you need unprivileged user namespaces enabled.
Run directly from the flake:
nix run github:sin-ack/agent-run -- <command>...Or install it into your profile:
nix profile install github:sin-ack/agent-runRun: mise use github:sin-ack/agent-run@latest
(You might need to pass --before 0d to get the latest release, due to Mise's
date filters.)
- Download the binary from the latest release: https://github.com/sin-ack/agent-run/releases/latest
- Put it somewhere in your PATH, e.g.
$HOME/.local/bin.
Don't forget to chmod +x!
Configuration is in TOML. There is a JSON schema you can use with tools like Taplo to get completions in your editor (see below).
agent-run finds its configuration in two ways:
- An explicit
--configargument - The closest
.agent-run/config.tomlin the directory hierarchy
If no configuration is found, the following default configuration is used:
[global]
network = true
inherit_env = trueThe tool configuration to use is selected by looking at the basename of the first argument of the command.
Currently, no configuration merging across multiple configs is done. This might change later.
# Add this line to your TOML file to add support for completion:
#:schema https://github.com/sin-ack/agent-run/raw/master/schema.json
[global]
mount = [
# Mount the user cache so tools that need it can write to it.
"~/.cache",
# Make the project directory writable.
"..",
]
[tools.pi]
mount = [
# Allow access to pi's own directory.
"~/.pi",
# Pi writes bash outputs to /tmp/pi-bash-*.log so we must make all of /tmp
# writable. :(
"/tmp",
]
[tools.codex]
mount = ["~/.codex"]
[tools.opencode]
# OpenCode extracts its native OpenTUI library to TMPDIR so we need a writable
# tmpdir for it. Make sure /tmp/opencode exists before running agent-run.
env = ["TMPDIR=/tmp/opencode"]
mount = [
"/tmp/opencode",
"~/.cache/opencode",
"~/.config/opencode",
"~/.local/share/opencode",
"~/.local/state/opencode",
]
[tools.claude]
mount = [
"~/.claude",
# Workspace trust requires ~/.claude.json. It must exist before launching
# agent-run.
"~/.claude.json",
]Configuration in agent-run consists of "tool configs". There is a global tool
config under global and one for each tool you define under tools.<name>.
Tool configs have the following keys:
inherit_env: Whether to inherit the environment variables from host. Defaults to true.network: Whether to allow network access within the sandbox. Defaults to true.mount: Paths to mount as read-write. The host filesystem is always mounted read-only; this only makes certain paths writable. Tildes are expanded. Environment variables are not expanded. If the path is relative, it is treated as relative to the configuration file itself. Absolute paths are passed as-is. We always identity-mount into the sandbox. If the given path is not found a diagnostic warning is printed and the path is left read-only.env: Environment variables to pass to the sandbox. Ifinherit_envis true, this is merged into the host environment variables. Each argument is a string in one of two forms:KEY=VALUE: SetKEYtoVALUE.VALUEcan be the empty string in which case it is set to the empty string in the sandbox too.KEY: PassKEYfrom the host environment through. If the variable is unset, it is left unset in the sandbox. Otherwise,KEYis set to the host environment variable's value.
For a full reference see schema.json.
The environment variable RUST_LOG is respected, and supports debug and
trace for compact and verbose debug logs respectively.
Claude Code stores workspace trust in ~/.claude.json, not in ~/.claude.
Both paths must therefore be writable (and must exist before agent-run starts):
[tools.claude]
mount = ["~/.claude", "~/.claude.json"]You need the following:
[tools.opencode]
mount = [
"/tmp/opencode",
]
env = ["TMPDIR=/tmp/opencode"]Bun tries to extract bundled libraries to TMPDIR but fails by default.
- Permission prompts are annoying.
- I want the agent to do anything it wants as long as it can't touch non-project files. I don't care about overlays, parallel agents or other fancy features.
- I want a small and simple tool, ideally just one binary I can put into my
bin/. - I want basic per-tool configurability.
Nothing satisfied all of these criteria so I made my own.
agent-run is primarily intended to catch mistakes made by agents. Arbitrary
malicious code is not guaranteed to be handled perfectly safely. In particular,
because the entire file system is exposed as read-only, information stealing
(when network access is enabled) and things like writing to user-writable socket
files is still possible. If this is your threat model, consider using things
like microVMs.
- We build a
bwrapbinary for the target. - We embed it inside the binary and exec into it via memfd, passing it the appropriate arguments from the config.
You need Bazel. I recommend using Bazelisk.
- Run it via Bazel:
bazel run --run_in_cwd //:agent-run -- [args...] - Build a release binary:
bazel build --config=size --platforms=//platforms:[target] //:agent-run - Run tests (including Clippy):
bazel test //... - Run Clippy only:
bazel test //:clippy_test - Regenerate third-party license notices (requires
cargo-about):tools/update-third-party-licenses.sh
This project is licensed under the GNU General Public License, version 3. See LICENSE for details. See THIRD_PARTY_LICENSES.md for third-party license notices.
Footnotes
-
Currently aarch64 and x86_64 are directly supported. ↩