Run a coding agent in a sandboxed environment.
-
Tiny standalone binary (<1MB). Runs on any recent GNU/Linux system .1 - 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-run
Run: 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
--config
argument - The closest
.agent-run/config.toml
in the directory hierarchy
If no configuration is found, the following default configuration is used:
[global]
network = true
inherit_env = true
The 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.
#:schema https://github.com/sin-ack/agent-run/raw/master/schema.json
[global]
mount = [
"~/.cache",
"..",
]
[tools.pi]
mount = [
"~/.pi",
"/tmp",
]
[tools.codex]
mount = ["~/.codex"]
[tools.opencode]
env = ["TMPDIR=/tmp/opencode"]
mount = [
"/tmp/opencode",
"~/.cache/opencode",
"~/.config/opencode",
"~/.local/share/opencode",
"~/.local/state/opencode",
]
[tools.claude]
mount = [
"~/.claude",
"~/.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_env
is true, this is merged into the host environment variables. Each argument is a string in one of two forms:KEY=VALUE
: SetKEY
toVALUE
.VALUE
can be the empty string in which case it is set to the empty string in the sandbox too.KEY
: PassKEY
from the host environment through. If the variable is unset, it is left unset in the sandbox. Otherwise,KEY
is 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 binary for the target.
bwrap -
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.
↩