Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Agent-box & Portal documentation

Welcome. This site documents two related projects:

  • Agent-box: workspace + container orchestration for autonomous coding agents
  • Portal: host capability mediation service (Unix socket + MessagePack), usable standalone

If you are new, start with Choose your path.

Demo

Agent-box demo

(Click images to open in lightbox. Diagrams use mdbook-excalidraw with built-in zoom/pan.)

Diátaxis layout

  • Tutorials: guided learning paths
  • How-to guides: practical task playbooks
  • Reference: CLI/config contracts
  • Explanation: architecture and rationale

Current status

Portal is still evolving. Where behavior is policy-sensitive, reference pages document defaults and configurable alternatives.

Choose your path

Use this quick routing guide to pick the right docs entry point.

I want sandboxed local agent sessions

Use Agent-box.

Start with: Tutorial: Agent-box first run

I want host capability mediation, but no container orchestrator

Use Portal standalone.

Start with: Tutorial: Portal standalone first run

I want Agent-box with Portal-enabled wrappers

Use both.

Start with: Tutorial: Connect Portal to Agent-box

What’s the difference?

  • Agent-box manages repositories, workspaces, and containers.
  • Portal brokers selected host operations over a Unix socket with policy controls.
  • Wrappers (for example wl-paste, gh) provide transparent, tool-compatible access to Portal methods.

Tutorials

Tutorials are learning-oriented and linear. Pick one path and follow it end-to-end.

Tutorial: Agent-box first run

Outcome

You create a workspace and start a containerized session with ab.

Prerequisites

  • ab installed
  • Docker or Podman available
  • ~/.agent-box.toml created with workspace_dir, base_repo_dir, and runtime.image

Steps

  1. Check basic CLI access:

    ab info
    
  2. Create a workspace (JJ by default):

    ab new myrepo -s first-session
    
  3. Spawn the container:

    ab spawn -r myrepo -s first-session
    
  4. Inside the container, verify where you are:

    pwd
    

    You should be in the workspace path managed by Agent-box.

What you learned

  • How to create a named session workspace
  • How to spawn a container bound to that workspace

Next: see How-to guides for custom runtime and CI usage.

Tutorial: Portal standalone first run

Outcome

You run agent-portal-host and send successful requests with agent-portal-cli.

Prerequisites

  • agent-portal-host and agent-portal-cli installed
  • ~/.agent-box.toml with [portal] enabled
  • wl-paste available on host if testing clipboard method

Minimal config

[portal]
enabled = true
socket_path = "/run/user/1000/agent-portal/portal.sock"

[portal.policy.defaults]
clipboard_read_image = "allow"
gh_exec = "ask_for_writes"

Steps

  1. Start the host service in a terminal:

    RUST_LOG=info agent-portal-host
    
  2. In another terminal, verify connectivity:

    agent-portal-cli ping
    agent-portal-cli whoami
    
  3. Optionally test image clipboard request:

    agent-portal-cli clipboard-read-image --out /tmp/clip.bin
    

What you learned

  • Portal works independently of Agent-box
  • Portal methods are reachable over a Unix socket via official CLI

Next: Portal wrapper contract.

Tutorial: Connect Portal to Agent-box

Outcome

You run an Agent-box container where tools can use Portal through wrapper binaries.

Prerequisites

  • Agent-box setup working (ab spawn succeeds)
  • Portal host running (agent-portal-host)
  • Wrappers installed in container image or mounted into container PATH

Steps

  1. Enable portal in config:

    [portal]
    enabled = true
    socket_path = "/run/user/1000/agent-portal/portal.sock"
    
    [portal.policy.defaults]
    clipboard_read_image = "allow"
    gh_exec = "ask_for_writes"
    
  2. Start portal host on the machine running containers:

    agent-portal-host
    
  3. Spawn an Agent-box session:

    ab spawn -r myrepo -s portal-session
    

    Agent-box mounts the configured socket and sets AGENT_PORTAL_SOCKET in the container.

  4. In the container, validate wrapper-backed flow:

    wl-paste --list-types
    

    If wrappers are in PATH and policy allows, this returns an image MIME type when present.

What you learned

  • How Agent-box and Portal integrate
  • How wrappers keep calling conventions tool-compatible

How-to guides

How-to guides are task-oriented. Use these when you already understand the basics and need to get work done.

How to run Portal with a custom wrapper

Goal

Add a wrapper binary that forwards a host-capability request through Portal.

Prerequisites

  • Running agent-portal-host
  • Access to socket path (AGENT_PORTAL_SOCKET or config default)
  • Wrapper implemented in Rust (wrappers/ convention)

Steps

  1. Define wrapper CLI shape that matches the tool you want to emulate.
  2. In wrapper, create a PortalClient using env/config resolution.
  3. Translate wrapper input into a Portal method request.
  4. Forward host response bytes/stdout/stderr to wrapper stdout/stderr.
  5. Exit with mapped exit code from portal response.
  6. Place wrapper ahead of native utility on PATH in target environment.

Validation

  • Wrapper command succeeds against live agent-portal-host.
  • Expected output format matches calling tool expectations.
  • Failure path returns useful stderr and non-zero code.

How to debug Portal wrapper failures

Goal

Find and fix common failures for wl-paste/gh wrappers and other Portal clients.

Quick checklist

  1. Is host service running?
    pgrep -a agent-portal-host
    
  2. Can you ping the socket directly?
    agent-portal-cli ping
    
  3. Is the wrapper using the expected socket path?
    echo "$AGENT_PORTAL_SOCKET"
    
  4. Enable host logs:
    RUST_LOG=debug agent-portal-host
    

Common failures

  • failed to connect to socket
    • socket path mismatch or host service not running
  • denied
    • policy mode blocks method/container
  • prompt_failed
    • prompt_command missing or exits non-zero in ask-mode
  • clipboard_failed
    • no allowed image MIME currently in clipboard or host wl-paste issue
  • gh_exec_failed
    • host gh unavailable or command failure

Next actions

  • Confirm [portal.policy] defaults and overrides.
  • Confirm wrapper is first on PATH in container.
  • Re-run request via agent-portal-cli to isolate wrapper-specific parsing issues.

How to enable pi image paste via portal wrapper

Goal

Make image paste work transparently in pi sessions using portal-backed wl-paste wrapper.

Requirements

  1. agent-portal-host running on host
  2. Portal enabled in ~/.agent-box.toml
  3. Wrapper binary (wl-paste) available in container PATH before system wl-paste

Steps

  1. Start portal host:
agent-portal-host
  1. Spawn with Agent-box (portal enabled):
ab spawn -s my-session
  1. In container/session env:
export WAYLAND_DISPLAY=wayland-1
# AGENT_PORTAL_SOCKET is injected by ab when portal is enabled
  1. Validate wrapper flow:
wl-paste --list-types
wl-paste --type image/png --no-newline

Notes

  • Policy is enforced host-side by agent-portal-host.
  • Wrapper should not prompt in-container.

Agent-box: use profiles effectively

Goal

Use profiles to keep ~/.agent-box.toml modular and compose task-specific runtime settings.

When to use this

Use profiles when you want to:

  • avoid one giant [runtime] block
  • share reusable config chunks (for example nix, gpg, rust)
  • apply extra settings only for specific sessions

Step 1: Define reusable profiles

Create named profiles under [profiles.*]:

[profiles.nix]
env = ["NIX_REMOTE=daemon"]
mounts.ro.absolute = ["/nix/store"]
mounts.rw.absolute = ["/nix/var/nix/daemon-socket/"]

[profiles.rust]
mounts.rw.home_relative = ["~/.cargo"]

[profiles.gpg]
mounts.rw.absolute = [
  "/run/user/1000/gnupg/S.gpg-agent:~/.gnupg/S.gpg-agent",
]

Step 2: Create a baseline profile

Compose a default profile with extends:

default_profile = "base"

[profiles.base]
extends = ["nix", "rust"]

This profile is automatically applied to ab spawn.

Step 3: Add extra profiles per command

Apply additional profiles in CLI order:

ab spawn -r myrepo -s mysession -p gpg

You can stack multiple flags:

ab spawn -r myrepo -s mysession -p rust -p gpg

Step 4: Verify the resolved config

Inspect what Agent-box will actually use:

ab dbg resolve
ab dbg resolve -p rust -p gpg

Validate that your config is accepted:

ab dbg validate

Merge behavior checklist

  • Scalars: later layers override earlier layers
  • Arrays: values append
  • Objects: merge recursively
  • Profile order matters for scalar conflicts

How to customize Agent-box container runtime

Goal

Adjust runtime backend, image, entrypoint, mounts, ports, hosts, and network mode.

Steps

  1. Edit ~/.agent-box.toml:

    [runtime]
    backend = "podman" # or "docker"
    image = "agent-box:latest"
    entrypoint = "/bin/bash"
    ports = ["8080:8080"]
    hosts = ["host.docker.internal:host-gateway"]
    
    [runtime.mounts.ro]
    absolute = ["/nix/store"]
    
    [runtime.mounts.rw]
    home_relative = ["~/.local/share"]
    
  2. Validate configuration:

    ab dbg validate
    
  3. Preview resolved profile/runtime:

    ab dbg resolve -p your-profile
    
  4. Override per spawn when needed:

    ab spawn -s demo -p rust -P 3000:3000 -H myhost:10.0.0.1 --network=bridge
    

Notes

  • --network=host and port/host mappings may conflict on Docker runtime.
  • Overlay mount mode (o) is Podman-only.

How to forward GPG agent to Agent-box containers

Goal

Use host GPG keys/signing from inside container sessions.

Steps

  1. Discover host socket locations:
gpgconf --list-dirs
  1. Configure overlay mount for ~/.gnupg (Podman):
[runtime.mounts.o]
home_relative = ["~/.gnupg"]

[runtime.mounts.rw]
home_relative = [
  "/run/user/1000/gnupg/S.gpg-agent:~/.gnupg/S.gpg-agent",
  "/run/user/1000/gnupg/S.keyboxd:~/.gnupg/S.keyboxd",
]
  1. Spawn session and test:
ab spawn -s my-session
gpg --list-secret-keys

Notes

  • Replace 1000 with your UID.
  • For smartcards, add S.scdaemon socket mapping.
  • Docker does not support overlay mounts; Podman is preferred here.

Troubleshooting

  • Lock conflicts: remove stale locks (find ~/.gnupg -name '.#lk*' -delete)
  • IPC issues: verify socket path with gpg-connect-agent 'getinfo socket_name' /bye

How to share host Nix store with containers

Goal

Use host Nix binaries/store paths from inside Agent-box containers.

Configuration

[runtime.env]
# alternatively set in runtime.env as KEY=VALUE string entries

[runtime]
env = ["NIX_REMOTE=daemon"]

[runtime.mounts.ro]
absolute = ["/nix/store"]

[runtime.mounts.rw]
absolute = ["/nix/var/nix/daemon-socket/"]

Verify

ab spawn -s my-session
nix --version

Notes

  • Store is mounted read-only.
  • Daemon socket mount enables requesting builds/fetches through host daemon.

Explanation

Explanation pages provide architecture, rationale, and trade-offs.

Use this section to build mental models, then jump to tutorials/how-to/reference for action-oriented details.

Portal architecture overview

Portal is a host-side mediation layer for selected capabilities that should not be exposed directly to containers.

Core model

  • agent-portal-host listens on a Unix socket.
  • Clients send MessagePack requests with method payloads.
  • Host resolves caller identity from peer credentials.
  • Host applies policy (default + per-container overrides).
  • Host performs allowed operation and returns structured response.

Why this model

Direct host socket passthrough is broad and hard to audit. Portal centralizes control, policy, and observability behind method-level requests.

Implemented methods

  • ping
  • whoami
  • clipboard.read_image
  • gh.exec

Important constraints

  • Prompting happens host-side, never in container wrappers.
  • Timeouts and rate limiting are explicit configuration concerns.
  • Host binary resolution avoids recursive wrapper execution.

Agent-box architecture overview

Agent-box orchestrates safe, disposable execution environments for autonomous coding agents.

Core responsibilities

  • Discover and resolve repositories under base_repo_dir
  • Create workspaces (JJ or Git)
  • Build container runtime config from layered settings + profiles + CLI overrides
  • Spawn containers with deterministic mounts, env, networking, and entrypoint

Layered configuration model

  • Global config defines defaults.
  • Repo-local config refines project-specific behavior.
  • Profiles provide composable bundles (mounts/env/ports/hosts/context).

Runtime abstraction

Agent-box supports Docker and Podman backends through runtime-specific implementation while preserving one CLI surface.

Relationship to Portal

Agent-box can mount Portal socket and export AGENT_PORTAL_SOCKET, but Portal remains optional and independently operable.

Agent-box workflow internals

This page explains the runtime flow behind ab new and ab spawn.

Repository/workspace model

  • Source repositories are discovered under base_repo_dir.
  • Workspaces are created under workspace_dir.
  • Workspace mode is either JJ workspace or Git worktree.

ab new flow

  1. Resolve repository ID (explicit or from current directory).
  2. Choose workspace type (JJ default, or Git).
  3. Create workspace for selected session name.

ab spawn flow

  1. Resolve workspace path (--session mode) or current dir (--local).
  2. Load and validate layered configuration.
  3. Resolve profile graph (default_profile + CLI profiles).
  4. Build runtime-specific container configuration.
  5. Apply mounts/env/ports/hosts/network options.
  6. Mount portal socket and set AGENT_PORTAL_SOCKET when portal is enabled.
  7. Execute selected runtime backend (Podman or Docker).

Path resolution notes

  • Home-relative paths are translated for host/container user homes.
  • Relative mount source paths are resolved from current working directory.
  • Symlinked paths are expanded to preserve resolution behavior inside container.

Portal and Agent-box relationship

Portal and Agent-box are complementary, not coupled.

Independent concerns

  • Agent-box: workspace + container lifecycle orchestration
  • Portal: host capability mediation with policy/prompt/audit boundary

Combined flow

When used together:

  1. Agent-box starts container and mounts portal socket.
  2. Agent-box sets AGENT_PORTAL_SOCKET in container env.
  3. Wrapper/tool in container calls portal client.
  4. Portal host enforces policy and executes host operation.

Request flow diagram

Use scroll/pinch to zoom, drag to pan (Excalidraw interactive viewer).

flowchart LR
  A[Tool in container] --> B[Wrapper binary in container]
  B -->|MessagePack over Unix socket| C[agent-portal-host on host]
  C --> D{Policy decision}
  D -->|allow| E[Execute host capability]
  D -->|ask| F[Prompt command]
  F -->|approved| E
  F -->|denied or timeout| G[Return denial]
  E --> H[Return stdout stderr bytes and exit code]
  G --> H
  H --> B
  B --> A

Why keep them separable

  • Portal can serve non-Agent-box environments.
  • Agent-box remains useful without Portal methods.
  • Integration boundary stays explicit (socket + env), reducing hidden coupling.

ADRs and rationale

The accepted Portal ADRs are now part of this book.

ADR list

  1. ADR 0001: Host Portal Service for Container-to-Host Capabilities
  2. ADR 0002: Transparent Portal Access via Wrapper Binaries

Read them in order.

ADR 0001: Host Portal Service for Container-to-Host Capabilities

  • Status: Accepted
  • Date: 2026-02-27

Context

We need a secure way for containers to access selected host capabilities (starting with host clipboard image read), with optional user approval similar to xdg-desktop-portal behavior.

Directly exposing host Wayland/X11 sockets to containers is too broad and does not provide granular per-action approval/policy controls.

Decision

Implement a separate host binary (agent-portal-host) that runs as a systemd user service and listens on a Unix socket for container requests.

An official CLI client (agent-portal-cli) will be provided.

1. Identity and container attribution

  • Use Unix socket peer credentials (SO_PEERCRED) to identify the caller process.
  • Resolve peer PID + namespace/cgroup metadata back to a Podman container ID.
  • Display resolved Podman container identity to the user in approval prompts.

2. Protocol and transport

  • Use Unix domain sockets.
  • Use MessagePack as the on-wire protocol.
  • Design protocol to be method-based and extensible for future host capabilities.

3. User prompt integration

  • Prompting is done via configurable dmenu-style command.
  • User can configure a command such as rofi -dmenu ....

4. Policy model

  • Policy is configurable.
  • It must support allowing clipboard.read_image without prompting.
  • Per-method/per-container policy overrides are supported.

5. Initial clipboard scope

  • First capability: clipboard.read_image only.
  • Restrict to image MIME types (allowlist).
  • Log request decisions and outcomes.

6. Operational safeguards

  • Handle multiple containers concurrently.
  • Apply concurrency limits.
  • Apply per-container and/or global rate limiting.
  • Timeouts are configurable; 0 means no timeout.

7. Configuration source

  • Reuse ~/.agent-box.toml for portal configuration.
  • Use a dedicated namespaced section (e.g. [portal]).

8. Data transfer strategy

  • MVP may return image bytes inline in MessagePack with size limits.
  • File descriptor passing (SCM_RIGHTS) is acknowledged and can be added later for efficient large payload transfer across the Unix socket boundary.

Consequences

Positive

  • Fine-grained host capability mediation for containers.
  • Better security posture than mounting host display/session sockets.
  • User-visible approval flow with container attribution.
  • Extensible foundation for future portal methods.

Trade-offs

  • Host daemon becomes a sensitive trust boundary and must be hardened.
  • Identity resolution from PID/ns/cgroup to Podman ID must be robust.
  • Prompt UX and policy defaults affect safety and usability.

Follow-up implementation tasks

  1. Add agent-portal-host binary skeleton and Unix socket server.
  2. Implement peer credential extraction and Podman container ID resolution.
  3. Define MessagePack request/response schema and versioning.
  4. Implement clipboard.read_image method with MIME allowlist and size limits.
  5. Add prompt adapter with configurable dmenu-style command.
  6. Add policy/rate-limit/timeout plumbing sourced from ~/.agent-box.toml.
  7. Add structured audit logging.
  8. Add agent-portal-cli official CLI client (ping, request method, dump output).

ADR 0002: Transparent Portal Access via Wrapper Binaries

  • Status: Accepted
  • Date: 2026-02-27

Context

Portal-based host capability access is safer than direct host socket passthrough, but requires callers to know and integrate a custom API.

Many agent tools (e.g. pi) already call standard host utilities directly (such as wl-paste). Requiring every agent/tool to learn portal APIs is not transparent and hurts adoption.

Decision

Provide transparent wrapper binaries that mimic standard utility behavior while internally using the portal socket API.

Initial wrapper:

  • wl-paste wrapper using portal clipboard.read_image.

Implementation structure:

  • New top-level wrappers/ crate for compatibility binaries.
  • Shared Rust portal client API in common (portal_client) for direct Rust consumers.

Scope (MVP)

The wrapper supports the Wayland usage pattern required by pi image paste flow:

  1. wl-paste --list-types
  2. wl-paste --type <selected-mime-type> --no-newline

Behavior:

  • --list-types: returns the currently available clipboard image MIME type exposed by portal.
  • --type ... --no-newline: returns raw image bytes from portal, enforcing MIME match when requested.

Consequences

Positive

  • Agent/tools remain unaware of portal internals.
  • Drop-in compatibility improves usability.
  • Single mediation point (portal) keeps policy/security controls centralized.

Trade-offs

  • Wrapper compatibility must track behavior of real utilities.
  • Some utility flags may not be supported initially.
  • Extra binary indirection adds maintenance surface.

Follow-up

  • Expand wrapper flag compatibility as needed.
  • Add more wrappers for other host capability entry points.
  • Consider packaging wrappers on PATH ahead of native tools inside container images.

Security model

This section explains the security posture of using Portal and Agent-box together or separately.

Threat reduction goals

  • Avoid broad host socket exposure to containers.
  • Add policy gates to sensitive host operations.
  • Provide audit-friendly mediation point.

Trust boundaries

  • Container: untrusted or semi-trusted agent execution context.
  • Portal host: trusted broker enforcing method policy.
  • Host binaries (gh, wl-paste): executed only by host broker.

Control mechanisms

  • Unix socket peer credentials for caller identity.
  • Method-level policy modes (allow/ask/deny, gh_exec policy modes).
  • Optional prompt-based approval command.
  • Concurrency limits, rate limits, and request/prompt timeouts.
  • Clipboard MIME allowlist and payload size limit.

Residual risks

  • Misconfigured policy can allow broader access than intended.
  • Prompt UX failures can degrade safety or usability.
  • Host broker is a sensitive component and must be monitored and updated.

Operational guidance

  • Keep policies explicit and minimal.
  • Use deny-by-default for high-risk methods where appropriate.
  • Enable debug logs during rollout and review outcomes.

Reference

Reference pages are factual and scannable.

Use these when you need exact command flags, config keys, and behavior contracts.

Do not edit by hand. Regenerate with:

nu docs/scripts/generate-cli-reference.nu

Portal CLI reference

agent-portal-host

Command:

cargo run -q -p agent-portal --bin agent-portal-host -- --help

Output:

2026-03-01T16:49:06.778024Z  INFO agent_portal_host: PATH path=["/nix/store/3vs2fr2mazafcdwyza15bfhpmccx1k7z-patchelf-0.15.2/bin", "/nix/store/r9wbjib6xxjkyb9yvjvrkl4sq61i2lyn-gcc-wrapper-15.2.0/bin", "/nix/store/qarrb8yfby1yyypm32vabzgxgq3w41ma-gcc-15.2.0/bin", "/nix/store/7ri1mm5y99nkr7657r77wycrvhg7z9x0-glibc-2.40-66-bin/bin", "/nix/store/d75200gb22v7p0703h5jrkgg8bqydk5q-coreutils-9.8/bin", "/nix/store/dwiyp91lmxq864plaas14jm14m87sg3f-binutils-wrapper-2.44/bin", "/nix/store/cl88v2m1y5q3k6jlkq5jjf73nmfgl1px-binutils-2.44/bin", "/nix/store/9db7xb1axdgqp4lakdxbp27yh7kfvm0b-cargo-1.91.1/bin", "/nix/store/lvdr3nws8gs2qcgm3rnwvjprcx55kx7l-rustc-wrapper-1.91.1/bin", "/nix/store/i1zl6150nc77vryj6n9razki0l5bzxv5-rustfmt-1.91.1/bin", "/nix/store/bqgnqxrjkhmfw1pwy4ir7yn3mdxs4sva-clippy-1.91.1/bin", "/nix/store/hxn2qrz1zmk5q01wsb7n3d58brzrsizb-pkg-config-wrapper-0.29.2/bin", "/nix/store/827b5xq1ghbbdrp1faa4fw32xj1m2p51-openssl-3.6.0-bin/bin", "/nix/store/5dslbfaryrm3lsjr0d6bdbp7f2g40dwp-mdbook-0.5.1/bin", "/nix/store/fwq388qahsyy8pngn05zvrkdfywy90sc-mdbook-excalidraw-0.1.0/bin", "/nix/store/fryiyyhhilpqhx2wc4b6l5gnxc8w18rg-git-2.52.0/bin", "/nix/store/d75200gb22v7p0703h5jrkgg8bqydk5q-coreutils-9.8/bin", "/nix/store/wd99g2j010fdkry0ws1bhxzm52w82ssx-findutils-4.10.0/bin", "/nix/store/20zvyjvxq9x2mkp7rbnvrwjjzq2n76hh-diffutils-3.12/bin", "/nix/store/k06ssckzrzn9jjvvs4n62m6567zmbx6x-gnused-4.9/bin", "/nix/store/qfmq7p42ak5yn389qvx7zpxkan5i4xiy-gnugrep-3.12/bin", "/nix/store/nmxm04dhkaqg1q6hai70n9zmzb0q49a5-gawk-5.3.2/bin", "/nix/store/k1lcfin159706lihwx5hhvl80dbij4jw-gnutar-1.35/bin", "/nix/store/0hv4z5s3r1h4lmvn0427mlxjxgvg34nr-gzip-1.14/bin", "/nix/store/p61ba9fdgx3358bpp18hv4rslf6n5bq6-bzip2-1.0.8-bin/bin", "/nix/store/mkm3my2067305hdh7rzmi10npwr7y17f-gnumake-4.4.1/bin", "/nix/store/lw117lsr8d585xs63kx5k233impyrq7q-bash-5.3p3/bin", "/nix/store/clrf4mjwr8xcfpvway6w34wzvqc1hry4-patch-2.8/bin", "/nix/store/j1zc5jh0vi9sbxj09ldj4xklgm6kpf8n-xz-5.8.1-bin/bin", "/nix/store/s2k48fw3y698j4kcvmw0520m06ihv2z4-file-5.45/bin", "/nix/store/x12lw455sq6qy2wcya85d7rb88ybc3df-bash-interactive-5.3p9/bin", "/nix/store/c2p7haf4zzkbrir9zs662r68c5dmylbq-patchelf-0.15.2/bin", "/nix/store/a245z3cvf9x9sn0xlk6k8j9xhxbhda1z-gcc-wrapper-15.2.0/bin", "/nix/store/mjf8jlq9grydcdvyw6hb063x5c34g5gf-gcc-15.2.0/bin", "/nix/store/0bdqq2z98kg2hfn3k60if6pb5fd5p10h-glibc-2.42-47-bin/bin", "/nix/store/i2vmgx46q9hd3z6rigaiman3wl3i2gc4-coreutils-9.9/bin", "/nix/store/i6ppbrlpp6yki8qvka7nyv091xa8dchx-binutils-wrapper-2.44/bin", "/nix/store/47mn80zqpygykqailwzw8zlag4cgl75q-binutils-2.44/bin", "/nix/store/4q96zm895m64s3011i20gph9anxkpk0a-nushell-0.109.1/bin", "/nix/store/i2vmgx46q9hd3z6rigaiman3wl3i2gc4-coreutils-9.9/bin", "/nix/store/16wfacfgap3chf7mcjnd8dwi85dj4qqi-findutils-4.10.0/bin", "/nix/store/3p87h6dn5i87i3iq9364imzbqgwvkg2p-diffutils-3.12/bin", "/nix/store/ryz8kcrm2bxpccllfqlb7qldsfnqp5c2-gnused-4.9/bin", "/nix/store/02vv0r262agf9j5n2y1gmbjvdf12zkl0-gnugrep-3.12/bin", "/nix/store/2xq9rayckw8zq26k274xxlikn77jn60j-gawk-5.3.2/bin", "/nix/store/qyg62bc2xnpwz0fa9prqxvvk00zj4g9q-gnutar-1.35/bin", "/nix/store/84yyzmxs7mb8nhkvcfv9n1l9irpb6mnq-gzip-1.14/bin", "/nix/store/90yw24gqmwph4xjp4mqhpx1y1gcrvqla-bzip2-1.0.8-bin/bin", "/nix/store/vbah5c4rzy1q1hbqhginyxjhj8d4dj8j-gnumake-4.4.1/bin", "/nix/store/f15k3dpilmiyv6zgpib289rnjykgr1r4-bash-5.3p9/bin", "/nix/store/wwij6563c6wbg4kzgjhng7vlhf7api19-patch-2.8/bin", "/nix/store/zys6d102zp171wpwcs08g632886w2qxs-xz-5.8.2-bin/bin", "/nix/store/nyy0bvgjwd98x7ih8pl6pr79qjljgsf7-file-5.45/bin", "/home/dmnt/.pi/agent/bin", "/nix/store/yazhl9gb5lpdgm220nm4202imc2k2cz1-fd-10.3.0/bin", "/nix/store/5ij40vzd46x6psvdv5yg5gr6igrpwyd4-ripgrep-15.1.0/bin", "/home/agent/.nix-profile/bin", "/nix/var/nix/profiles/default/bin", "/nix/var/nix/profiles/default/sbin"]
Host portal service for container capability requests

Usage: agent-portal-host [OPTIONS]

Options:
      --socket <SOCKET>  Override socket path
  -h, --help             Print help

agent-portal-cli

Command:

cargo run -q -p agent-portal --bin agent-portal-cli -- --help

Output:

Official CLI client for agent portal host service

Usage: agent-portal-cli [OPTIONS] <COMMAND>

Commands:
  ping                  
  whoami                
  clipboard-read-image  
  gh-exec               
  exec                  
  help                  Print this message or the help of the given subcommand(s)

Options:
      --socket <SOCKET>  Override socket path
  -h, --help             Print help

wl-paste wrapper

Command:

cargo run -q -p agent-wrappers --bin wl-paste -- --help

Output:

Usage: wl-paste [--list-types] [--type <mime>] [--no-newline]

Portal MessagePack type reference

Portal uses MessagePack encoding over a Unix domain socket.

This page documents request/response pairs by method.

Envelopes

Request envelope

PortalRequest {
  version: u16,
  id: u64,
  method: RequestMethod (flattened)
}
  • version: protocol version (currently 1)
  • id: client-generated request ID
  • method: serde-tagged enum (method + optional params)

Response envelope

PortalResponse {
  version: u16,
  id: u64,
  ok: bool,
  result: ResponseResult | null,
  error: PortalError | null
}
  • id is echoed from request
  • if ok = true, result is set
  • if ok = false, error is set

Error object

PortalError {
  code: string,
  message: string
}

Common code values: denied, prompt_failed, rate_limited, clipboard_failed, gh_exec_failed, too_busy.

Method pairs

ping

Request:

{ "method": "ping" }

Success response (Pong):

{
  "type": "Pong",
  "data": {
    "now_unix_ms": u128
  }
}

whoami

Request:

{ "method": "whoami" }

Success response (WhoAmI):

{
  "type": "WhoAmI",
  "data": {
    "pid": i32,
    "uid": u32,
    "gid": u32,
    "container_id": string | null
  }
}

clipboard.read_image

Request:

{
  "method": "clipboard.read_image",
  "params": {
    "reason": string | null
  }
}

Success response (ClipboardImage):

{
  "type": "ClipboardImage",
  "data": {
    "mime": string,
    "bytes": binary
  }
}

Common error codes for this method:

  • denied
  • prompt_failed
  • clipboard_failed
  • rate_limited
  • too_busy

gh.exec

Request:

{
  "method": "gh.exec",
  "params": {
    "argv": [string, ...],
    "reason": string | null,
    "require_approval": bool
  }
}

Success response (GhExec):

{
  "type": "GhExec",
  "data": {
    "exit_code": i32,
    "stdout": binary,
    "stderr": binary
  }
}

Common error codes for this method:

  • denied
  • prompt_failed
  • gh_exec_failed
  • rate_limited
  • too_busy

exec

Request:

{
  "method": "exec",
  "params": {
    "argv": [string, ...],
    "reason": string | null,
    "cwd": string | null,
    "env": { string: string, ... } | null
  }
}

Success response (Exec):

{
  "type": "Exec",
  "data": {
    "exit_code": i32,
    "stdout": binary,
    "stderr": binary
  }
}

Common error codes for this method:

  • denied
  • prompt_failed
  • exec_failed
  • rate_limited
  • too_busy

Serialization notes

  • Request enum tags: method + params
  • Response enum tags: type + data
  • PortalRequest.method is flattened into request envelope
  • Rust source of truth: common/src/portal.rs

Portal configuration reference

Portal config lives under [portal] in ~/.agent-box.toml.

Top-level keys

  • enabled (bool, default: true)
  • socket_path (string, default: /run/user/<uid>/agent-portal/portal.sock)
  • prompt_command (string|null, default: unset)
  • timeouts.request_ms (u64, default: 0 = no timeout)
  • timeouts.prompt_ms (u64, default: 0 = no timeout)
  • limits.max_inflight (usize, default: 32)
  • limits.prompt_queue (usize, default: 64)
  • limits.rate_per_minute (u32, default: 60)
  • limits.rate_burst (u32, default: 10)
  • limits.max_clipboard_bytes (usize, default: 20971520)
  • clipboard.allowed_mime (array of strings, default: image/png, image/jpeg, image/webp)

Policy defaults

[portal.policy.defaults]

  • clipboard_read_image: allow | ask | deny (default: allow)
  • gh_exec: ask_for_writes | ask_for_all | ask_for_none | deny_all (default: ask_for_writes)
    • aliases accepted by config parser:
      • allow -> ask_for_none
      • ask -> ask_for_writes
      • deny -> deny_all

Per-container policy override

[portal.policy.containers."<container-id>"]

Container ID is resolved from peer process cgroup metadata.

Example:

[portal.policy.containers."3f7a1d5c2b8e"]
clipboard_read_image = "deny"
gh_exec = "ask_for_all"

Example

[portal]
enabled = true
socket_path = "/run/user/1000/agent-portal/portal.sock"
prompt_command = "rofi -dmenu -p 'agent-portal'"

[portal.timeouts]
request_ms = 5000
prompt_ms = 15000

[portal.limits]
max_inflight = 32
prompt_queue = 64
rate_per_minute = 60
rate_burst = 10
max_clipboard_bytes = 20971520

[portal.clipboard]
allowed_mime = ["image/png", "image/jpeg", "image/webp"]

[portal.policy.defaults]
clipboard_read_image = "allow"
gh_exec = "ask_for_writes"

JSON Schema

Portal configuration is part of the overall ~/.agent-box.toml schema. The full JSON Schema can be used for validation and IDE autocompletion.

Portal wrapper contract reference

This page describes behavior expected from compatibility wrappers in wrappers/.

General contract

  • Wrappers expose familiar command names/flags.
  • Wrappers do not call host capability directly inside container.
  • Wrappers call Portal methods via Unix socket.
  • Wrappers forward output as tool-compatible stdout/stderr.
  • Wrapper process exit code matches operation result semantics.

Socket resolution order

  1. AGENT_PORTAL_SOCKET environment variable
  2. ~/.agent-box.toml -> [portal].socket_path
  3. Built-in default /run/user/<uid>/agent-portal/portal.sock

wl-paste wrapper contract

  • --list-types returns a single available image MIME type selected by portal policy.
  • --type <mime> --no-newline writes raw image bytes.
  • If requested MIME does not match available MIME, wrapper errors.

gh wrapper contract

  • Forwards argv as gh.exec request payload.
  • Includes a human-readable reason string for prompt/audit context.
  • Does not prompt in-container.
  • Prints portal-returned stdout/stderr and exits with portal-returned exit code.

Host-side execution model

  • Policy decisions and prompts are enforced by agent-portal-host.
  • Host service resolves host-native binaries (gh, wl-paste) to avoid wrapper recursion.

Versioning

Current request/response protocol version field is 1.

Do not edit by hand. Regenerate with:

nu docs/scripts/generate-cli-reference.nu

Agent-box CLI reference

ab

Command:

cargo run -q -p ab -- --help

Output:

Agent Box - Git repository management tool

Usage: ab <COMMAND>

Commands:
  info   Show repository information and list workspaces
  new    Create a new workspace (jj or git worktree)
  spawn  Spawn a new container for a workspace
  help   Print this message or the help of the given subcommand(s)

Options:
  -h, --help  Print help

ab new

Command:

cargo run -q -p ab -- new --help

Output:

Create a new workspace (jj or git worktree)

Usage: ab new [OPTIONS] [REPO_NAME]

Arguments:
  [REPO_NAME]  Repository name (defaults to current directory's git repo)

Options:
  -s, --session <SESSION>  Session/workspace name
      --git                Create a git worktree
      --jj                 Create a jj workspace
  -h, --help               Print help

ab spawn

Command:

cargo run -q -p ab -- spawn --help

Output:

Spawn a new container for a workspace

Usage: ab spawn [OPTIONS]

Options:
  -s, --session <SESSION>        Session name (mutually exclusive with --local)
  -l, --local                    Use current directory as both source and workspace (mutually exclusive with --session)
  -r, --repo <REPO>              Repository identifier (defaults to current directory's git repo)
  -e, --entrypoint <ENTRYPOINT>  Override entrypoint from config
  -c, --command <COMMAND>        Command to run in the container (passed to entrypoint)
      --git                      
      --jj                       
  -n, --new                      Create workspace if it doesn't exist (equivalent to running `ab new` first)
      --ro                       Mount source directory as read-only
  -m, --mount <MOUNT>            Additional mount (home-relative). Format: [MODE:]PATH or [MODE:]SRC:DST MODE is ro, rw, or o (default: rw). Paths use ~ for home directory. Relative host source paths are resolved against the current working directory. Example: -m ~/.config/git -m ro:~/secrets -m rw:~/data:/app/data -m ../pierre
  -M, --Mount <MOUNT>            Additional mount (absolute). Format: [MODE:]PATH or [MODE:]SRC:DST MODE is ro, rw, or o (default: rw). Same path used on host and container. Relative host source paths are resolved against the current working directory. Example: -M /nix/store -M ro:/etc/hosts -M ../shared
  -p, --profile <PROFILE>        Additional profiles to apply (can be specified multiple times). Profiles are applied after the default_profile (if set) and in order specified. Example: -p git -p rust
  -P, --port <PORT>              Port mapping to expose (can be specified multiple times). Format: [HOST_IP:]HOST_PORT:CONTAINER_PORT or just CONTAINER_PORT. Example: -P 8080:8080 -P 3000 -P 127.0.0.1:9090:9090
  -H, --add-host <HOST:IP>       Custom host-to-IP mapping added to /etc/hosts in the container (can be specified multiple times). Format: HOST:IP  (use `host-gateway` as IP to resolve to the host machine). Example: -H myhost:192.168.1.1 -H host.docker.internal:host-gateway
      --no-skip                  Don't skip mounts that are already covered by parent mounts
      --network <MODE>           Network mode to use (e.g. host, bridge, none, or a container name). Passed directly as --network=<MODE> to the container runtime
  -h, --help                     Print help

ab info

Command:

cargo run -q -p ab -- info --help

Output:

Show repository information and list workspaces

Usage: ab info

Options:
  -h, --help  Print help

Agent-box configuration reference

Main configuration file: ~/.agent-box.toml

Layered configuration

Load order:

  1. ~/.agent-box.toml (required)
  2. {git-root}/.agent-box.toml (optional)

Merge behavior:

  • Scalars: repo-local overrides global
  • Arrays: values are appended
  • Objects: merged recursively

Root keys

  • workspace_dir (path): base directory for generated workspaces
  • base_repo_dir (path): base directory for source repositories
  • default_profile (string|null): profile automatically applied to ab spawn
  • profiles (table): named profile definitions
  • runtime (table): runtime/backend settings
  • context (string): root context content
  • context_path (string, default /tmp/context): in-container path for context file
  • portal (table): portal host integration settings

All paths support ~ expansion.

[runtime]

  • backend (string, default podman): podman or docker
  • image (string): container image
  • entrypoint (shell-style string): parsed to argv
  • env (array of KEY=VALUE)
  • env_passthrough (array of variable names)
  • ports (array of -p compatible port mappings)
  • hosts (array of HOST:IP entries)
  • skip_mounts (array of glob patterns)
  • mounts (table): ro, rw, and o mount categories

Mount table shape

Each of runtime.mounts.ro, runtime.mounts.rw, and runtime.mounts.o has:

  • absolute (array of strings)
  • home_relative (array of strings)

Mount modes:

  • ro: read-only
  • rw: read-write
  • o: overlay (Podman only)

CLI additional mount syntax (ab spawn)

  • [MODE:]PATH
  • [MODE:]SRC:DST

MODE values: ro, rw, o (default: rw).

Examples:

  • -m ~/data
  • -m ro:~/.config/git
  • -m rw:~/src:/app/src
  • -M /nix/store
  • -M o:/tmp/cache

Environment passthrough

env_passthrough copies host env values into the container at spawn time.

Example:

[runtime]
env_passthrough = ["PATH", "SSH_AUTH_SOCK", "TERM"]

Context composition

Context is built in this order:

  1. Root context
  2. Each resolved profile context in profile resolution order

Values are joined with newlines and written to a temp file mounted at context_path.

Port mappings

Port values follow container runtime -p syntax:

  • HOST_PORT:CONTAINER_PORT
  • HOST_IP:HOST_PORT:CONTAINER_PORT
  • CONTAINER_PORT

Example:

[runtime]
ports = ["8080:8080", "127.0.0.1:9090:9090", "3000"]

Host entries

hosts entries are passed as runtime --add-host values.

Example:

[runtime]
hosts = ["host.docker.internal:host-gateway", "myhost:10.0.0.1"]

Network mode

CLI flag: ab spawn --network=MODE

Typical values:

  • host
  • bridge
  • none
  • runtime-specific named network

On Docker, --network=host conflicts with published ports and add-host options.

Runtime backend differences

  • Podman: supports overlay mount mode (o) and keep-id user namespace behavior
  • Docker: no overlay mounts; uses direct user mapping

Profiles

Profiles are reusable config fragments you can layer on top of runtime defaults.

Profile table: [profiles.NAME]

Supported keys:

  • extends (array of profile names)
  • mounts (same shape as runtime mounts)
  • env (array of KEY=VALUE)
  • env_passthrough (array of variable names)
  • ports (array of port mapping strings)
  • hosts (array of HOST:IP entries)
  • context (string)

Profile inheritance (extends)

A profile can inherit from one or more profiles using extends. Inherited values are merged using the same layered rules described above:

  • Scalars override
  • Arrays append
  • Objects merge recursively

Example:

[profiles.base]
env = ["RUST_BACKTRACE=1"]
mounts.rw.home_relative = ["~/.cargo"]

[profiles.gpg]
mounts.rw.absolute = ["/run/user/1000/gnupg/S.gpg-agent:~/.gnupg/S.gpg-agent"]

[profiles.dev]
extends = ["base", "gpg"]
ports = ["8080:8080"]

Activation order

Final runtime config is resolved in this order:

  1. root runtime config ([runtime])
  2. default_profile (if configured)
  3. each CLI profile flag in order (ab spawn -p one -p two)

That means later profiles can override scalar values from earlier layers, while arrays continue to append.

Typical usage

Set a baseline profile for daily use:

default_profile = "base"

Then add task-specific profiles per spawn:

ab spawn -r myrepo -s mysession -p rust -p gpg

Validation and inspection

Validate config:

ab dbg validate

Preview merged config/profile resolution:

ab dbg resolve
ab dbg resolve -p rust -p gpg

Portal integration

Portal config is defined under [portal] in the same file.

See Portal config and Portal wrapper contract.

JSON Schema

A machine-readable JSON Schema for the configuration is available for validation, IDE autocompletion, and tool integration.

Agent-box requirements

Runtime requirements

  • Rust toolchain (workspace uses Rust edition 2024)
  • Git
  • Jujutsu (jj) for JJ workspace flows
  • Docker or Podman for container execution
  • wl-paste available on host when using portal clipboard methods
  • agent-portal-host running for wrapper/API operations

Optional tooling

  • Nix / flakes for reproducible development shell workflows
  • Nushell to run CLI reference generation script:
    • nu docs/scripts/generate-cli-reference.nu

Environment variables reference

  • AGENT_PORTAL_SOCKET

    • Used by portal clients/wrappers to select socket path.
    • Resolution priority is env var first, then config/default.
  • AGENT_PORTAL_HOST_WL_PASTE

    • Used by agent-portal-host to override host wl-paste binary path.
  • AGENT_PORTAL_HOST_GH

    • Used by agent-portal-host to override host gh binary path.

Logging

  • RUST_LOG
    • Controls tracing filter for agent-portal-host and other Rust binaries using tracing subscriber.

Runtime passthrough

Variables listed in [runtime].env_passthrough are copied from host into container at spawn time.

Glossary

  • Agent-box: CLI (ab) that manages repository workspaces and container sessions.
  • Portal: host daemon + API/CLI for mediated host capability access.
  • Wrapper: compatibility binary that presents familiar command behavior while forwarding to Portal.
  • Session: named workspace instance used by Agent-box (ab new/ab spawn).
  • Workspace type: JJ workspace or Git worktree.
  • Policy decision: allow/ask/deny behavior for portal methods.
  • Policy mode (gh_exec): ask_for_writes, ask_for_all, ask_for_none, deny_all.
  • Prompt command: configurable dmenu-style command invoked by portal host for ask-mode approvals.
  • Container override policy: per-container policy table keyed by container ID.