Agentbox is a sandboxing environment for coding harnesses to work without worrying about affecting the host system
  • Python 98.7%
  • Dockerfile 1.3%
Find a file
2026-08-12 18:23:06 -04:00
.agentbox Harden podman run with dropped capabilities and no-new-privileges 2026-08-01 19:21:54 -04:00
.kilo Removed unnecessary planning files 2026-08-10 17:32:08 -04:00
.tasks/kilo-support Added plans for kilocode support 2026-07-13 13:34:12 -04:00
src/agentbox Added supplementary group passthrough configuration 2026-08-12 18:09:09 -04:00
tests Fixed signing tests with SSH signing config 2026-08-12 18:23:06 -04:00
.gitignore Moved to a nested gitignore 2026-07-15 02:05:13 -04:00
agentbox.toml Remove devcontainer support 2026-07-15 05:29:29 -04:00
pyproject.toml Added typechecking to the project dependencies 2026-08-10 16:53:27 -04:00
README.md Added supplementary group passthrough configuration 2026-08-12 18:09:09 -04:00
uv.lock Added typechecking to the project dependencies 2026-08-10 16:53:27 -04:00

agentbox

Run AI coding agents inside rootless Podman containers. Supported harnesses are Codex and Kilo Code.

The default flow is intentionally interactive:

  1. agentbox creates an ephemeral local clone under .agentbox/runs/.
  2. The original checkout is not mounted into the container.
  3. The selected harness runs interactively with full permissions inside the container.
  4. When the harness exits, you decide whether to pull committed work back.

Setup

uv run agentbox init
uv run agentbox doctor
uv run agentbox codex build
uv run agentbox kilo build

agentbox init creates these local files independently and never overwrites one when it already exists:

  • agentbox.toml
  • .agentbox/codex/Containerfile
  • .agentbox/kilo/Containerfile
  • .agentbox/kilo/kilo.jsonc

Each Containerfile is the mutable local definition of its managed harness image. Edit one when you need a custom base image or additional tools for that harness. When a Containerfile is first generated, agentbox pins the configured base_image by digest: the tag is pulled and the FROM line is recorded as ubuntu:24.04@sha256:<digest> (the manifest-list digest, so the pin stays usable across architectures). A base_image that already carries a digest is used verbatim. If no registry digest can be resolved (for example offline, or a locally built base image), the Containerfile is written unpinned with a warning.

Run tests with:

uv run python -m unittest discover -s tests -v
uv run ruff check

agentbox expects rootless Podman. Each harness driver declares its own state mounts and environment, while agentbox validates and renders Podman sandboxing, workspace mounts, image management, and run lifecycle behavior centrally. Codex uses CODEX_HOME=/codex-home, preferring the host CODEX_HOME environment variable when set, otherwise ~/.codex.

Containers run with --cap-drop=ALL and --security-opt=no-new-privileges, and the managed images do not include sudo. Network access remains unrestricted because harnesses require API and package access. The isolation boundary is the unmounted host checkout together with rootless --userns=keep-id, not network isolation.

Projects can opt into additional Linux capabilities after the default drop by setting runtime.capabilities, for example:

[runtime]
capabilities = ["SYS_ADMIN", "SYS_CHROOT"]

Additional Podman security options can be passed through with runtime.security_options, for example:

[runtime]
security_options = ["unmask=ALL"]

Agentbox always retains no-new-privileges; configured options cannot remove it. Security options are passed to Podman as written and can materially weaken container isolation or expose host resources.

Device passthrough can be configured using full Podman device specifications:

[runtime]
devices = ["/dev/example", "/dev/source:/dev/target:rwm"]
keep_groups = true

Agentbox passes each value directly to Podman. Configured devices increase the container's access to host hardware and should be enabled only when required. Set keep_groups = true when a rootless container needs the caller's supplementary group access for a device. This passes --group-add=keep-groups to Podman, preserves all supplementary groups rather than selected groups, requires the crun OCI runtime, and is unavailable with remote Podman clients.

On SELinux-enforcing hosts, rootless Podman bind-mounts devices with their host labels. Access may be denied unless an administrator allows containers to use device labels:

sudo setsebool -P container_use_devices=true

This changes host-wide SELinux policy, so enable it only on systems where container device access is required.

Run Harnesses

uv run agentbox codex run
uv run agentbox kilo run

By default, agentbox uses the current harness Containerfile contents to select a managed image tag:

agentbox-codex:<full-containerfile-sha256>
agentbox-kilo:<full-containerfile-sha256>

agentbox <harness> build skips the Podman build when that exact image already exists locally. agentbox <harness> run and agentbox <harness> shell automatically build the current managed image when it is missing.

Each run also snapshots the Containerfile used to build its image into .agentbox/runs/<run-id>/Containerfile. This keeps runs reproducible: even after you edit .agentbox/<harness>/Containerfile (which changes the managed image tag), agentbox runs enter and agentbox <harness> shell --run can rebuild the run's original image from its snapshot when it is no longer present locally.

Editing the Containerfile produces a new content-addressed tag, so old images accumulate over time. Manage them with:

uv run agentbox codex images          # list managed images (current/referenced)
uv run agentbox codex prune           # remove images no run still references
uv run agentbox codex prune --dry-run # show what prune would remove
uv run agentbox kilo images
uv run agentbox kilo prune

prune keeps the current managed image and any image referenced by a saved run. Managed images pin the base image by digest (resolved when the Containerfile is generated) and harness tools by version. The pin does not move when the registry tag is updated; to refresh a dependency, intentionally edit its version or digest — or delete the Containerfile to have it regenerated with a fresh digest — and rebuild with:

uv run agentbox codex build --rebuild
uv run agentbox kilo build --rebuild

The build's --pull=newer behavior cannot refresh a digest-pinned base image; the pinned digest must be updated first.

Pass --image IMAGE to bypass the managed Containerfile image entirely:

uv run agentbox codex run --image ubuntu:24.04
uv run agentbox codex shell --image localhost/custom-codex:dev
uv run agentbox kilo run --image localhost/custom-kilo:dev

The override is passed directly to Podman and recorded in run metadata as-is; agentbox does not check, pull, or build it first.

If a dry-run would need to generate a Containerfile, digest resolution is deferred and the printed managed-image tag contains <containerfile-digest>. That placeholder describes the eventual content-addressed tag but cannot be copied from dry-run output to pre-build the image.

If the checkout is dirty, the CLI prompts before copying dirty file contents into the isolated clone. In non-interactive use, choose explicitly:

uv run agentbox codex run --dirty include
uv run agentbox codex run --dirty ignore
uv run agentbox kilo run --dirty ignore

New run clones inherit only Git commit identity from the host checkout. agentbox resolves user.name and user.email from CLI flags, then [git] config, then git config --get in the original repo, and writes resolved values into the run clone's local .git/config:

[git]
user_name = "Your Name"
user_email = "you@example.com"
sign_imports = false
uv run agentbox codex run --git-user-name "Your Name" --git-user-email you@example.com
uv run agentbox codex shell --git-user-name "Your Name" --git-user-email you@example.com
uv run agentbox kilo run --git-user-name "Your Name" --git-user-email you@example.com

Set sign_imports = true or pass --sign-imports to rewrite imported run commits on the host with git cherry-pick -S. This keeps signing keys out of the sandbox. --no-sign-imports disables that behavior for a command. Signed imports create/update the agentbox/<run-id> branch; ff-only remains an exact-history operation and is not available while signed imports are enabled.

When the run finishes, agentbox shows a compact git log --oneline preview of commits in the run that are not on the host branch, then prompts:

[b] Import to branch agentbox/<run-id>
[f] Fast-forward <branch> to <commit>
[l] Leave in run for later review (default)

In non-interactive use, choose explicitly:

uv run agentbox codex run --pull branch
uv run agentbox codex run --pull ff-only
uv run agentbox codex run --pull later
uv run agentbox kilo run --pull later

Codex launches as:

codex --cd <workspace> --sandbox danger-full-access --ask-for-approval never

Kilo launches as:

kilo [<prompt>]

Both managed images include uv so harness sessions can run Python project setup and test commands; Kilo itself does not require uv.

These full-permission modes are safe only because they run against the isolated clone, not the original checkout.

Kilo runs as the image's ubuntu user. Host Kilo XDG data is mounted read-write at /home/ubuntu/.local/share/kilo, using the host XDG_DATA_HOME default or override. This keeps authentication shared: Kilo stores auth.json in XDG data. Agentbox creates this mutable host-backed directory when needed; Podman assigns it to the user running Kilo; doctor reports its first-use absence as a warning. Host XDG cache and state are not mounted.

Each saved run mounts <run_store>/<run-id>/home at /home/ubuntu. Everything Kilo writes under its home persists when re-entering the same run, is isolated from the host and other runs, and is removed by agentbox runs prune. Existing host home contents remain untouched and are never copied into runs. New runs initialize their home from /home/ubuntu in the selected image, then snapshot an existing host XDG_STATE_HOME/kilo/model.json into their home state tree. This optional model seed is non-fatal if missing or unreadable, does not propagate later host changes, and is not applied retroactively to existing saved runs.

Kilo global configuration is mounted read-only: XDG_CONFIG_HOME/kilo (or ~/.config/kilo), ~/.kilo, ~/.kilocode, and KILO_CONFIG_DIR when set. No missing config directory is created. agentbox init also creates the repository-owned .agentbox/kilo/kilo.jsonc, mounted read-only at /agentbox/config/kilo.jsonc and set as KILO_CONFIG. This file is read from the host repository root rather than the isolated clone, so a run cannot change the config that started it.

When .agentbox/kilo/kilo.jsonc exists, it wins over a host KILO_CONFIG and agentbox prints a warning identifying the ignored host path. If it does not exist, a host KILO_CONFIG is mounted read-only and used normally. Project Kilo configuration, commands, agents, and skills remain available from the isolated run clone, including kilo.json, .kilo/, and legacy .kilocode/ paths.

Bring Work Back

The end-of-session prompt can import committed work into agentbox/<run-id>, fast-forward the current branch when it is safe, or leave the run for later review. Fast-forward requires a clean host worktree, the same branch the run was created from, and no host-only commits outside the run history.

Pull handling intentionally runs even after a non-zero harness exit so non-interactive pull modes can import work from a failed run.

List saved runs:

uv run agentbox runs list

Open a shell in a run:

uv run agentbox runs enter <run-id>

runs enter and <harness> shell --run accept --image to override the saved image for that session without rewriting run metadata.

Import committed work from a run as a new local branch:

uv run agentbox runs import <run-id>
git switch agentbox/<run-id>

Uncommitted changes are never auto-committed. Enter the run and handle them manually.