Part of the AgentBox docs. Start at CLAUDE.md. For cloud-specific state (per-org credential volumes, snapshot manifests under
~/.agentbox/cloud-checkpoints/, sshfs mount root, Daytona credentials) seecloud-providers.md.
~/.agentbox/config.yaml— global user config (layered defaults). Same shape as the per-project file and thedefaults:block inagentbox.yaml. Owned by@agentbox/config(read by everyapps/clicommand at startup). Manage viaagentbox config get|set|unset|list|edit|path|list-projects—set/unset/editonly target--globaland--project(default), the workspacedefaults:block is hand-edited. Precedence (highest wins): CLI flag > workspacedefaults:> per-project (~/.agentbox/projects/<sha1-16-of-abs-path>/config.yaml) > global > built-in. Engine override (engine.kind) is the only key applied at CLI startup viasetEngineOverride()inhost-export.ts; everything else flows throughloadEffectiveConfig()per command. The full key set + types live inKEY_REGISTRY(packages/config/src/types.ts) — single source of truth for the parser, the JSON schema, andconfig setcoercion. The ctl parser accepts top-leveldefaults:as a permissive passthrough (no dep on@agentbox/config); the host re-validates strictly when loading.~/.agentbox/state.json— registry of created boxes. Mutated only throughrecordBox/removeBoxRecord/mutateState(@agentbox/sandbox-core), which take a cross-process lock (state.json.lock, with stale-break) around the read-modify-write and write atomically (temp file +rename). This is load-bearing for parallelagentbox create/destroy: without it, concurrent processes lost each other's records (last-writer-wins) and could leave a half-written file, which in turn wedged the queue's running-count and made boxes vanish fromagentbox list.recordBoxalso de-duplicates a clashing per-projectprojectIndexunder the lock (two concurrent creates can allocate the same index before either records). Box dir segments are<id>-<n>-<mnemonic>, id-prefixed, so on-disk paths stay unique regardless ofn.BoxRecord.lastAgent(claude/codex/opencode) records which agent the box was last launched with — written on everyagentbox claude/codex/opencodelaunch (foreground + queued, viarecordLastAgent). It's durable, unlike the in-box session pointers which are cleared when an agent stops, soagentbox recoverreads it to relaunch (or attach) the right agent — and it's the only such signal for an adopted box whose state was rebuilt from a live sandbox (recover --provider <cloud> --adopt).BoxRecord.displayNameis an optional cosmetic label set byagentbox status <box> --set-name(viasetBoxDisplayName, a locked read-modify-write; empty/blank clears it). Unlikenameit does not drive the container / git branch / Portless URL — those stay on the originalname— so a rename is a pure, provider-agnostic state edit. Display surfaces (list, the hub, the tray) preferdisplayName ?? name, andfindBoxresolves a box by itsdisplayNametoo (lowest-precedence exact match, after id/name, so a label can't shadow another box's id/name).~/.agentbox/auth.json(mode 0600) — long-lived Claude OAuth token captured on firstagentbox claudeviaclaude setup-token. Forwarded to every box asCLAUDE_CODE_OAUTH_TOKEN. Host env vars (ANTHROPIC_API_KEY,CLAUDE_CODE_OAUTH_TOKEN) override it.CLAUDE_EFFORTandANTHROPIC_MODELare also forwarded when set on the host — Claude Code stores the user's model selection (Opus/Sonnet/Haiku via/modelor--effort) only in the parent claude's process env, not in~/.claude.jsonor~/.claude/settings.json. Both create-time (docker run -e) andagentbox claude startexec-time (docker exec -e) re-read these from the current host shell, so launching from inside a host claude session propagates that session's model to the box (packages/sandbox-docker/src/claude.ts→FORWARDED_ENV_KEYS).~/.agentbox/snapshots/<id>/— frozen APFS clones of host workspaces (the--host-snapshotpath; config keybox.hostSnapshot, renamed frombox.snapshot— the old key now errors with a migration hint viaRENAMED_KEYSinpackages/config/src/parse.ts)~/.agentbox/checkpoints/<sha1-16-of-project-root>/<box-name>-<n>/manifest.json— per-project warm-state checkpoint metadata only (schema: 2,type,image,parents, lineage). The captured filesystem itself is a local Docker image tag namedagentbox-ckpt-<sha1-16-of-project-root>:<name>(CHECKPOINT_IMAGE_PREFIX+checkpointImageTag()inpackages/sandbox-docker/src/checkpoint.ts). Capture isdocker exec /usr/local/bin/agentbox-checkpoint-cleanup(apt + tmp + logs + bash history) →docker commit <ctr> <tag>forlayered; forflattened(--merged, or auto whenchainDepth >= checkpoint.maxLayers, default 3) the intermediate commit is thendocker create/docker export-ed to a rootfs tarball and rebuilt via a tinyFROM scratchDockerfile thatADDs the tarball and replays the base image's Env/Cmd/Entrypoint/WorkingDir/User/ExposedPorts (everythingdocker exportdiscards). A box created from a checkpoint just uses that image as its base —runBox(image=<ckpt-tag>)— so/workspaceis already populated and there's noseedWorkspacestep.BoxRecord.checkpointImagemirrorsrecord.imagefor plain-vs-checkpoint disambiguation;checkpointSource = { ref, type, chain }carries lineage for the auto-flatten rule. Tags are durable project assets: they survive boxdestroyand are allowlisted inprune --allby theagentbox-ckpt-prefix (parallel toSHARED_CLAUDE_VOLUME);agentbox checkpoint rmremoves the manifest anddocker image rms the tag. Project default ref is the per-project config keybox.defaultCheckpoint;agentbox create/claude --snapshot <ref>overrides it. Capture/restore is host-side; the in-box agent triggers it through the relay (agentbox-ctl checkpoint→/rpc checkpoint.create→ relay spawns the hostagentbox checkpoint createCLI viaAGENTBOX_CLI_ENTRY).~/.agentbox/boxes/<id>/run/ctl.sock— host-side view of the in-box ctl socket (bind-mounted to/run/agentbox/in the container)~/.agentbox/boxes/<id>/status.json— durable snapshot the in-box supervisor pushes through the relay (services, tasks, listening ports, the web-serviceexpose:, claude activity). Prefer reading this viareadBoxStatus()(host-export.ts) as the host-side source of box runtime facts — it works when the box is paused/stopped and avoids adocker execround-trip. Reach fordocker exec agentbox-ctl …live reads only when you need fresher-than-snapshot data and the container is running (e.g.agentbox status's liveTASKS/SERVICEStables).getBoxEndpointsfollows this rule: snapshot first, hostagentbox.yamlonly as fallback. Additive schema — old snapshots lack newer fields (treated as absent);schemastays1. The snapshot'sclaudeblock (activitystate+ tmuxsessionTitle) is joined by optionalcodex/opencodeblocks: the ctl daemon'sStatusReporterprobes all three agent tmux sessions each snapshot (probeAgentSessioninpackages/ctl/src/tmux.ts→tmux #{pane_title}), so a codex/opencode box surfaces its session title inlist/status --inspect.codexalso has an activitystatefed byagentbox-ctl codex-state— wired end-to-end, but codex's own~/.codex/hooks.jsonlifecycle hooks don't fire in codex-cli 0.133.0 (verified:features.hooksisstable/on,--dangerously-bypass-hook-trust, both JSON shapes, exec + TUI — none execute the hook), socodex.statestaysunknownin practice andagentSummaryfalls back to a plaincodex; the pipeline lights up automatically if a future codex build runs the seeded hooks. OpenCode has no activity hooks (its plugin-based integration was deferred).~/.agentbox/boxes/<id>/workspace— per-box host export target foragentbox open(andagentbox open --path). Empty until refresh runs — by design.createBoxmkdirs it at create-time and bind-mounts it into the container at/host-export(CONTAINER_EXPORT_MERGED);refreshExportinhost-export.tsdoesdocker exec rsync /workspace/ /host-export/(with--exclude=node_modulesby default). The container/workspacelives in the container's writable layer and is invisible to macOS directly, which is why a copy is needed.agentbox open --path(canonical spelling;--printis a kept alias) prints the host path and still refreshes (use--no-refreshto skip), so scripts that pipe the path get a fresh snapshot in one call. The oldagentbox pathcommand was folded intoagentbox open --path(logic inpath.ts'srunPath, called byopen.ts);agentbox pathno longer exists. The--upperflag retired with the FUSE overlay — the merged export is the only view now.agentbox url(web app URL —http://<container>.orb.localon OrbStack,http://127.0.0.1:<webHostPort>otherwise;--loopbackforces loopback) andagentbox screen(noVNC viewer) are separate top-level commands.agentbox urlworks even when no service declaresexpose:— only the create-time web-port reservation (BoxRecord.webContainerPort) is required; pre-feature boxes error with a recreate hint. Boxes created before the bind-mount existed (/host-exportabsent) fall back to adocker exec tar | tar -xpipe into the host dir — slower but doesn't need a container restart.- Per-box git worktrees live inside the container at
/workspace(root repo) and/workspace/<sub>(1st-level nested repos in a monorepo). There is no host-side worktree dir under~/.agentbox/boxes/<id>/worktrees/anymore — the worktree's working tree is the container's writable layer.agentbox createrunsgit worktree add -b agentbox/<box-name>[--<sub>] <containerPath> HEADinside the container viaseedWorkspace(packages/sandbox-docker/src/in-box-git.ts), against the bind-mounted host.git/(RW at its identical absolute host path).extensions.worktreeConfig=trueis set on the main repo socommit.gpgsign=false(per-worktree) can disable signing for the box's commits — boxes carry no SSH keys. The host's tracked-uncommitted + untracked state is carried over at create via host-sidegit stash create(commit lands in the shared.git) + in-containergit stash apply --index+ a host→container tar pipe for untracked files; the user's main checkout is never modified. The host's.git/worktrees/<branch>/gitdirpoints to the container-only/workspacepath — cosmetically visible ingit worktree liston the host, otherwise inert. Ondestroy,git -C <hostMainRepo> worktree remove --force <containerPath>(best-effort, falls back toworktree prune) deregisters from the main repo so the host's worktree list doesn't accumulate orphans. ~/.agentbox/relay.pidand~/.agentbox/relay.log— pidfile + log for the host relay node process spawned byensureRelay(). Seehost-relay.md.- Per-box
agentbox-docker-<id>(or sharedagentbox-docker-cachewhenbox.dockerCacheSharedis set) — the in-box dockerd's/var/lib/dockerdata root. Per-box variant removed ondestroy; shared variant preserved (allowlisted inprune --all). Holds images, layers, inner containers, networks, and volumes pulled/created by the agent inside the box. - Docker objects: containers
agentbox-<id|name>(per-box;/workspacelives in the container's writable layer, not a volume) + the Claude Code config volume (agentbox-claude-configshared by default, oragentbox-claude-config-<id>when--isolate-claude-configis set)./workspace/node_modules(and.next,target,.venv, …) lives in the container's writable layer alongside the rest of the working tree; isolated per box, captured byagentbox checkpoint create(docker commit), wiped ondestroy. Host darwinnode_modulesis never copied into the box (the seedWorkspace tar pipe in the no-git case + the worktree-add carry-over in the git case both originate from git-tracked content); the wizard-generatedagentbox.yamlinstall task force-rebuilds Linux-native deps on first box start (marker-guarded). The relay is no longer a docker container —prune --allreaps any leftoveragentbox-relaycontainer +agentbox/relay:devimage +agentbox-netnetwork from previous versions. Host's~/.claudeis the authoritative source: everycreate/claudersyncs host -> volume (additive — host wins on overlap, box-only files like session logs are preserved). Host's~/.claude.json(file, not directory) also syncs into the volume as_claude.json; an image-baked symlink at/home/vscode/.claude.json -> /home/vscode/.claude/_claude.jsonroutes claude's reads/writes through the volume. Hook commands referencing host-absolute paths under$HOME/are filtered out during sync (packages/sandbox-docker/src/claude-hooks-filter.ts) so the in-box claude doesn't spamcc-status: not founderrors. The same module'sclearInstallMethodstrips the top-levelinstallMethodfield from the synced_claude.jsonso the in-box claude (installed via Anthropic's native installer at/home/vscode/.local/bin/claude) redetects rather than trip an integrity warning when the host recorded a different install method. The rsync also runs with--copy-unsafe-linksso user-skill symlinks (e.g.~/.claude/skills/<x> -> ../../.agents/skills/<x>) are dereferenced into real directories inside the volume —~/.agentsis not bind-mounted. The rsync excludesnode_modulesso the host's darwin-arm64 plugin binaries (fsevents.node,@esbuild/darwin-arm64,@rollup/rollup-darwin-arm64, sharp, …) never reach the linux box;rebuildPluginNativeDeps()then re-runsnpm installfor each plugin cache the first timeagentbox claudelaunches in a fresh box (idempotent — gated by a per-plugin.agentbox-installedmarker, since some plugins have empty dep lists that don't produce anode_modules/dir). Installs run in parallel (bounded, MAX=4) and share a persistent npm cache at/home/vscode/.claude/.agentbox-npm-cache(in the claude-config volume, so apackage@versionis fetched once globally and reused by every later box/plugin-version) with--prefer-offline. A failed install writes a.agentbox-install-failedmarker (mtime = failure time); that plugin is skipped rather than retried on every launch until the marker ages past 6h (PLUGIN_INSTALL_BACKOFF_MS), then it retries. The host pre-scanscanPluginCacheForRebuild()mirrors the same accept/skip rules (incl. the fail-backoff window) so it never disagrees with the in-box script. A one-shot migration in the rsync helper wipes pre-existingnode_modules/from the shared volume the first time a post-upgrade sync runs (sentinel~/.claude/.agentbox-cleaned-nm-v1); without it, existing users would keep darwin binaries indefinitely. Every top-level JSON under~/.claude/plugins/(currentlyinstalled_plugins.json+known_marketplaces.json) has its host-home prefix rewritten to/home/vscodevia an inlinesedsweep in the helper container — without rewritingknown_marketplaces.json.installLocation, claude can't load the marketplaces (it falls back to a<org>-<repo>slug derived fromsource.repolikemicrosoft-playwright-cli, which doesn't exist on disk, masquerading as "Plugin X not found in marketplace Y" for every plugin in the marketplace).claude-hooks-filter.ts'saddProjectAliasduplicates_claude.json.projects[<host-cwd>]toprojects['/workspace']so project-scoped MCP servers / trust / history apply inside the box (workspace is always/workspaceregardless of host path). The shared volume is never auto-removed bydestroyorprune(it holds user identity); per-box isolated volumes are removed with their box. - The Codex CLI config volume mirrors the claude one:
agentbox-codex-configshared by default,agentbox-codex-config-<id>under--isolate-codex-config(config keybox.isolateCodexConfig), mounted at/home/vscode/.codex, recorded asBoxRecord.codexConfigVolume.createBoxmounts + host→volume rsync-syncs it (additive, host-authoritative) when either the caller isagentbox codexor the host has a~/.codex(so a plainagentbox createfor a Codex user still gets a working box). The rsync excludessessions//log//history.jsonl(rollout transcripts) andhooks.json— the latter is box-owned:seedCodexHookscopies the image-bakedagentbox-codex-hooks.jsonto~/.codex/hooks.jsonfor activity reporting (see the status.json note above re: codex 0.133.0 not firing them). It also excludes Codex's session-state DBs / indexes (state_*.sqlite*,logs_*.sqlite*,session_index.jsonl,external_agent_session_imports.json,shell_snapshots/) andrm -rfs any that a pre-fix sync already left in the shared volume.state_*.sqliteis thethreadsINDEX over the rollout files — Codex reads the resume cwd fromthreads.cwd, so seeding the host copy made a teleported (agentbox fork --agent codex) session resume at its host cwd and pop Codex's "Choose working directory" prompt, overriding the cwd we rewrite in the rollout. The index is a derived cache Codex backfills from the box's rollouts (thebackfill_statetable), so the box rebuilds it from the one teleported rollout (already rewritten to/workspace). Excluding it also stops the host's entire cross-project Codex history (all indexedthreads) from leaking into every box.config.tomlis not rsynced at all (anchored--exclude=/config.toml) — the box copy is reconciled instead (reconcileVolumeCodexConfig): the host config is sanitized (sanitizeCodexConfigForBox—mcp_servers/ top-levelnotify/ local-sourcemarketplaces(and their dependentplugins) whose paths point at host-only absolute locations (the host home,/Applications,/opt/homebrew, …) are stripped, so in-box codex doesn't try to exec macOS-only paths like the desktop Codex.app'snode_replMCP server, which otherwise printsMCP client \node_repl` failed to starton every launch), then the box's existing config is **merged in additively** (mergeCodexConfigForBox): box-only[marketplaces.]/[plugins.]/[mcp_servers.]/[projects.]entries and box-only top-level keys survive, so in-boxcodex plugin add/codex mcp addstate isn't wiped by the nextagentbox codexstart — but the host wins on every overlapping key (host-authoritative: an in-box enable flip of a host-listed plugin reverts, and a marketplace deleted on the host lives on in the box until removed there or the volume is isolated/recreated). The sanitizer also **pre-trusts/workspace** (injects[projects."/workspace"] trust_level = "trusted") so codex doesn't pop the "trust this folder?" prompt on attach — the host config only trusts host-absolute project paths, never/workspace(this is the stable path-based trust, distinct from the brittle hash-based hook trust). For a Codex user with **no** hostconfig.toml, the minimal/workspace-trusted config (MINIMAL_TRUSTED_CODEX_CONFIG) is merged into whatever the box has. Best-effort otherwise (codex-config.ts; the cloud path sanitizes the staged tarball inhost-stage.ts— no merge there, cloud boxes seed a fresh FS at prepare). **Codex plugins ride this sync**: the git-marketplace snapshots (.tmp/marketplaces/) are carved back into the rsync/staging via--includerules ahead of the.tmpexclude (without them, in-boxcodex plugin list/addfails with "marketplace root does not contain a supported manifest", and the box can't re-clone — the git shim blocksclone), while the rest of.tmp(the ~200 MB desktop-appbundled-marketplaces+pluginspayloads) stays excluded; and marketplace dirs underplugins/cache/+.tmp/marketplaces/that the **merged** config no longer references are purged (buildCodexOrphanPurgeScripton docker,purgeOrphanCodexMarketplaceDirson the cloud staging path). The codex sync **excludesskills/**:/.codex/skills//.agents/skills/are symlinks into, which the box mounts from its own volume (see the agents-config bullet below), so codex reads/.agents/skills/.codexdirectly and the per-codex copies are redundant; excluding them also avoids anrsync"could not make way for new symlink" failure when the shared volume already holds them as real dirs from an earlier deref (a trailingfindpurges those stale dirs, keeping codex's runtime-managed.system). The cloud path's equivalentCODEX_RSYNC_EXCLUDES(host-stage.ts) globs the same DBs (version-proof acrossstate_5→state_6schema bumps).used to be a RW host bind-mount (buildIdentityMounts); that was replaced by the volume so boxes can't corrupt host state. Codex plugins need no native-deps rebuild (marketplace payloads are JS/markdown; the platform-specificplugins/.plugin-appserverruntime is excluded). The shared codex volume is allowlisted indestroy/prune --allexactly like the shared claude volume; per-box isolated codex volumes are removed with their box.packages/sandbox-docker/src/codex.tsownsensureCodexVolume/buildCodexMounts/startCodexSession/pullCodexConfig`. - The Agent Skills volume
agentbox-agents-config(always shared — skills only, no auth, so no per-box isolation) is mounted at/home/vscode/.agents, recorded asBoxRecord.agentsConfigVolume. It carries the host's~/.agents— the cross-agent "Agent Skills" dir (~/.agents/skills/<name>, managed by the open skills installer +.skill-lock.json). Codex discovers skills from~/.agents/skillsdirectly (not just~/.codex/skills, which only symlinks a subset back into it), so without this volume an in-box codex saw only its 5 runtime.systemskills, never the user's full set (and theagentboxfork skill).createBoxmounts + host→volume rsync-syncs it (additive, host-authoritative,rsync -a --copy-unsafe-linksto materialize each skill's symlinks, broken/out-of-tree ones--excluded viafindUnsyncableSymlinks) whenever the host has a~/.agents. Allowlisted indestroy/prune --alllike the other shared agent volumes. Cloud bakes it into the prepare snapshot viastageAgentsStaticForUpload(host-stage.ts,rsync -a -Lderef → tarball → extracted at/home/vscode/.agents), wired into bothsandbox-daytona/sandbox-hetznerprepare.packages/sandbox-docker/src/agents.tsownsensureAgentsVolume/buildAgentsMounts/resolveAgentsVolume. - The OpenCode config volume (
agentbox-opencode-config,box.isolateOpencodeConfig,BoxRecord.opencodeConfigVolume) follows the codex pattern with one twist: OpenCode splits its state across three dirs —~/.config/opencode(config),~/.local/share/opencode(data + theauth.jsonprovider credentials), and~/.local/state/opencode(state — incl.model.json, the last-selected model). A single volume holds all three: it is mounted at the data dir/home/vscode/.local/share/opencode; the config dir is relocated into that volume'sconfig/subdir via the box-global envOPENCODE_CONFIG_DIR, and the state dir into.state/opencodevia the box-global envXDG_STATE_HOME(both OpenCode-derivable paths, so safe box-global — unlikeXDG_DATA_HOME; OpenCode has no dedicatedOPENCODE_STATE_DIR).ensureOpencodeVolumersyncs host~/.local/share/opencode→ volume root (excl.storage/log/project/cache/bin), host~/.config/opencode→ volumeconfig/, and host~/.local/state/opencode→ volume.state/opencode(excl.locks, newest-wins--updatesince the selected model is two-way state, not host-authoritative config).createBoxmounts it when the caller isagentbox opencodeor the host has any OpenCode dir.agentbox opencode loginruns the interactiveopencode auth login(provider picker) in a throwaway container. On the cloud providers there is no persistent per-box data store, somodel.jsonis instead seeded host→box per-create at the default~/.local/state/opencodepath (seedOpencodeModelStateinpackages/sandbox-cloud/src/agent-credentials.ts, called from the shared cloud create flow). Allowlisted indestroy/prune --alllike the other shared agent volumes.packages/sandbox-docker/src/opencode.tsowns the helpers. - The box image is
agentbox/box:dev. On first use the CLI pulls it from GHCR (ghcr.io/madarco/agentbox/box, taggedsha-<build-context fingerprint>) and retags it locally; on a pull miss it builds frompackages/sandbox-docker/Dockerfile.box. Build context is the monorepo root (so the Dockerfile canCOPY packages/ctl/dist/bin.cjs); seeBUILD_CONTEXT_DIR,BOX_IMAGE_REGISTRY,registryRefForSha, andpullOrBuildinimage.ts. Pulling is governed by thebox.imageRegistryconfig key (empty = always build) and the--buildflag oncreate/prepare. Publishing is automated in.github/workflows/box-image.yml; the GHCR package must be made public once for anonymous pulls to work.