This document is the single source of truth for the CLI surface, the config schema, and the module boundaries. Do not change anything here without telling the lead. The three teammate modules are built in parallel against it.
The machine-readable half of this contract lives in src/types.ts
(interfaces + constants). This file is the prose half. They must agree.
| Path | Owner | Responsibility |
|---|---|---|
src/types.ts |
lead | Shared types/constants. Change-request only. |
src/index.ts |
lead | Thin entry → cli.run(). |
bin/agentbox.mjs |
lead | Launcher (spawns tsx). |
assets/observer.mjs |
lead | Bundled Observer web server. |
src/cli/ |
cli-dev | Arg parsing, --help, config resolution, command router. |
src/docker/ |
docker-dev | Image build, container run/exec, host-mount prep. |
assets/Dockerfile |
docker-dev | The image definition. |
src/session/ |
session-dev | tmux entry-script generation, TTY sizing. |
README.md |
lead | Written last, from teammate reports. |
Invoked as agentbox <command> [flags] (via bin/agentbox.mjs or
tsx src/index.ts). up is the default command when none is given.
| Command | Behavior |
|---|---|
up |
(default) Build image if missing → ensure host mounts → write entry script → run/attach the container with tmux + Claude lead + Observer. |
shell |
Open an interactive bash shell in the box (start the container if needed; no tmux). |
rebuild |
Force docker build --no-cache, then behave like up. |
doctor |
Smoke test: check docker availability, image presence, host mount sources, tmux-in-image, port, and config. Print a checklist; exit non-zero on any hard failure. |
down |
Stop + remove the container. |
--help,-h— print usage (top-level and per-command). Never errors.--workspace <path>— override workspace mount (also envWORKSPACE).--memory <val>— docker memory limit, e.g.4g(also envMEM).--cpus <val>— docker cpu limit, e.g.4(also envCPUS).--port <n>— observer port (default 4317).--network <mode>—docker build --networkmode, e.g.host(also envDOCKER_NETWORK). Unset → Docker's default build network.--gpus <val>—docker run --gpuspassthrough, e.g.all(Linux/NVIDIA).--yolo— launch the lead with--dangerously-skip-permissions(teammates inherit it). Opt-in only; recorded as aagentbox.yolocontainer label so reattach can report the box's actual mode.--no-cache— (rebuild/up) force a clean image build.
WORKSPACE→config.workspaceMEM→config.memoryCPUS→config.cpusDOCKER_NETWORK→config.buildNetwork
See AgentboxConfig in src/types.ts. Built by cli.resolveConfig(env, flags).
Defaults (DEFAULTS in types):
- image
agentbox:latest, containeragentbox, tmux sessionagentbox - observer port
4317, memory4g, cpus4 workspacedefault =process.cwd()claudeDirdefault =~/.claude,claudeJsondefault =~/.claude.jsondockerfile=assets/Dockerfile,buildContext=assets/observerAsset=assets/observer.mjsentryScriptPath= a host temp path in a per-user 0700 subdir (os.tmpdir()/agentbox-<uid>/entry.sh) — never a fixed path in the shared temp dir (tamper/TOCTOU on multi-user hosts)
// src/cli/index.ts
export function run(argv: string[]): Promise<number>;
export function resolveConfig(env: NodeJS.ProcessEnv, flags: Record<string, string | boolean>): AgentboxConfig;
// src/docker/index.ts
export const docker: DockerModule; // see src/types.ts
// src/session/index.ts
export const session: SessionModule; // see src/types.tscli orchestrates: it imports { docker } and { session } and calls them.
docker and session MUST NOT import cli (no cycles). They may import
../types.ts only.
| Host source | → Container target | Notes |
|---|---|---|
config.workspace |
/workspace |
the code you edit |
config.claudeDir (~/.claude) |
/home/agent/.claude |
creds live here — never bake auth into the image |
config.claudeJson (~/.claude.json) |
/home/agent/.claude.json |
touch first so Docker mounts a file, not a dir |
config.entryScriptPath |
/agentbox/entry.sh |
run via bash (no execute-bit reliance) |
config.observerAsset |
/agentbox/observer.mjs |
the dashboard |
config.gitconfigPath (if present) |
/home/agent/.gitconfig |
read-only — git identity reuse |
config.ghConfigDir (if present) |
/home/agent/.config/gh |
read-only — gh CLI auth reuse |
Named cache volumes (CACHE_VOLUMES in types.ts): agentbox-npm-cache,
agentbox-cargo-registry, agentbox-pip-cache → the box itself stays
disposable (removed via down, or replaced by up's stale-container
cleanup — see §6.7), only tool downloads persist. Host identity mounts are
read-only, existence-gated, and NEVER copied into the image.
- Publish
-p <observerPort>:<observerPort>. - Apply
--memory <config.memory>and--cpus <config.cpus>. - Run as the non-root user
agent.
- Claude Code OAuth fails inside Docker → credentials come from the host
bind mounts (
~/.claude,~/.claude.json). Never bake auth into the image. - Observer must bind
0.0.0.0(not127.0.0.1) or the published port is unreachable from the host browser. (Handled inassets/observer.mjs.) - teammateMode
tmuxonly splits panes if the lead window is otherwise empty → keep the Observer in its OWN window (observer), never a pane inmain. - Launch the entry script via
bash /agentbox/entry.shso a missing execute bit never blocks startup. - Set
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1in themainwindow, and launch the lead with--teammate-mode tmux: since Claude Code v2.1.179 the default teammateMode isin-process, which silently disables the split-pane layout. - Read the real TTY size via
stty sizeso tmux isn't stuck at 80×24. - The tmux attach client must NEVER be container PID 1. The container
runs detached (
docker run -d, deliberately NOT--rm); the entry script sets the session up and idles while it exists, and clients attach viadocker exec … tmux attach. OtherwiseC-b d(detach) exits PID 1 and kills every agent. Not using--rmmeans a container that exits early (entry-script crash) stays inspectable viadocker logs;upremoves a stale stopped container before starting fresh, anddownremoves it explicitly.
Each teammate validates its OWN module (typecheck + a runnable smoke check that needs no Docker daemon where possible), max 5 iterations, then reports to the lead: what works, what's stubbed, and any contract friction. The lead synthesizes and writes the README.