- Python 91.9%
- Shell 8.1%
| src/agentbox | ||
| .gitignore | ||
| .python-version | ||
| prek.toml | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
agentbox
Fair warning: this project is partially vibe-coded (i.e., there is AI-written code I have not fully reviewed -- especially the bash scripts). Use at your own risk.
The purpose of this project is to run "agentic" tools such as codex and claude-code in a sandboxed environment. A particular problem with these tools is that they default to too-permissive setups. Codex defaults to allow-read across your entire filesystem. Claude-code disables the sandbox by default, and instead uses (likely vibe-coded) static analysis of bash commands. Beyond the incorrect defaults these tools use, you have to trust that there are no bugs in their implementation.
Agentbox adds another layer of protection by using buildah working containers.
By default, agentbox mounts your current workspace read/write, with no other mounts. This blocks
all reads and modification of your host system, but allows the agent to interact with the
codebase. You can change how the workspace is mounted per run with
--mode.
A non-goal of this project is blocking intentionally malicious actions, and I make no guarantees about the security of agentbox.
Installation and Basic Usage
uv tool install git+https://forgejo.circuitsacul.dev/circuitsacul/agentbox.git
# create a box
agentbox box new default
# enter the box (new session; mounts your _current_ folder read/write)
agentbox run default
# options go before the box name, e.g. mount the workspace read-only:
agentbox run --mode read default
Changes made inside a box are persistent, so you can install tools and setup configuration for them.
Workspace mount modes
agentbox run mounts your current directory into the box at /home/agent/session/workspace.
The --mode (-m) flag selects how.
--mode |
workspace mount |
|---|---|
write (default) |
current directory, read/write |
read |
current directory, read-only (:ro) |
overlay |
current directory via an overlay (:O); writable in the box, changes discarded on exit |
none |
no project mount; a fresh, ephemeral scratch directory unique to each run |
agentbox run --mode read default # let the agent read the repo but not modify it
agentbox run --mode overlay default # writable view of the repo; all changes vanish on exit
agentbox run --mode none default # scratch session; the repo is not mounted at all
overlay uses buildah's overlay volume flag (:O): your current directory is the read-only
lower layer, and all writes go to a temporary upper layer in container storage that buildah
discards when the run exits -- workspace writes in the box never modify the host directory.
Each run gets its own upper layer; nothing is shared between sessions. Avoid modifying the
directory on the host while an overlay run is active (overlayfs behavior is undefined when the
lower layer changes underneath it). Rootless overlay requires kernel overlayfs-in-userns
support or fuse-overlayfs on the host; buildah errors out if neither is available.
none yields an empty workspace that is discarded when the run exits (never shared between
sessions). Everything outside the workspace persists as normal, only the workspace is ephemeral.
Scratch dirs live under $XDG_DATA_HOME/agentbox/scratch and are cleaned up on a best-effort
basis.
Configuration
Agentbox reads config from $XDG_CONFIG_HOME/agentbox/config.json. If it does not exist, it will
be seeded on first run from builtin defaults. Once it exists, it overrides the builtin config.
Built-in defaults
You can find the current built-in defaults in the source code at src/data/config.json. It just
forwards some useful env vars and sets the HOME variable to /home/agent.
Schema
| key | type | meaning |
|---|---|---|
mounts |
list of mount objects | extra bind mounts for agentbox run |
forward_env |
list of names | host env vars copied into the box if set |
forward_env_prefixes |
list of prefixes | forward every host var whose name starts with one of these |
set_env |
map name→value | env vars set in the box; overrides anything forwarded |
flags.create |
list of tokens | raw flags appended to buildah from (box creation) |
flags.run |
list of tokens | raw flags appended to buildah run |
Mount object:
| field | default | meaning |
|---|---|---|
source |
— | host path; $VARS and ~ are expanded |
dest |
(required) | absolute container path; used literally (not expanded) |
options |
"rw" |
volume options, e.g. "ro" (read-only) |
if_missing |
"skip" |
when source is missing: skip (omit the mount), create (make a directory — fails if it already exists as a file), require (fail the run) |
$VARS and ~ in source are expanded; an unknown $VAR is left verbatim. dest is
used literally and must be an absolute container path.
Env handling
Agentbox applies forward_env_prefixes and forward_env first, then applies set_env (i.e.,
set_env can override forwarded env variabled.)
Raw flags
flags.create and flags.run are passed through buildah. Each item is a single token (they are
quoted.) As such, either use the "--flag=value" format, or split into two tokens ("--flag", "value".)
flags.create only applies to new boxes; flags.run applies on every run. If you need to alter
create-time flags for a box, see Snapshots.
SELinux
agentbox creates every box with SELinux confinement disabled
(buildah from --security-opt label=disable), so the box process runs unconfined and can
read the bind-mounted workspace without relabeling the host's files. You therefore never need
the z/Z relabel option on a mount.
Snapshots
A snapshot is a buildah commit of a box's filesystem, stored as an image under the
agentbox-snapshot repository.
| command | what it does |
|---|---|
agentbox snapshot new <box> [--name NAME] |
commit <box> to a snapshot; default name is {utc-timestamp}-{box}-{short-image-id} |
agentbox snapshot list [--box BOX] |
table of snapshots: source box, size, created, and dependent boxes; --box filters by source box |
agentbox snapshot delete <name> |
delete a snapshot |
agentbox snapshot prune [--box BOX] [--yes] |
delete every snapshot with no dependent boxes; --box to filter by source box |
agentbox box new <name> --from <snapshot> |
create a box from a snapshot (with current flags.create) |
Recipe: change a create flag on an existing box
flags.create only applies to a freshly created box. You can workaround this via snapshots:
- Add the flag to
flags.createin$XDG_CONFIG_HOME/agentbox/config.json. agentbox snapshot new <current box>; this yields a snapshot nameagentbox box new <new box> --from <snapshot>- Confirm the new box works, then delete the old box:
agentbox box delete <old box>.
Recipe: clipboard / image paste (Wayland)
Lets clipboard-image paste work inside the box by exposing the host Wayland socket. Add to your
config at $XDG_CONFIG_HOME/agentbox/config.json:
{
"mounts": [
+ {
+ "source": "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY",
+ "dest": "/home/agent/.runtime/wayland-0",
+ "options": "rw"
+ }
],
"set_env": {
+ "XDG_RUNTIME_DIR": "/home/agent/.runtime",
+ "WAYLAND_DISPLAY": "wayland-0"
}
}
Then, once, inside the box: sudo dnf install -y wl-clipboard.
Recipe: per-box virtualenv (shadow .venv)
The workspace is bind-mounted into the box, so a host .venv (built for the host)
would leak in and conflict. Keep a separate host .venv.agentbox and mount it over the
workspace's .venv so the box gets its own. Add this to your config mounts:
{
"source": "$PWD/.venv.agentbox",
"dest": "/home/agent/session/workspace/.venv",
"options": "rw"
}
The default behavior for mounts is to skip if the source path does not exist. As such, you will
need to manually create the .venv.agentbox folder the first time for it to work.