๐ŸŽ Corral

Corral โ€” feature catalog

One line per command, config key, mode and guest control. Generated from the code by corral docs (make docs rewrites this file; a test fails when it is stale). corral docs --json prints the same catalog as JSON. Narrative and examples: README ยท threat model: SECURITY.md ยท design: ARCHITECTURE.md ยท measurements: FEASIBILITY.md.

What it is: each project gets its own Linux VM (Lima + Apple Virtualization) in which an AI coding agent runs with no permission prompts; the project is mounted at its real path, the Mac's home, keys and other repositories are not there. A repository's own config can shape the guest but never widen what the box reaches.

Commands

Command Group Does
corral agents Insight & setup List supported agents and manage their persistent state
corral agents import <agent> Copy host settings/skills (never credentials) into the agent's box state
corral agents logout <agent> Remove the stored login for an agent from ~/.corral/agents
corral audit Insight & setup Show the session audit log (who launched what, where, with which env)
corral claude [-- agent args...] Agents Launch Claude Code (Anthropic) โ€” installed from the official installer
corral code Agents Open the project inside the box in VS Code (or Cursor / JetBrains) over SSH
corral config Insight & setup Show the resolved configuration for the current project
corral config init Write a commented .corral.toml into the project (--host: your per-project file instead)
corral config path Print the global config file path
corral delete [box] Box lifecycle Delete a box (VM disk); the project and agent login are kept
corral docs Insight & setup Print the feature catalog (every command, config key, mode, control) โ€” for humans and AI assistants
corral doctor [box] Insight & setup Check the host; with a box name, preflight what the project declared from inside the box
corral egress [box] Insight & setup Show a box's network mode, allowed destinations, what it reached and what was refused
corral gc Box lifecycle Stop boxes idle longer than their idle_stop (the sweep also runs on launch and in the dashboard; list never stops anything) and remove egress logs of boxes deleted more than 14 days ago
corral golden Box lifecycle Golden images: provisioned once per toolchain set, cloned per project
corral golden build Build (or verify) the golden image for this project's configuration
corral golden prune Delete golden images no existing box was cloned from (also runs after upgrade and golden build)
corral info [box] Insight & setup Show details of a box: mounts, resources, agents, forwarded env
corral list Insight & setup List boxes and their state (read-only; --json adds live guest metrics for running boxes)
corral logs [box] Insight & setup Show the box's boot/provisioning log (Lima hostagent + serial console)
corral rebuild [box] Box lifecycle Delete and re-create a box with the current configuration
corral restart [box] Box lifecycle Stop and start a box
corral run <command> [args...] Agents Run a single command inside the project's box
corral setup Insight & setup Interactive first-time setup: prerequisites, defaults, login
corral shell Agents Open an interactive shell inside the project's box
corral snapshot Box lifecycle Snapshot a box's disk so you can roll back after an agent session
corral snapshot create [box] <tag> Create a snapshot
corral snapshot delete [box] <tag> Delete a snapshot
corral snapshot list [box] List snapshots
corral snapshot restore [box] <tag> Roll the box disk back to a snapshot
corral start [box] Box lifecycle Start a stopped box without launching anything
corral stop [box] Box lifecycle Stop a box (default: the current project's box)
corral undo [box] Box lifecycle Roll the box disk back to the newest automatic snapshot (or --tag)
corral uninstall Insight & setup Delete every box and Corral state (agent logins included)
corral up Box lifecycle Create/start the project's box without launching anything
corral upgrade Insight & setup Upgrade corral (Homebrew tap or source checkout) and Lima
corral version Print version information

Every command takes -C <dir> (project) and --box <name>. Layers: defaults < ~/.corral/config.toml < <project>/.corral.toml (restricted) < ~/.corral/projects/<box>.toml (trusted) < flags.

Configuration keys

Trust: project-ok โ€” a repository may set it (guest-only effect) ยท project-may-tighten โ€” a repository may only make it stricter ยท trusted-only โ€” refused in the repository file (it would widen what the box reaches on the Mac).

Key Type Default Trust Meaning
default_agent string claude project-ok Agent started by the bare dashboard enter and used for login hints.
cpus int 4 project-ok vCPUs per box; a project may set it, capped against the host.
memory string 4GiB project-ok RAM ceiling per box (e.g. 4GiB); the guest never returns memory to the Mac, so this is the eventual cost. Capped against the host.
disk string 60GiB project-ok Sparse VM disk size; only used blocks cost anything.
yolo bool true project-may-tighten Skip the agent's own permission prompts inside the box (the VM is the boundary). --ask keeps them for one run.
stop_on_exit bool false project-ok Stop the box when the session ends.
readonly_project bool false project-may-tighten Mount the project read-only in the box.
shared_agent_state bool true project-may-tighten Deprecated alias of agent_state: true = shared, false = isolated.
agent_state string project-may-tighten Where the agent login lives: shared (host dir mounted rw), seeded (copied in once), isolated (log in per box).
git_identity bool true project-may-tighten Forward git user.name / user.email (never keys) into the box.
ssh_agent bool false trusted-only Forward the SSH agent socket into the box.
no_env_passthrough bool false project-may-tighten Disable automatic forwarding of the agent's credential variables (forward_env).
protect_git_metadata bool true project-may-tighten Shadow .git/config and .git/hooks in the box so in-box edits cannot reach what the Mac executes.
name string trusted-only Override the box name (default: <dir-slug>-<hash>).
network string full project-may-tighten full (internet), broker (only egress hosts through the allow-list proxy on the Mac, DNS closed, sudo removed), offline (nothing but the Mac, sudo removed).
rosetta bool false project-ok Run amd64 binaries and --platform linux/amd64 containers inside the arm64 box (Apple Silicon).
idle_stop string 30m project-ok Stop a box with no live session after this long (30m, off).
golden bool true project-ok Build the box by cloning the shared golden image (toolchains + agents) instead of from scratch.
source string mount project-may-tighten mount (checkout mounted live at its real path) or clone (nothing mounted; the box clones the repo at session start).
snapshot string off project-ok auto takes an APFS clone of the box disk at each session start from stopped; corral undo restores it.
snapshots_keep int 3 project-ok How many auto snapshots to keep.
profile string default project-may-tighten Named security floor: default, offline, strict (broker + isolated login + no ssh agent + no passthrough + git shadow). Keys may tighten beyond it, never loosen.
forward_env []string [ANTHROPIC_API_KEY CLAUDE_CODE_OAUTH_TOKEN] trusted-only Host variables forwarded by name when set (over SSH SendEnv, never argv).
env []string project-may-tighten KEY=value literals set in the box; a bare KEY forwards the host value (trusted layers only).
env_from_host []string trusted-only GUEST_VAR=HOST_VAR aliases; the session refuses to start if the host variable is unset.
keychain_env []string trusted-only Variables whose value is read from the macOS Keychain (service = name) at launch when not exported; a missing item refuses to start.
env_file string trusted-only KEY=value file consulted after the exported environment and before the Keychain โ€” the credential path for an unattended host (launchd). Must be 0600, yours, under your home; refused otherwise.
max_running int trusted-only Admission: refuse to start another box when this many are running (exit 75, requeue); 0 = no limit.
memory_reserve string 8GiB trusted-only Admission: RAM that must stay free for macOS โ€” a box is refused (exit 75) when the running boxes' measured footprint plus its memory would exceed host RAM minus this.
timeout string trusted-only Default for run --timeout: end the session after this long (SIGTERM, then SIGKILL after 10 s), exit 124.
packages []string project-ok Extra Ubuntu apt packages installed in the box.
toolchains []string [node] project-ok Preinstalled toolchains (see Toolchains below); they define the golden image. Unioned across layers; an explicit [] drops the default node.
toolchain_versions map[string]string project-ok { flutter = "3.44.2" } or "3.44.2@<commit>": pin a toolchain's release (flutter today); part of the golden image's identity, verified against the commit when given.
mounts []string trusted-only Extra host directories mounted into the box (host:guest[:ro]); home and credential directories are refused.
provision []string project-ok Repository scripts run at the end of provisioning as the box user (# corral: system for root in full mode); a failure fails the start.
hide []string project-ok Project paths shown empty inside the box (.env, secrets/).
box_dirs []string project-ok Project directories kept on the box disk and bind-mounted over the mount (node_modules, build): fast installs, empty on the Mac.
egress []string [api.anthropic.com *.anthropic.com platform.claude.com] trusted-only Allow-list for network = "broker": exact hosts or *.suffix, :port to widen beyond 80/443; git_tokens hosts are added automatically.
git_tokens map[string]config.GitToken trusted-only { "<host>" = "<HOST_VAR>" } or { "<host>" = { token = "<HOST_VAR>", user = "gitlab+deploy-token-<id>" } }: HTTPS git credential for the box, offered only to that host; the variable may come from keychain_env.
api_brokers []config.APIBroker trusted-only [[api_brokers]] name, upstream, token, auth (header|bearer|basic), header/user, allow = ["METHOD /path/**"]: a credential-holding proxy on the Mac โ€” the box calls $CORRAL_API_<NAME>/โ€ฆ, the Mac adds the token and forwards only allow-listed method+path calls; every call audited.

Modes

`network = full broker
`source = mount clone`
`agent_state = shared seeded
`profile = default offline
`snapshot = off auto`
`golden = true false`
api_brokers Credential-holding API proxy on the Mac for GitLab/Jira-style REST calls: scoped by method+path, token never in the box, works in full and broker network modes (not offline).

Guest controls

What enforces the configuration inside the box (systemd units, re-applied before every session) and on the Mac.

corral-broker nftables funnel: only the broker port on the Mac gateway; DNS closed; sudo removed. Re-applied before every session; a session refuses to start if it is not active.
corral-offline nftables reject-all except the Mac; sudo removed. Same session gate.
corral-git-shadow Guest-local .git/config, empty .git/hooks over the mount (protect_git_metadata).
corral-hide Empty box-owned file/tmpfs over each hide path.
corral-boxdirs Box-disk directory bind-mounted over each box_dirs path.
provision failure record A repository provision script that exits non-zero is recorded in /corral/runtime/provision/; corral refuses to start after create/start.
egress broker (host) Per-box CONNECT/forward proxy on 127.0.0.1:<port>, allow-list decided on the Mac; every attempted destination โ€” allowed or denied โ€” and every api_brokers call is recorded by name in the box's egress log (corral egress).
audit log ~/.corral/logs/sessions.jsonl: launches, variable names, denials, snapshots, deletes (corral audit).
egress log ~/.corral/logs/egress-<box>.jsonl: one line per connection the box attempted through its broker (host:port, allowed/denied), per api_brokers call (method, path, status) and per session start/end; kept 14 days after the box is deleted, rotated at 16 MiB (corral egress --log).

Toolchains

toolchains = [...]; every download is pinned and checksum-verified, no curl | bash except the agent vendor's own installer.

android Android SDK cmdline-tools (SHA-256 pinned), platform-tools, android-35, build-tools 35.0.0, JDK; amd64 multiarch so aapt2 runs under Rosetta.
docker Docker Engine + compose + buildx inside the box; the box user is in the docker group โ€” except under network = "broker"/"offline", where that group is root-equivalent and is removed.
flutter Flutter stable (3.47.1 by default; toolchain_versions pins another release), release commit pinned and verified, Dart + Android artifacts precached.
go Latest stable Go from go.dev (SHA-256 verified).
java OpenJDK 17, JAVA_HOME.
node Node.js 22 LTS from nodejs.org (SHA-256 verified), npm min-release-age = 7.
python Python 3, pip, venv, pipx (Ubuntu apt).

Environment inside the box

CORRAL=1 Set in every shell inside a box (also /etc/corral-release).
CORRAL_NAME / CORRAL_PROJECT / CORRAL_VERSION / CORRAL_AGENT Session identity variables.
CORRAL_NETWORK / CORRAL_SOURCE The box's network and source mode.
CORRAL_YOLO 1 when the agent wrapper adds its skip-prompts flags; 0 with --ask.
CORRAL_USER / CORRAL_HOME In provision scripts: the box user (carries the Mac uid) and their home.
HTTP_PROXY / HTTPS_PROXY / NO_PROXY In broker mode: the allow-list proxy on the Mac gateway.
CORRAL_API_<NAME> Base URL of an api_brokers route (http://192.168.5.2:<port>/<name>); the credential is added on the Mac, never present in the box.
CLAUDE_CONFIG_DIR Claude Code state relocated to /corral/agents/claude; DISABLE_AUTOUPDATER=1.

Host layout

~/.corral/config.toml Global config (corral setup).
<project>/.corral.toml Repository config โ€” can only shape the guest (see trust column).
~/.corral/projects/<box>.toml Your per-project trusted layer (egress, tokens, mounts).
~/.corral/lima/<box>/ Lima instance: disks, per-box SSH key, ssh.config (LIMA_HOME).
~/.corral/boxes/<box>.json Metadata, rendered template and its hash (drift detection).
~/.corral/agents/<agent>/ Shared agent login/state.
~/.corral/snapshots/<box>/ APFS-clone snapshots.
~/.corral/ssh/config One Include per box for corral code / ssh lima-<box>.
~/.corral/logs/ sessions.jsonl (audit), egress-<box>.jsonl (per-box egress log), broker-<box>.log (broker child output).

Exit codes and outcomes

corral run for queues and scripts: the agent's status passes through; Corral's own outcomes use sysexits(3) codes agents do not.

0โ€“255 (agent's) The command/agent finished; this is its own exit status (outcome = "exit").
78 EX_CONFIG โ€” --preflight refused the session: a declared grant does not work from inside the box. Fix the configuration; do not retry (outcome = "preflight-refused").
75 EX_TEMPFAIL โ€” admission refused: max_running reached or the memory budget (memory_reserve) would be exceeded. Requeue (outcome = "admission-refused").
69 EX_UNAVAILABLE โ€” the box became unreachable during the session (SSH lost, instance gone, guest OOM-kill when detectable). Requeue and alert (outcome = "unreachable").
124 --timeout elapsed; the session was terminated (outcome = "timeout").
1 Any other Corral error (configuration refused by policy, Lima failure); the message names the next command.
run --result <file> Writes one JSON object per run โ€” box, agent, outcome, exit_code, reason, started, ended, duration, forwarded_env โ€” the same record the audit log gets.