agentbox.yaml

Reference for the in-box services and tasks configuration file

agentbox.yaml lives at your workspace root and is read inside the box by the supervisor (@agentbox/ctl, shipped as agentbox-ctl). It declares tasks (one-shot setup units) and services (long-running processes) run under a DAG scheduler, plus a few host-side blocks (carry, defaults, ide).

The file is validated twice: the host CLI pre-validates it before any box work on agentbox create (a config error aborts the run), and the in-box daemon re-validates on start. Editor autocomplete and validation come free from the JSON Schema. For the conceptual guide, see services and tasks.

TIP

Point the file at the published schema on the first line so editors (e.g. the Red Hat YAML extension) give you completion and validation:

# yaml-language-server: $schema=https://agent-box.sh/schema/agentbox.schema.json

In-repo examples use a local relative path instead; published projects use the URL above.

Top-level keys

All four top-level keys are optional, and unknown keys are rejected (additionalProperties: false). A missing or empty agentbox.yaml is completely fine — create does not fail without one.

KeyRead byPurpose
servicessupervisorLong-running processes (map of name → spec).
taskssupervisorOne-shot units that run to completion before dependents.
idehost (agentbox code)VS Code attach customizations; the supervisor ignores it.
defaultshost (@agentbox/config)Project-level AgentBox config defaults.

carry is also declared at the top level but is host-applied and parsed separately — see its own section below. Service and task names must match [A-Za-z0-9_-]+.

A minimal valid file can be just one task — no web app or port required:

# yaml-language-server: $schema=https://agent-box.sh/schema/agentbox.schema.json
tasks:
  install:
    command: pnpm install

Services

A service is a long-running process. The spec requires command; unknown fields are rejected.

FieldDefaultMeaning
command(required)Shell string (run via bash -c) or an argv array.
cwd/workspaceWorking directory; relative paths resolve against /workspace.
envExtra env vars; scalar values, coerced to strings.
autostarttrueStart automatically when the daemon boots.
restarton-failureRestart policy — see needs and restart.
backoffExponential backoff between restarts.
needsDAG dependencies.
ready_whenReadiness probe — see ready_when.
exposeMark the one web service — see expose.
idePer-service VS Code hints (host-side only).

A service moves through pending → waiting → starting → running → ready, and can land in unhealthy, crashed, backoff, or stopped. Logs land at /var/log/agentbox/<svc>.log inside the box.

Here is the web service from examples/express-ready:

services:
  web:
    command: 'set -a; [ -f .env ] && . ./.env; set +a; node server.js'
    needs: [install]
    env:
      PORT: '3000'
      GREETING: 'hello from agentbox'
    expose:
      port: 3000
      as: 80
    ready_when:
      port: 3000
      timeout_ms: 60000
    restart: on-failure

Manage a service from inside the box:

$ agentbox-ctl restart web
$ agentbox-ctl stop web
$ agentbox-ctl start web

stop does not exit the daemon; start restarts a previously-stopped service.

TIP

Use the array form of command to avoid shell quoting; use the string form (bash -c) when you need shell features like pipes, &&, or sourcing env files.

Tasks

A task is a one-shot unit that runs to completion. It accepts only four fields: command, cwd, env, and needs. Tasks cannot have restart, autostart, backoff, or ready_when — the schema rejects them. That is the key distinction from services.

A task moves through pending → waiting → running → done, and can land in failed or skipped. Tasks run before dependent services via needs:. Typical use: install deps, build, seed a database.

Make tasks idempotent — they re-run on every supervisor restart (which happens on box start, not just create). Guard slow paths with a marker file, as in the repo-root agentbox.yaml:

tasks:
  install:
    command: |
      set -e
      MARKER=node_modules/.agentbox-installed
      if [ -f "$MARKER" ]; then
        echo "deps already installed (marker present) — skipping"
        exit 0
      fi
      corepack enable >/dev/null 2>&1 || true
      pnpm install --frozen-lockfile || pnpm install
      touch "$MARKER"

  build:
    command: pnpm build
    needs: [install]

Re-run a task in-box:

$ agentbox-ctl run-task install
$ agentbox-ctl run-task install --force

run-task resets the task to pending so the scheduler reruns it; it is a no-op on an already-done task unless you pass --force.

HEADS UP

Tasks re-run on every daemon start, not just at create. A non-idempotent task (an unguarded git init, a destructive migration) will fire repeatedly — guard it with a marker file.

ready_when

A per-service readiness probe. Exactly one of port, log_match, or http must be present.

FieldDefaultMeaning
portTCP connect probe to host:<port>.
host127.0.0.1Host for the port probe; ignored otherwise.
log_matchRegex matched against the service's output; first match flips to ready.
httpHTTP(S) URL; sends GET, waits for expect_status (default: any 2xx).
expect_statusSpecific status to wait for; only valid with http.
interval_ms500Poll interval (ignored for log_match).
initial_delay_ms0Delay before the first probe.
timeout_ms60000Total time before timing out.
on_timeoutkillkill re-enters the restart policy; mark_unhealthy leaves the process running but flagged.
services:
  api:
    command: 'node server.js'
    ready_when:
      http: http://127.0.0.1:3000/health
      expect_status: 200
      timeout_ms: 60000

The port and log_match variants are one-liners: port: 3000 or log_match: 'listening on'. Use on_timeout: mark_unhealthy as the escape hatch for legitimately slow cold starts.

TIP

Downstream units gated with needs: [api] start only once the probe reports ready, not merely when the process spawns.

expose

expose marks a service as the web service. At most one service may set it.

expose:
  port: 3000
  as: 80

port (1–65535) is the port the service listens on inside the box. as is the container port AgentBox forwards to it. The supervisor runs an in-process TCP forwarder binding container :80127.0.0.1:<port> (the box's node binary has cap_net_bind_service, so binding :80 works as non-root).

Because expose is wired by the supervisor, not the container runtime, adding or changing it and running agentbox-ctl reload activates the web service with no box restart. The setup wizard relies on this when it writes agentbox.yaml after create.

HEADS UP

as must be 80 — it is the single container port AgentBox reserves for the published web URL. Any other value is rejected.

See web apps and tunnels for how the exposed port becomes a published URL on the host. VNC and screen sharing are separate — see browser and screen.

needs and restart

needs is an array of task/service names that must reach their terminal-good state before this unit starts, forming a DAG. Independent units launch in parallel. Cycles and unknown references are rejected at config load.

restart (services only) controls relaunch:

ValueBehavior
alwaysRestart regardless of exit code.
on-failure(default) Restart only on a non-zero exit.
neverLeave it dead after it exits.

backoff (services only) sets exponential backoff between restarts: initial_ms (default 500), max_ms (default 30000), factor (default 2). The runtime enforces max_ms >= initial_ms — a cross-field rule the JSON Schema cannot express.

The examples/test-workspace file contrasts all three restart modes:

services:
  ticker:
    command: 'i=0; while true; do echo "tick $i"; i=$((i+1)); sleep 1; done'
    restart: always

  flaky:
    command: 'echo starting; sleep 2; echo crashing; exit 1'
    restart: on-failure
    backoff:
      initial_ms: 200
      max_ms: 2000
      factor: 2

  one-shot:
    command: 'echo "hello from one-shot"; sleep 1'
    restart: never

After editing the DAG, apply the diff without restarting the box:

$ agentbox-ctl reload
added: (none)
removed: (none)
changed: web

HEADS UP

A needs cycle or a reference to a non-existent unit fails validation — the daemon will not start, and on the host agentbox create aborts before doing any docker work.

See background and parallel for running multiple boxes, and services and tasks for the DAG model in prose.

defaults

defaults is a host-side block: project-level AgentBox config defaults, with the same shape as ~/.agentbox/config.yaml. The in-box supervisor ignores it entirely; @agentbox/config validates it strictly when the host loads it.

It sits in AgentBox's layered precedence — CLI flag > workspace > project (this block) > global ~/.agentbox/config.yaml > built-in default — so it is the place to pin per-project box-creation defaults.

defaults:
  box:
    provider: docker

The full key set lives at https://agent-box.sh/schema/user-config.schema.json.

TIP

Use defaults to make a repo "remember" its provider and checkpoint so teammates get the same box without passing flags.

See configuration for the full key set and precedence. Note the cross-provider gotcha: pinning provider-native snapshot ids in shared keys can collide — the configuration page covers the per-provider box.defaultCheckpointDocker / ...Daytona / ...Hetzner / ...Vercel overrides.

carry

carry is declared at the top level but is host-applied, not parsed by the supervisor (it has its own parser). It is a declarative host→box file copy that bypasses .gitignore. Each entry maps a host src to an in-box dest:

FieldRule
srcMust start with /, ~/, or ./. ~/ = host home; ./ = project root.
destMust start with / or ~/. ~/ expands to /home/vscode.
modeOctal — accepts 0o600, "0600", or "600".
userNumeric uid that owns the file in-box (default 1000 = vscode; 0 keeps it root-owned).
excludeList of tar globs / bare dir names to drop when copying a directory (additive on top of the defaults below).
optionaltrue skips a missing src silently instead of erroring.

A shorthand string form "src=dest" (or just "src" to mirror) is also accepted.

carry:
  - src: ~/.agentbox/carry-smoke/marker.txt
    dest: ~/carried-marker.txt
    mode: 0o600
    optional: true
  - src: ../legacy-app
    dest: ~/legacy-app
    exclude:
      - "*/cache"

The canonical use case is developing AgentBox inside an AgentBox: carry ~/.agentbox/secrets.env and ~/.agentbox/claude-credentials.json so the in-box CLI is already authenticated.

When copying a directory, heavy regenerable dirs (.git, node_modules, bin, obj, packages, dist, .next, target) are dropped by default; exclude: adds to that set. The host resolver blocks .. traversal, denies /proc|/sys|/dev|/etc/passwd|/etc/shadow, caps each entry at box.cpMaxBytes (default 100 MiB) after excludes — the same limit agentbox cp uses — and flags symlinks that escape $HOME and the project root. On create, claude, codex, and opencode the host prompts once — listing each src→dest with size, mode, and symlink warnings — before copying.

# auto-approve every carry entry for this box
agentbox claude --carry-yes

# skip the carry block entirely
agentbox claude --carry skip

You can also set AGENTBOX_CARRY_YES=1 or AGENTBOX_CARRY=skip. Note that -y/--yes does not auto-approve carry — a non-TTY -y with non-empty entries fails loud and asks for the explicit env var. agentbox fork is the exception: it sends carry by default (opt out with agentbox fork --carry skip).

HEADS UP

carry deliberately bypasses .gitignore and copies host files into the box. Treat the box as the untrusted side and only carry what the agent genuinely needs (credentials, env files). The per-entry cap is box.cpMaxBytes (default 100 MiB), the same limit agentbox cp enforces.

Mark credential entries optional: true so a box still comes up when a given file or agent isn't installed on the host. For how non-carry env files reach the box, see environment; for how git state is seeded, see sync and git and teleport a project.

Validating the file

Editor validation is automatic via the schema modeline. The host CLI also pre-validates on agentbox create before any docker work — a config error aborts with a formatted message — and the in-box daemon re-validates on start. You can validate by hand inside the box without starting the daemon:

$ agentbox-ctl validate
OK: 1 service(s)

The path argument is optional and defaults to /workspace/agentbox.yaml. The command exits with code 2 on a syntax or shape error, or a missing file.

TIP

The runtime parser and the JSON Schema are kept in lockstep by a drift test, so what your editor flags and what the daemon enforces agree — except cross-field rules like max_ms >= initial_ms, which only the runtime validator can catch.

See cli for the host-side agentbox status and agentbox logs commands that proxy into the in-box supervisor.

On this page