AI Engineering from Scratch · Manual© 2026 Rohit Ghumare · MIT license

Docker Sandboxes and Docker Agent 101

Isolated microVMs for coding agents, and the agent runtime that runs inside them, command by command

Docker Sandboxes (sbx) and Docker Agent (docker-agent) sbx 0.47.0, docker-agent 1.149.0 · bf4169c · 2026-10-07 · Edition 2026.10
  • The Two Products on One Page
  • sbx run
  • sbx policy, sbx secret, sbx mcp
  • Kits and sbxenv.yaml
  • docker-agent run and the Agent File
  • docker-agent serve and share
  • Operating It
Plate IOne agent, one microVM, one proxy
One agent, one microVM, one proxy The recorded docker-agent run --sandbox on the capture Mac. On the host, docker-agent v1.149.0 stages the kit sandbox-kits/<hash>, writes the allowlist with models.dev and localhost:12434 into the proxy rules, and asks sbx and sandboxd to create the sandbox, which becomes running. Below the Hypervisor.framework line, the microVM docker-agent-<hash> from docker/docker-agent-sbx-templates:latest holds docker-agent version main, its filesystem and shell tools, the workspace mounts, and an environment whose proxy is gateway.docker.internal:3128 and whose provider keys read proxy-managed. The model call goes up to host.docker.internal:12434, through the proxy, and on to Docker Model Runner on port 12434 with ai/qwen3:4b. The answer is: The working directory contains README.md with 1 line. HOST · MACOS 26.2 ARM64 docker-agent run --sandbox --exec files-sandbox.yaml docker-agent v1.149.0, Homebrew sbx, sandboxd v0.47.0 proxy :3128 on the host Model Runner ai/qwen3:4b sandbox-kits/<hash> the staged kit allowlist models.dev localhost:12434 created, running create stage allow proxy rules :12434 docker-agent version main commit 154b78f2 runs files-sandbox.yaml host.docker.internal:12434 through HTTP_PROXY Hypervisor.framework · kern.hv_support: 1 microVM docker-agent-<hash> · docker/docker-agent-sbx-templates:latest workspace mounts repo-sandbox rw, agents ro sandbox-kits ro, cfg ro environment HTTP_PROXY gateway.docker.internal:3128 ANTHROPIC_API_KEY=proxy-managed filesystem, shell tools that act on the mounts inside the VM The working directory contains README.md with 1 line.
The agent runs in a microVM that sbx creates, its model call leaves through the host proxy, and the provider keys inside are placeholders. Read top to bottom. From capture/out/27-sandbox-run.txt, 27-inside.txt, and 27-inside-run.txt.
00

How to read this manual

Every command output in this manual comes from one recorded run of sbx and docker-agent on one Mac, and every rule traces to a ranked source.

This manual explains Docker Sandboxes, the sbx CLI at v0.47.0, and Docker Agent, the docker-agent CLI at v1.149.0. It follows one task from start to end: run an agent you do not trust on files you do.

Who this manual is for

You run coding agents such as Claude Code or Codex on real repositories, and you know Docker, git, and YAML. You have not used Docker Sandboxes or Docker Agent.

After the last part, you can:

  • Run a coding agent inside a sandbox, give it only the files it needs, and get its commits back without giving it your credentials.
  • Read one line of sbx policy log and say which rule allowed or blocked the connection, and what to change.
  • Write an agent file that runs a team of agents over local tools and a local model, record it, and replay it from a cassette.
  • Serve the same agent file over the HTTP API, MCP, ACP, and A2A, and share it as a signed OCI artifact.
  • Map any command from the 2025 Docker Sandboxes plugin or the cagent material to its current form.

The sandbox lesson explains when a task needs a process, a container, or a microVM boundary. This manual starts where that lesson stops, with two products that build the microVM boundary.

How it was made

The manual pins sbx v0.47.0 at commit 0411f50e, released 2026-10-05, and docker-agent v1.149.0 at tag commit bf4169c, released 2026-10-07. The facts were verified on 2026-10-08. sbx is a proprietary binary with no public source, so its help text ranks first.

Facts come from seven sources, ranked by authority. When two of them disagree, the higher one wins and the text says so.

RankSourceWhat the manual takes from itCited as
1the help text of sbx 0.47.0 and docker-agent 1.149.0, as the binaries print itevery command, flag, default, and pathhelp-sbx or help-agent and a command
2agent-schema.json at v1.149.0every key, type, and default of the agent fileschema and a definition
3the Sandbox Kit Spec v3 at tag v3.0.0-m.8the kit descriptor and each capabilitykitspec or kitcap and a section or a capability
4the Docker documentation, fetched 2026-10-08rules and limits that the help text does not statedocs-sbx, docs-agent, docs-dmr, docs-mcp, or docs-desktop and a page
5the release notes of sbx and the docker-agent changelogwhen a behaviour appeared, changed, or went awayrel-sbx or rel-agent and a version
6Docker blog posts and talkshistory and positioning, never a ruleblog or talk and a date
7the capture kit in capture/every command output, file, and record shownthe capture file name

Sources 1 to 5 are vendored under research/sources/, so the audit checks each quote word for word. Blog posts and talks are not vendored, and their quotes are not checked.

The sources disagree in many places. The largest are the kit generations (conflict C13) and the agent names and template image of each launch path (C9 and C61). Others are host secrets that sbx never injects (C7) and the two agent binaries on one Mac (C48). The sources reference lists every conflict with the ruling this manual prints.

The capture kit

The capture kit drives the real sbx, docker-agent, and docker binaries and writes what they print to capture/out/. It masks the values that change on every run, uses the Python standard library only, and has three recorded tiers:

TierWhat it needsWhat it records
Asbx, docker-agent, and the docker client installed, with no daemon, network, model, or keyversions, root help, docker-agent doctor, toolsets, models list, a dry run, and the legacy Desktop commands (files 00, 01, 16, 29)
Bsbx signed in, the sandboxd daemon, image pulls from Docker Hub, and HTTPS to example.com and mcp.deepwiki.comthe sandbox lifecycle, ports, cp, templates, --clone, policy, secrets, MCP, kits, env plan, and skills (files 02 to 15)
MDocker Desktop 4.94.0 with Docker Model Runner on TCP port 12434 and ai/qwen3:4b pulled, plus containers, Compose, buildx, and a local registryagent runs, teams, permissions, sessions, eval, five servers, share, Model Runner, Compose, run --sandbox, and the v3 kit build (files 17 to 28, 98)

The kit was recorded on one Mac with macOS 26.2 on Apple silicon. It ran sbx v0.47.0, docker-agent v1.149.0, Docker Desktop 4.94.0, and Docker Model Runner with ai/qwen3:4b. The documented default model, ai/qwen3:latest, failed twice to pull on that Mac with a digest mismatch (capture/out/25-model-pull-latest.txt). Every agent file therefore names the 4B tag of the same repository.

This kit cannot run offline in CI. sbx starts real microVMs from images on Docker Hub after a Docker sign-in, and the agent runs need a model. Where the tools are absent, python3 capture/run.py --check prints skipped with the reason and exits 0. Tier C needs Cloud Sandboxes, a GitHub token, or a change to ~/.ssh/config. It is not recorded, and the sections that need it cite the docs.

Most runs of tier M replay a recorded cassette with --fake, so a check compares like with like. The eval, three of the servers, one run with an explicit base_url, and run --sandbox call the model live with temperature: 0. The kit README lists what the recorded run showed that the docs did not say.

Conventions

docker-agent names the Homebrew binary, v1.149.0, in every command. docker agent names the Docker Desktop plugin, v1.144.0 on the capture Mac, and appears only where a program prints it.

$CAPTURE stands for the capture directory and $HOME for the home directory. Values that change on every run appear as tokens such as <uuid>, <hash>, <ts>, and <port>. The command lines of tier M leave out --config-dir, --data-dir, and --cache-dir, which point at capture/work/.

Citations name a place: help-sbx sbx run a command in the help text, schema AgentConfig a definition in the schema, and kitspec §3.4 a section of the kit spec. A docs citation such as docs-sbx Network access policies names a page or heading, and rel-sbx v0.43.0 names a release. A listing copies lines from a capture file unchanged, and a … marks a cut that the note explains.

Figures animate on the web, where a Replay button runs the steps again, and print and reduced motion show the final frame.

A first run

You need sbx v0.47.0, a Docker sign-in with sbx login, and a global network policy, which you set once. The capture set it to the balanced preset:

the global policy, set oncecapture/out/03-policy-init.txttext
$ sbx policy init balanced
Global network policy initialized to "balanced".
[exit 0]
Nothing is cut.

Then make a scratch directory and open a shell sandbox on it:

mkdir -p ~/sbx-scratch
sbx run shell ~/sbx-scratch

sbx run shell gives you a Bash login shell in a new sandbox, named shell-sbx-scratch after the default <agent>-<workdir> help-sbx sbx run. Inside, pwd prints the same path as on your host. Leave the shell, then list the sandbox and remove it:

sbx ls
sbx rm --force shell-sbx-scratch

The capture made its own shell sandbox with sbx create, which prints this summary:

the summary of a new shell sandboxcapture/out/04-create.txttext
$ sbx create shell $CAPTURE/fixtures/repo --name m101-demo

sandbox    m101-demo
agent      shell
workspace  $CAPTURE/fixtures/repo (rw)
image      docker/sandbox-templates:shell-docker
cpu        10
memory     32 GiB

Pulling image
  <layers>
✓ Image ready
✓ Created sandbox m101-demo
…
The image layers are masked as <layers>, and the closing hint is cut.

For the agent side, turn on Docker Model Runner with host TCP on port 12434 and pull the model with docker model pull ai/qwen3:4b. Save this agent file as dmr.yaml:

an agent on a local modelcapture/fixtures/agents/dmr.yamlyaml
version: "16"

providers:
  runner:
    provider: dmr
    base_url: http://localhost:12434/engines/llama.cpp/v1

models:
  qwen:
    provider: runner
    model: ai/qwen3:4b
    temperature: 0

agents:
  root:
    model: qwen
    description: Answers with one word.
    instruction: Reply with exactly one word.
Nothing is cut.

Run docker-agent doctor first. It finds the runner and the model:

the doctor with Model Runner reachablecapture/out/25-doctor.txttext
…
Docker Model Runner
  Status: reachable, 1 model(s) pulled:
    - docker.io/ai/qwen3:4b

Model auto-selection
  auto -> dmr/docker.io/ai/qwen3:4b
…
Cut to the Model Runner and model selection blocks.

Then run the agent without the terminal interface:

one answer from the local modelcapture/out/25-run-dmr.txttext
$ docker-agent run --exec --last fixtures/agents/dmr.yaml 'Say hello.'
Hello
[exit 0]
Nothing is cut. The capture passes the file by its path under fixtures/agents.

Open capture/out/27-sandbox-run.txt next. It is the same kind of agent run inside a sandbox, and the end-to-end section reads it line by line.

How the parts are ordered

Part 1 puts both products on one page. Parts 2 to 4 take sbx apart: the sandbox and its daemon, the isolation layers, and kits with environment files. Parts 5 and 6 take docker-agent apart: the agent file and its run, then the served and shared agent. Part 7 covers operation, and the Reference holds the lookup tables:

PartWhat it covers
1 · The Two Products on One Pagesbx runs any agent inside a microVM it owns, docker-agent runs an agent file, and docker-agent run --sandbox joins them through a kit and the proxy.
2 · sbx runA sandbox has a name, a workspace, a template, and a lifecycle that seven verbs and one daemon control.
3 · sbx policy, sbx secret, sbx mcpFive layers isolate the agent, and each layer has its own commands: the hypervisor, the workspace, the policy and its proxy, the secret store, and the MCP gateway.
4 · Kits and sbxenv.yamlA kit declares what a sandbox contains and can reach, and an environment file declares the sandbox and its secrets for approval before anything runs.
5 · docker-agent run and the Agent FileAn agent file is agents, models, toolsets, and the rules between them, and docker-agent run drives the loop, records it, and replays it.
6 · docker-agent serve and shareOne agent file answers over REST and SSE, an OpenAI-compatible endpoint, MCP, ACP, and A2A, travels as a signed OCI artifact, and runs on a local model.
7 · Operating ItThe same sandbox runs in the cloud, an organization narrows it with policies and reads its audit log, old commands map to new ones, and the claims are checked.
R · ReferenceEvery sbx and docker-agent command, settings keys and paths, agent file keys, toolsets, providers, sources and conflicts, a glossary, and the figure index.

Colour in figures

Each hue keeps one meaning in every figure:

bluesbx, sandboxd, and the sandbox microVM
violetdocker-agent, agent files, and the agent loop
greenthe workspace: files, mounts, clones, kits, and templates
amberlifecycle: sandbox states, sessions, and tool call phases
tealstored records: session.db, policy rules, the secret store, the audit log
indigonetwork traffic: the proxy, served endpoints, and streams
plummodels and model calls: Docker Model Runner and hosted providers
olivetools and effects outside the agent: MCP servers, shell commands, HTTP APIs
rosefailure: blocked connections, denied tool calls, errors
greythe host, the operator, Docker Hub, and anything outside both products

Figure 0.1 teaches the eight arrow styles with exchanges from the capture.

Fig. 0.1reading a sequence figure in this manualsequence
Reading a sequence figure in this manual Seven lifelines: the sbx CLI, the sandboxd daemon, the local policy store, a sandbox VM, the host proxy on port 3128, example.com, and Docker Model Runner on port 12434. Ten numbered rows use the eight arrow styles: a CONNECT to example.com:443 as a solid ink call, 403 Forbidden as a dashed rose failure, sbx policy allow network as a call, the rule example.com [tcp] as a solid teal durable write, Rule added as a dashed ink reply, the second CONNECT, the tunnel to example.com as a dashed olive outside effect, POST chat/completions as a solid plum model call, chat.completion.chunk as a dotted indigo stream, and running to stopped as a dashed amber state change. sbx CLI sandboxd host daemon policy store local policy sandbox VM the guest proxy :3128 on the host example.com outside Model Runner :12434 1 CONNECT example.com:443 a call · solid ink 2 403 Forbidden a failure · dashed rose 3 allow network a call · solid ink 4 example.com [tcp] a durable write · solid teal 5 Rule added a reply · dashed ink 6 CONNECT example.com:443 the same call, now allowed 7 tunnel to :443 an outside effect · dashed olive 8 POST chat/completions a model call · solid plum 9 chat.completion.chunk a stream · dotted indigo 10 running to stopped a state change · dashed amber
Each of the eight arrow styles marks one kind of exchange, and every label is a real command, rule, status, or event name from the capture. Read each numbered row from the tail of the arrow to its head, and the note under each label names the style. Rows 1 to 7 come from capture/out/09-blocked.txt, 09-allow.txt, and 09-allowed.txt. Rows 8 and 9 come from 27-inside-run.txt and the cassette 18-files, and row 10 from 04-auto-stop.txt.

The model call in row 8 passes through the proxy, which figure 1.3 draws as a separate hop.

Sources:help-sbx sbx run (research/sources/help-sbx.md); manual.json; research/sources/README.md; research/plan.md; research/conflicts-register.md rows C7, C9, C13, C48, C61; capture/README.md, capture/run.py; capture/fixtures/agents/dmr.yaml; capture/cassettes/18-files.yaml.gz; capture/out/README.md, 00-versions.txt, 03-policy-init.txt, 04-auto-stop.txt, 04-create.txt, 04-workspace.txt, 09-allow.txt, 09-allowed.txt, 09-blocked.txt, 25-doctor.txt, 25-model-ls.txt, 25-model-pull-latest.txt, 25-run-dmr.txt, 27-inside-run.txt, 27-sandbox-run.txt, 29-docker-agent-plugin.txt

Part 1

The Two Products on One Page

sbx runs any agent inside a microVM it owns, docker-agent runs an agent file, and docker-agent run --sandbox joins them through a kit and the proxy.

  1. 1.1sbx and docker-agent
  2. 1.2docker-agent run --sandbox, one run end to end
1.1

sbx and docker-agent

Each binary owns one thing: sbx owns the microVM, its proxy, and its secret store, and docker-agent owns the loop between a model, tools, and sub-agents.

You want a coding agent to work on a repository, but not to read your SSH keys or call any host it likes. Docker gives you two command-line programs for that job, sbx and docker-agent. A Mac with Docker Desktop also has a third name, docker agent, and this section draws the line between all three.

When you finish this section, you can name the owner of each part of a sandboxed run and pick a launch path. You can also tell two agent binaries on one machine apart.

What sbx owns

Sandbox: a microVM that sbx creates for one agent. "Every sandbox runs inside a lightweight microVM with its own Linux kernel" docs-sbx Isolation layers, and it has a private Docker Engine. Inside m101-demo, the capture reads Linux 7.0.14 on Ubuntu 26.04.1 and Docker Engine 29.8.1 (capture/out/04-guest.txt). The workspace appears at the same absolute path, and "containers started by the agent never appear in your host's docker ps" docs-sbx Develop and test locally.

sandboxd: the host daemon behind every local sbx command, reached at $HOME/Library/Application Support/com.docker.sandboxes/sandboxes/sandboxd/sandboxd.sock (capture/out/02-daemon-status.txt). "You don't need Docker Desktop or Docker Engine to use sbx" docs-sbx Install Docker Sandboxes. You do need a Docker sign-in with sbx login, and sbx diagnose reports it as Authentication — authenticated (capture/out/02-diagnose.txt). Conflict C15 records why both claims hold.

The daemon also runs the proxy, and one of its two log categories is proxy help-sbx sbx daemon log-level set. "All outbound TCP traffic from the sandbox routes through a proxy on your host" docs-sbx Architecture. On macOS, sbx secret set keeps each key in the system Keychain docs-sbx Where secrets are stored. The proxy adds the key to a request after the request leaves the VM, so the VM sees only the placeholder proxy-managed (capture/out/04-env.txt). The proxy and secrets take each one apart, and figure 1.1 shows who owns what.

Fig. 1.1what sbx owns and what docker-agent ownslayers
What sbx owns and what docker-agent owns Two columns over two bands. In the host band, sbx owns the sbx v0.47.0 CLI, the sandboxd daemon on its socket, the macOS Keychain that holds secrets, and the proxy on gateway.docker.internal:3128. docker-agent owns the docker-agent v1.149.0 binary, the agent file, the staged kit under sandbox-kits, and the persistent allowlist with localhost:12434. A dashed line marks the hypervisor. In the guest band, sbx owns the Linux 7.0.14 kernel, the private Docker Engine 29.8.1, the virtiofs workspace mount at the same path, and the environment with proxy variables and proxy-managed keys. docker-agent owns only the loop inside: the docker-agent process, its filesystem and shell tools, and its model call. owned by sbx owned by docker-agent HOST · MACOS 26.2 ARM64 sbx v0.47.0 the CLI you type sandboxd sandboxd.sock Keychain sbx secret set proxy :3128 policy, key swap docker-agent v1.149.0, Homebrew agent file files-sandbox.yaml sandbox-kits/<hash> the staged kit allowlist localhost:12434 Hypervisor.framework · kern.hv_support: 1 GUEST · ONE MICROVM PER SANDBOX Linux 7.0.14 aarch64 its own kernel, Ubuntu 26.04.1 Docker Engine 29.8.1 its containers stay out of docker ps workspace mount virtiofs rw, same absolute path environment HTTPS_PROXY, keys proxy-managed docker-agent process the loop: model, tools, sub-agents filesystem, shell tools that run in the VM model call host.docker.internal:12434 nothing else in the guest
sbx owns every layer from the daemon to the guest kernel, and docker-agent owns its binary, its files, and the loop inside the guest. Read each column from the host band down through the dashed hypervisor line. The kernel and engine come from m101-demo in capture/out/04-guest.txt and 04-env.txt. The docker-agent column comes from the run in 27-sandbox-run.txt and 27-inside.txt.
the command groups of sbxcapture/out/01-help-sbx.txttext
$ sbx --help
Docker Sandboxes creates isolated sandbox environments for AI agents, powered by Docker.
…
Sandbox Commands:
…
  create      Create a sandbox for an agent
  exec        Execute a command inside a sandbox
…
  run         Run an agent in a sandbox
…
Management Commands:
  daemon      Manage sandboxd daemon
  diagnose    Diagnose common issues with your sbx installation
  mcp         Manage MCP servers
  policy      Manage sandbox policies
…
  secret      Manage stored secrets
…
Each group is cut to the commands this part names, and the other groups and the flags are cut.

The agents sbx runs

sbx run accepts eleven agent names: claude, codex, copilot, cursor, devin, docker-agent, droid, gemini, kiro, opencode, and shell help-sbx sbx run. sbx create has eight of them as subcommands and leaves out copilot, droid, and kiro. Those three moved to public kits, and since v0.43.0 they "can be launched by name again" rel-sbx v0.43.0. sbx create docker-agent also answers to the alias cagent help-sbx sbx create docker-agent. shell gives you "a Bash login shell inside a sandbox with no pre-installed agent binary" docs-sbx Shell. Conflict C9 lists the shorter agent lists in older posts.

What docker-agent owns

Agent file: a YAML file that names agents, their models, their toolsets, and the rules between them. Docker Agent is "an open-source framework for building teams of specialized AI agents" docs-agent Docker Agent. Its run command drives the loop: a model call, the tool calls the model asks for, and the sub-agents it delegates to. The agent loop lesson explains that loop, and Part 6 serves and shares the same file.

The docker-agent binary isolates nothing by itself. On the host, its shell toolset runs commands "in the user's environment" (capture/out/16-toolsets.txt).

the command groups of docker-agentcapture/out/01-help-docker-agent.txttext
$ docker-agent --help
Docker AI Agent Runner.
…
Core Commands:
  getting-started Learn docker agent with a hands-on interactive tour
  run             Run an agent
  setup           Interactively set up a model (built-in provider, local, custom endpoint, or Claude Code)
  share           Share agents
…
Advanced Commands:
…
  eval            Run evaluations for an agent
…
  sandbox         Manage docker-agent sandbox settings
  serve           Start an agent as a server
  sessions        Inspect recorded sessions
…
Cut to the core group and four of the advanced commands. The diagnose group, the other commands, and the flags are cut.

Two launch paths

sbx run docker-agent ~/my-project starts from sbx. The sandbox uses docker/sandbox-templates:docker-agent and runs docker-agent run --yolo when you pass no arguments docs-sbx Docker Agent. Only project-level configuration in the workspace reaches it.

docker-agent run --sandbox agent.yaml starts from your agent file. Here docker-agent "orchestrates the installed sbx CLI" docs-agent Sandbox Mode, and --template defaults to docker/docker-agent-sbx-templates:latest help-agent docker-agent run. Two paths use two images, as conflict C61 rules. The capture kit recorded only the second path, and the next section follows it.

Fig. 1.2two ways to start docker-agent in a sandboxcomparison
Two ways to start docker-agent in a sandbox Two columns, one per launch path. Left: sbx run docker-agent on a project directory, where sbx creates the sandbox from docker/sandbox-templates:docker-agent, starts docker-agent run --yolo inside, reads only project config in the workspace, and applies the sbx policy; this manual has no capture of it. Right: docker-agent run --sandbox agent.yaml, where the host docker-agent drives sbx with docker/docker-agent-sbx-templates:latest, the binary inside reports version main, the agent file directory and the kit are mounted read-only, and models.dev and the sandbox allow hosts are opened; recorded in capture/out/27. A · sbx run docker-agent B · docker-agent run --sandbox you type sbx run docker-agent ~/my-project sbx creates the sandbox docker/sandbox-templates:docker-agent startup command inside docker-agent run --yolo agent file project config in the workspace only network the sbx policy of the sandbox in this manual docs only, no capture you type docker-agent run --sandbox agent.yaml docker-agent drives sbx docker/docker-agent-sbx-templates:latest agent binary inside docker-agent version main agent file its directory mounted ro, kit ro network sbx policy, plus models.dev and allow hosts in this manual capture/out/27-*
Both paths end with docker-agent in a microVM, but sbx run starts from a template image and docker-agent run --sandbox starts from your agent file. Read across each row. Path A comes from the docs page Docker Agent under docs-sbx, because no capture runs it. Path B comes from capture/out/27-sandbox-run.txt and 27-inside.txt.

Two agent binaries on one machine

Docker Desktop 4.94.0 bundles "Docker Agent v1.144.0" docs-desktop 4.94.0 as the CLI plugin docker agent. Homebrew installs v1.149.0 as docker-agent, which the docs say "can be used as a standalone binary" docs-agent Docker Agent.

two agent binaries on the capture Maccapture/out/29-docker-agent-plugin.txttext
$ docker agent version
docker agent version v1.144.0
Commit: 3873760f47ecf22f72f65bd776c056a337d6f35c
[exit 0]

$ docker-agent version
docker-agent version v1.149.0
Commit: Homebrew
[exit 0]
Nothing is cut.

The hints that v1.149.0 prints still say docker agent sandbox allow <host> (capture/out/16-sandbox-list.txt), and on this Mac that command runs v1.144.0. This manual writes docker-agent in every command and pins v1.149.0, as conflict C48 rules. A third build runs inside the sandbox template, as the next section shows.

The same plugin list holds sandbox v0.13.0, and docker sandbox prints only its removal notice (capture/out/29-docker-sandbox.txt). The migration section maps its commands. The list also holds ai v1.31.0, the plugin behind docker ai, which the docs call Gordon, "Docker's built-in AI assistant" docs-agent Docker Agent. Gordon is outside this manual.

Sources:help-sbx sbx run, sbx create, sbx create docker-agent, sbx daemon log-level set (research/sources/help-sbx.md); help-agent docker-agent run (research/sources/help-docker-agent.md); docs-sbx Isolation layers, Develop and test locally, Install Docker Sandboxes, Architecture, Where secrets are stored, Shell, Docker Agent (research/sources/docs-sandboxes.md); docs-agent Docker Agent, Sandbox Mode (research/sources/docs-docker-agent.md); docs-desktop 4.94.0 (research/sources/docs-desktop-release-notes.md); rel-sbx v0.43.0 (research/sources/sbx-releases.md); research/conflicts-register.md rows C9, C15, C48, C61, C72; capture/out/01-help-sbx.txt, 01-help-docker-agent.txt, 02-daemon-status.txt, 02-diagnose.txt, 04-env.txt, 04-guest.txt, 16-sandbox-list.txt, 16-toolsets.txt, 27-sandbox-run.txt, 27-inside.txt, 29-docker-agent-plugin.txt, 29-docker-plugins.txt, 29-docker-sandbox.txt

1.2

docker-agent run --sandbox, one run end to end

One recorded docker-agent run --sandbox shows the kit, the sandbox, and the allowlist, and one sbx exec into the same VM shows the model call through the proxy.

You have an agent file with the filesystem and shell toolsets, and its model is ai/qwen3:4b on Docker Model Runner on your Mac. On the host, shell runs with your permissions. --sandbox moves the tools into a microVM and leaves the model on the host.

This section follows the recorded run in capture/out/27-*, step by step, and figure 1.3 draws it. When you finish this section, you can read the launch summary of a sandboxed run and explain the two failures this run met.

Fig. 1.3one docker-agent run --sandbox, step by stepsequence
One docker-agent run --sandbox, step by step Seven lifelines: the operator, docker-agent v1.149.0 on the host, the .m101 state directories in the workspace, sbx with sandboxd, the sandbox VM, the host proxy on port 3128, and Docker Model Runner on port 12434. Fifteen rows: sandbox allow writes localhost:12434 to the persistent allowlist; run --sandbox --exec stages the kit sandbox-kits/<hash>, asks sbx to create docker-agent-<hash> from docker-agent-sbx-templates:latest, the sandbox becomes running with 10 CPUs and 32 GiB, and docker-agent allows models.dev and localhost:12434 on the proxy. Inside the VM the session store fails with readonly database (1032). An sbx exec then runs docker-agent inside with its data in /tmp/m101; its model call goes to host.docker.internal:12434 through the proxy, which forwards it to localhost:12434; the answer streams back as chat.completion.chunk events and the operator reads README.md with 1 line. sbx rm --force removes the sandbox. operator your shell docker-agent v1.149.0 .m101 dirs in workspace sbx and sandboxd sandbox VM the guest proxy :3128 on the host Model Runner :12434 1 sandbox allow localhost:12434 2 + localhost:12434 persistent allowlist 3 run --sandbox --exec files-sandbox.yaml 4 sandbox-kits/<hash> the auto-kit 5 create docker-agent-<hash> docker-agent-sbx-templates:latest 6 created, running cpu 10, memory 32 GiB 7 allow models.dev and localhost:12434 8 readonly database (1032) creating session store 9 sbx exec docker-agent run data dirs in /tmp/m101 10 host.docker.internal:12434 through HTTP_PROXY 11 localhost:12434 the sandbox allow rule 12 chat.completion.chunk the streamed answer 13 README.md with 1 line exit 0 14 sbx rm --force 15 removed
docker-agent stages a kit, has sbx create the VM, and opens two hosts on the proxy, and the model call from inside then leaves through that proxy. Read top to bottom, one numbered row per step, and row 8 is the failure that ends the run. Rows 1 to 8 come from capture/out/27-sandbox-allow.txt, 27-sandbox-run.txt, and 27-kit-cache.txt. Rows 9 to 15 come from 27-inside-run.txt and 27-sbx-rm.txt, and the chunk name in row 12 is the stream format of the cassette 18-files.

The agent file

The agent file differs from the host version only in its provider. Your host's 127.0.0.1 is "not reachable from inside the sandbox" docs-sbx Accessing host services from a sandbox, so the provider points at host.docker.internal:

the provider block of the sandboxed agent filecapture/fixtures/agents/files-sandbox.yamlyaml
version: "16"

providers:
  host-runner:
    provider: dmr
    base_url: http://host.docker.internal:12434/engines/llama.cpp/v1

models:
  local:
    provider: host-runner
    model: ai/qwen3:4b
    temperature: 0
…
Cut to the version, the provider, and the model. The agent, its instruction, and its two toolsets are cut.

Step 1: allow the model host

Persistent allowlist: the hosts that docker-agent sandbox allow stores, which "are added to the sandbox proxy's allow rules on every subsequent --sandbox run" help-agent docker-agent sandbox allow. The help names it the fix for a Blocked by network policy 403. The proxy translates host.docker.internal to localhost, so the capture allows localhost:12434 before the run:

one host added to the persistent allowlistcapture/out/27-sandbox-allow.txttext
$ docker-agent sandbox allow localhost:12434  (--config-dir, --data-dir, --cache-dir under fixtures/repo-sandbox/.m101, inside the workspace)
…
Added 1 host(s) to the persistent sandbox allowlist:
  + localhost:12434
[exit 0]
…
The welcome banner and the telemetry notice of the first run are cut, and so is the second command.

Step 2: keep the state directories in the workspace

The capture kit keeps docker-agent out of ~/.cagent with --data-dir, --cache-dir, and --config-dir. With --sandbox, a data directory outside the workspace is refused, and the help gives only the default, ~/.cagent help-agent docker-agent run. The capture kit therefore puts all three under .m101/ in the workspace:

a data directory outside the workspacecapture/out/27-data-dir-outside.txttext
…
Error: --data-dir must be inside the sandbox's writable workspace $CAPTURE/fixtures/repo-sandbox
[exit 1]
The command line is cut.

Step 3: the launch summary

the launch summary of docker-agent run --sandboxcapture/out/27-sandbox-run.txttext
…
Models gateway: none configured
Models catalog: allowlisting models.dev in the sandbox proxy
User sandbox allowlist: allowlisting 1 host(s) from `docker agent sandbox allow`:
  - localhost:12434

sandbox    docker-agent-<hash>
agent      docker-agent
workspace  $CAPTURE/fixtures/repo-sandbox (rw)
           $CAPTURE/fixtures/agents (ro)
           $CAPTURE/fixtures/repo-sandbox/.m101/cache/sandbox-kits/<hash> (ro)
           $CAPTURE/fixtures/repo-sandbox/.m101/cfg (ro)
image      docker/docker-agent-sbx-templates:latest
cpu        10
memory     32 GiB
…
✓ Created sandbox docker-agent-<hash>
The command line and the image pull progress are cut. The rest of the output follows in step 5.

The run set no --models-gateway. It allows models.dev, because "without it the first catalog lookup fails with a 403 Blocked by network policy error" docs-agent Auto-Kit. The next line repeats the host from step 1.

Auto-kit: a directory that docker-agent stages on the host, "bind-mounted read-only into the VM at the same path" docs-agent Auto-Kit. Here it is .m101/cache/sandbox-kits/<hash>, and its manifest.json holds only agent_ref and built_at (capture/out/27-kit-cache.txt). The workspace is read-write, and the agent file's directory, the kit, and the config directory are read-only.

Step 4: an ordinary sandbox

During the run, sbx ls lists docker-agent-<hash> with the agent docker-agent, the status running, and the same four workspaces, three of them :ro (capture/out/27-sbx-ls.txt). The sandbox is an ordinary one, and sbx lists and removes it like any other.

Step 5: the session store fails

Inside the VM, the run stopped before any model call:

the end of the sandboxed runcapture/out/27-sandbox-run.txttext
…
Error: creating session store: migration failed even after database reset: failed to create migrations table: attempt to write a readonly database (1032)
Error: 
[exit 1]
The two self-update warnings before the error are cut.

The data directory sits on the virtiofs workspace mount, which mount lists as rw. A Python sqlite3 write on the same mount fails the same way (capture/out/27-sqlite-probe.txt), so on this host SQLite cannot write through that mount. The run printed no safety mode, so conflict C62 stays open. Docker Agent "exits but does not stop or remove the sandbox VM" docs-agent Sandbox Mode, and the VM stayed running.

Step 6: the model call from inside

the agent binary and the proxy settings inside the VMcapture/out/27-inside.txttext
docker-agent version main
Commit: 154b78f2d517dae1397866dfb7586c4a302a3151
…
ANTHROPIC_API_KEY=proxy-managed
…
HTTP_PROXY=http://gateway.docker.internal:3128
…
NO_PROXY=localhost,127.0.0.1,::1,gateway.docker.internal
OPENAI_API_KEY=proxy-managed
…
Persistent sandbox allowlist is empty.
Cut to the version, four environment lines, and the in-VM allowlist. The other variables and the self-update warnings are cut.

The template runs docker-agent version main, not v1.149.0, although the docs build :latest from "The most recent v* release" docs-agent Sandbox Mode. The provider keys read proxy-managed. The in-VM allowlist is empty, because the allowed hosts arrive as proxy rules from the host.

The capture then starts the same agent file inside the VM by hand, with its state in /tmp/m101. This run shows the model path from the VM, not a working --sandbox launch:

the model call from inside the VMcapture/out/27-inside-run.txttext
$ sbx exec docker-agent-<hash> sh -c 'docker-agent --config-dir /tmp/m101 --data-dir /tmp/m101 --cache-dir /tmp/m101 run --exec --last …
…
The working directory contains README.md with 1 line.
[exit 0]
The command line is cut after the state flags, and the two self-update warnings are cut.

The request to host.docker.internal:12434 leaves through HTTP_PROXY, because that host is not in NO_PROXY. "The sandbox proxy translates host.docker.internal to localhost before forwarding the request" docs-sbx Accessing host services from a sandbox, and the rule from step 1 admits it. The answer is correct: the capture kit copies the same README into every workspace, and it holds the one line hello (capture/out/04-workspace.txt).

Step 7: remove the sandbox

the removalcapture/out/27-sbx-rm.txttext
$ sbx rm --force docker-agent-<hash>
Deleting sandbox docker-agent-<hash>...
Sandbox 'docker-agent-<hash>' removed
[exit 0]
Nothing is cut.

A later run from the same workspace reuses the VM, and creates a new one "only when the mount set has changed" docs-agent Sandbox Mode.

Sources:help-agent docker-agent run, docker-agent sandbox allow (research/sources/help-docker-agent.md); docs-agent Sandbox Mode, Auto-Kit (research/sources/docs-docker-agent.md); docs-sbx Accessing host services from a sandbox (research/sources/docs-sandboxes.md); research/conflicts-register.md rows C61, C62; capture/README.md; capture/fixtures/agents/files-sandbox.yaml; capture/out/27-sandbox-allow.txt, 27-data-dir-outside.txt, 27-sandbox-run.txt, 27-kit-cache.txt, 27-sbx-ls.txt, 27-sqlite-probe.txt, 27-inside.txt, 27-inside-run.txt, 27-sbx-rm.txt, 04-workspace.txt; capture/cassettes/18-files.yaml.gz

Part 2

sbx run

A sandbox has a name, a workspace, a template, and a lifecycle that seven verbs and one daemon control.

  1. 2.1sbx run, sbx create, sbx stop, and sbx rm
  2. 2.2sbx exec, sbx cp, and sbx ports
  3. 2.3sbx template save and sbx template load
  4. 2.4sandboxd, sbx diagnose, and sbx reset
  5. 2.5sbx settings list
2.1

sbx run, sbx create, sbx stop, and sbx rm

sbx run creates a sandbox when none exists and attaches to it, sbx create only creates, and stop, rm, and prune end a sandbox in three different ways.

You have a repository with two commits and a README, and you want a shell agent to work on it and on nothing else. The capture does that with sbx create shell $CAPTURE/fixtures/repo --name m101-demo, where $CAPTURE stands for the capture directory, and the rest of this part reuses that sandbox. When you finish this section, you can name the verb that creates, attaches, stops, removes, or prunes a sandbox, and what each one keeps.

The agent and the workspace

Agent: the first positional argument of sbx run and sbx create, "a built-in agent name or a sandbox kit reference" help-sbx sbx run. The built-in names are claude, codex, copilot, cursor, devin, docker-agent, droid, gemini, kiro, opencode, and shell. A kit reference is a directory, a ZIP file, a git repository, or an OCI reference, and relative paths "must be explicit paths such as ./my-kit or ../my-kit.zip" help-sbx sbx run. So sbx run my-kit looks for an agent called my-kit, and sbx run ./my-kit reads a kit, as the kit descriptor explains.

Workspace: the directory after the agent, mounted inside the microVM at the same absolute path. Inside m101-demo, pwd prints $CAPTURE/fixtures/repo, and mount lists that path as a virtiofs mount. sbx run mounts the current directory when you give no path, and sbx create without a path makes a sandbox with no mount, where "the agent then works in the container's own filesystem instead of on your files" help-sbx sbx create. Extra paths follow the first one, and :ro mounts one of them read-only. A read-only argument can name a single file, "which holds that one path out of reach inside a workspace the sandbox can otherwise write" help-sbx sbx run.

the create command and its summarycapture/out/04-create.txttext
$ sbx create shell $CAPTURE/fixtures/repo --name m101-demo

sandbox    m101-demo
agent      shell
workspace  $CAPTURE/fixtures/repo (rw)
image      docker/sandbox-templates:shell-docker
cpu        10
memory     32 GiB

Pulling image
  <layers>
✓ Image ready
✓ Created sandbox m101-demo

To connect to this sandbox, run:
  sbx run --name m101-demo
[exit 0]
Nothing is cut. The image layers are masked as <layers>.
the workspace seen from insidecapture/out/04-workspace.txttext
$ sbx exec m101-demo sh -c 'pwd; echo; ls -la; echo; cat README.md; echo; git log --oneline'
$CAPTURE/fixtures/repo
…
c7dfd56 second
086df68 first
[exit 0]
The directory listing and the README text are cut.

Names and re-attach

Name: the key of a sandbox in every later command, by default <agent>-<workdir>, the agent name and the workspace directory name. The rule is "at least two characters, starting with a letter or number, containing only letters, numbers, hyphens and periods (periods are rejected with --cloud); 'default' is reserved" help-sbx sbx create. Since v0.43.0 the daemon also rejects "names longer than 63 characters or ending in a hyphen or period" rel-sbx v0.43.0. The 2025 plugin allowed _ and + in a name, and conflict C25 records the change.

Two sandboxes can share one workspace. The capture creates m101-demo-2 on the same fixtures/repo, and sbx ls --json lists both as running. A blog post of 2026-03-11 said that Docker enforces one sandbox per workspace blog 2026-03-11. A talk of 2026-08-13 runs claude and codex on one workspace in two terminals talk 2026-08-13, and S27 rules that the names decide.

--name on sbx run re-attaches to an existing sandbox, and then "the agent positional is optional when the named sandbox already exists and is read from its spec" help-sbx sbx run. Give the agent too, and sbx run checks it against the stored one. sbx create prints that re-attach command at the end of its output.

two sandboxes on one workspacecapture/out/04-second-sandbox.jsonjson
{
  "sandboxes": [
    {
      "name": "m101-demo",
      …
      "status": "running",
      …
      "workspaces": [
        "$CAPTURE/fixtures/repo"
      ],
      …
    },
    {
      "name": "m101-demo-2",
      …
      "status": "running",
      …
      "workspaces": [
        "$CAPTURE/fixtures/repo"
      ],
      …
    }
  ]
}
Cut to the name, status, and workspace of each record. The ports of m101-demo belong to the next section.

What is fixed at creation

Some flags act on every attach, and most act once, when the sandbox is created. A re-attach with a different -p or --skills changes nothing, and the help says so flag by flag.

FlagWhen it appliesHelp page
-e KEY=VALUE, --env-file FILEthe agent session on every attach, and stored in the sandbox when this run creates itsbx run
--cpus N, -m SIZEat creation, memory defaults to half of host memory, between 512 MiB and 32 GiBsbx create
-p PORT, --deny-network HOSTat creation, and -p is ignored when re-attachingsbx run
--skills MODE, --static-mcp NAMES, --profile NAMEat creation onlysbx run
-t IMAGE, --pull POLICYat creation, and --pull is always, missing, or never, default alwayssbx run
-dprints the sandbox id and exits without a sessionsbx run
--rmremoves the sandbox after the agent session exits, new in v0.47.0sbx run

The recording Mac gave m101-demo cpu 10 and memory 32 GiB, the upper bound of the memory clamp. --rm "cannot be combined with --detached" rel-sbx v0.47.0, because a detached run has no session to end.

stop, rm, and prune

sbx stop keeps the sandbox and ends its microVM. The capture prints state preserved with the restart command, and the docs list what persists: "installed packages, Docker images, configuration changes, command history, and mountless workspace files all persist across stops and restarts" docs-sbx usage. The daemon also stops a sandbox on its own. After the capture closed its last session and waited, sbx ls showed m101-demo as stopped, and daemon.log gives the reason.

the daemon stops an idle sandboxcapture/out/04-auto-stop.txttext
$ grep auto-stop daemon.log | grep m101-demo | tail -2
{"time":"<ts>","level":"INFO","msg":"auto-stop grace period expired, stopping runtime","version":"v0.47.0 0411f50ee4700fe7bd37e6e7e3aced563e850ca9","runtime":"m101-demo"}
{"time":"<ts>","level":"INFO","msg":"auto-stopped runtime after last session disconnected","version":"v0.47.0 0411f50ee4700fe7bd37e6e7e3aced563e850ca9","runtime":"m101-demo"}
Cut to the log query and its two lines. The timestamps are masked as <ts>.

sbx run -d against an existing sandbox "keeps it running after sessions disconnect, until you stop or remove it" docs-sbx release-notes, and a kit can declare com.docker.sandbox/long-running@1 for the same effect, as the capabilities show.

sbx rm is the opposite of stop. For a local sandbox it "stops them, removes their containers, cleans up any Git worktrees, deletes sandbox state, and deletes secrets scoped to each removed sandbox" help-sbx sbx rm. --force skips the prompt and removes a sandbox with an open SSH connection, and --all removes every local sandbox.

sbx prune removes stopped sandboxes only, because "a running sandbox is never removed" help-sbx sbx prune. --filter until=168h keeps anything stopped within the last week, and a sandbox whose stop time the daemon cannot report is left alone. --dry-run --json shows both sets before you commit.

a dry run after one stopcapture/out/04-prune-dry-run.jsonjson
{
  "would_remove": [
    {
      "name": "m101-demo-2",
      "agent": "shell",
      "stopped_at": "<ts>",
      "workspaces": [
        "$CAPTURE/fixtures/repo"
      ]
    }
  ],
  "skipped_unknown_stop": []
}
Nothing is cut. m101-demo was still running.
the prune that ends the partcapture/out/04-prune.txttext
$ sbx prune --force
Deleting sandbox m101-demo...
Sandbox 'm101-demo' removed
Deleting sandbox m101-demo-2...
Sandbox 'm101-demo-2' removed
[exit 0]

$ sbx ls --json
{
  "sandboxes": []
}
[exit 0]
The stop of m101-demo before the prune is cut.

Both rm and prune delete the secrets scoped to the sandbox, which sbx secret explains. Figure 2.1 puts the verbs on the edges of one state machine.

Fig. 2.1the four states of a local sandboxstate
The four states of a local sandbox Four states: absent, running, stopped, and removed. sbx create and sbx run move a sandbox from absent to running. sbx stop and the auto-stop of the daemon move it to stopped, and sbx run --name, sbx exec, and sbx ports move it back to running. sbx rm and sbx prune move a stopped sandbox to removed, and sbx rm --force or sbx run --rm move a running one there. The sandboxes named are m101-demo and m101-demo-2 from the capture. absent no row in sbx ls running m101-demo m101-demo-2 removed gone from sbx ls stopped state preserved sbx create sbx run sbx rm --force sbx run --rm sbx stop or auto-stop sbx run --name sbx exec, sbx ports sbx rm sbx prune, stopped only dashed border: no microVM running · double border: no record remains
A sandbox is absent, running, stopped, or removed, and every sbx verb in this section moves it along exactly one edge. Read left to right. Dashed amber arrows are state changes, the names are the capture's two sandboxes, and the dashed box has no microVM running. From capture/out/04-create.txt, 04-stop.txt, 04-auto-stop.txt, and 04-prune.txt.

Sources:help-sbx sbx run, sbx create, sbx stop, sbx rm, sbx prune (research/sources/help-sbx.md); rel-sbx v0.43.0 and v0.47.0 (research/sources/sbx-releases.md); docs-sbx usage and release-notes (research/sources/docs-sandboxes.md); conflicts C25, S27 (research/conflicts-register.md); capture/out/04-create.txt, 04-workspace.txt, 04-guest.txt, 04-ls.json, 04-second-sandbox.txt, 04-second-sandbox.json, 04-stop.txt, 04-auto-stop.txt, 04-prune-dry-run.json, 04-prune.txt

2.2

sbx exec, sbx cp, and sbx ports

Three verbs reach into a sandbox: exec runs a command and starts a stopped sandbox, cp moves files across the boundary, and ports binds tcp4 unless told otherwise.

A server inside m101-demo listens on port 8080. From the host you want to open it, copy a file in, and read a result out. docker exec, docker cp, and docker run -p do those jobs for a container, and sbx has the same three verbs with a changed rule in each. When you finish this section, you can run a command as any user, copy a file either way, and publish a port that localhost reaches.

sbx exec

exec: sbx exec [flags] SANDBOX COMMAND [ARG...] runs one command inside the sandbox, and "If the sandbox is stopped, it is started first" help-sbx sbx exec. The flags follow docker exec, "except detached exec (-d/--detach) is not supported" help-sbx sbx exec, so -d appears in the help only to say so. The 2025 plugin ran docker sandbox exec -d detached, which conflict C31 records. With --cloud, the flags -d, --user, and --privileged "are rejected rather than silently ignored" help-sbx sbx exec.

The command runs as the agent user, uid 1000, with the sudo and docker groups, in the workspace directory. -u root runs it as root, which the help shows for apt-get update and the capture uses to create /opt/marker-from-demo. -w sets another working directory, -e KEY=VALUE and --env-file FILE set variables for that one command, and -it opens a shell.

Inside, a sandbox knows its own name. Since v0.39.0 sandboxes "expose their own identity as SANDBOX_NAME and SANDBOX_ID environment variables" rel-sbx v0.39.0, and SANDBOX_VM_ID stays as a deprecated copy of the name. WORKSPACE_DIR names the mount, and HOME is /home/agent.

the identity of m101-demo from insidecapture/out/04-env.txttext
$ sbx exec m101-demo sh -c 'env | sort'
…
HOME=/home/agent
…
PWD=$CAPTURE/fixtures/repo
…
SANDBOX_ID=<uuid>
SANDBOX_NAME=m101-demo
SANDBOX_VM_ID=m101-demo
…
WORKSPACE_DIR=$CAPTURE/fixtures/repo
…
[exit 0]
Cut to the identity and workspace variables. The proxy and credential variables belong to Part 3.

sbx cp

cp: sbx cp SRC DST, where "Either SRC or DST must be a sandbox path, written as SANDBOX:PATH" help-sbx sbx cp and the other side is a host path. "Copying between two sandboxes is not supported" help-sbx sbx cp. The capture's sbx cp m101-demo:/tmp/out.txt m101-demo-2:/tmp/x exits 1 with that message, so route such a copy through the host. A directory copy places the directory itself at the destination. When the destination is an existing directory, the source goes inside it, and -L follows symbolic links in the source. The sandbox path is a container path, so /tmp/in.txt in the capture sits outside the workspace mount and is deleted with the sandbox.

The one rule with a security history is copy-out. Release v0.38.0, published 2026-08-06, "Fixed a destination-escape flaw in sbx cp copy-out (CVE-2026-17106)" rel-sbx v0.38.0.

a copy in, a copy out, and a refused copycapture/out/06-cp.txttext
$ sbx cp $CAPTURE/work/in.txt m101-demo:/tmp/in.txt
[exit 0]

$ sbx exec m101-demo sh -c 'cat /tmp/in.txt; printf out > /tmp/out.txt'
in
[exit 0]

$ sbx cp m101-demo:/tmp/out.txt $CAPTURE/work/out.txt
[exit 0]

$ cat $CAPTURE/work/out.txt
out
[exit 0]

$ sbx cp m101-demo:/tmp/out.txt m101-demo-2:/tmp/x
error: copying between sandboxes is not supported
  try: sbx cp --help
[exit 1]
Nothing is cut.

sbx ports

ports: sbx ports SANDBOX lists the published ports, --publish SPEC adds one, and --unpublish SPEC removes one. The spec is [[HOST_IP:]HOST_PORT:]SANDBOX_PORT[/PROTOCOL], and "If HOST_PORT is omitted, an ephemeral port is allocated automatically" help-sbx sbx ports. Publishing "starts a stopped sandbox before creating the host binding" help-sbx sbx ports, as exec does.

The rule to learn is the address family. "When publishing without a PROTOCOL, tcp4 is used" help-sbx sbx ports, so the host binding is 127.0.0.1 alone, and you "Publish tcp explicitly to bind both families" help-sbx sbx ports. This changed in v0.42.0, when "a published port no longer listens on ::1 unless you name the protocol explicitly" rel-sbx v0.42.0. The 2025 plugin bound both loopbacks, as conflict C39 records.

PROTOCOLHost binding when HOST_IP is omitted
none127.0.0.1 as tcp4, or tcp6 on ::1 when HOST_IP is an IPv6 address
tcp, udp127.0.0.1 and ::1, or 127.0.0.1 alone when the sandbox is IPv4-only
tcp4, udp4127.0.0.1
tcp6, udp6::1

The capture publishes 18081:8080, gets 127.0.0.1:18081 -> 8080/tcp4, and curl answers 200 on 127.0.0.1 and fails with exit 7 on ::1.

a tcp4 binding and its listingcapture/out/05-ports.txttext
$ sbx ports m101-demo --publish 18081:8080
Published 127.0.0.1:18081 -> 8080/tcp4
[exit 0]

$ sbx ports m101-demo
HOST IP     HOST PORT   SANDBOX PORT   PROTOCOL
127.0.0.1   18081       8080           tcp4
[exit 0]
Nothing is cut.
only the IPv4 loopback answerscapture/out/05-ports-curl.txttext
$ curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:18081/README.md
200
[exit 0]

$ curl -sS -o /dev/null -w '%{http_code}\n' 'http://[::1]:18081/README.md'
curl: (7) Failed to connect to ::1 port 18081 after <n> ms: Couldn't connect to server
000
[exit 7]
Nothing is cut. The connect time is masked as <n>.

--publish 8080/tcp then binds both families, each on an ephemeral host port, and sbx ports --json lists every binding as host_ip, host_port, sandbox_port, and protocol.

a dual-stack binding beside the tcp4 onecapture/out/05-ports-tcp.txttext
$ sbx ports m101-demo --publish 8080/tcp
Published 127.0.0.1:<port> -> 8080/tcp
Published [::1]:<port> -> 8080/tcp
[exit 0]

$ sbx ports m101-demo --json
…
  {
    "host_ip": "::1",
    "host_port": "<port>",
    "sandbox_port": 8080,
    "protocol": "tcp"
  }
]
[exit 0]
Cut to the publish and the last record of the JSON listing. The ephemeral ports are masked as <port>.

Unpublish without a protocol removes the mapping "whether it was published with that same default or as dual-stack tcp" help-sbx sbx ports, and the capture ends with sbx ports m101-demo --json printing []. -p on sbx run applies at creation only, so change ports later with sbx ports. sbx ls prints the bindings in its PORTS column, as 127.0.0.1:18081->8080/tcp4 in the capture. In the cloud a published port gets a public URL and UDP is refused, as sbx --cloud shows.

Sources:help-sbx sbx exec, sbx cp, sbx ports, sbx run (research/sources/help-sbx.md); rel-sbx v0.38.0, v0.39.0, v0.42.0 (research/sources/sbx-releases.md); docs-sbx usage (research/sources/docs-sandboxes.md); conflicts C31, C39 (research/conflicts-register.md); capture/out/04-env.txt, 04-guest.txt, 04-workspace.txt, 05-ports.txt, 05-ports.json, 05-ports-curl.txt, 05-ports-ls.txt, 05-ports-tcp.txt, 05-ports-unpublish.txt, 06-cp.txt, 07-template-save.txt

2.3

sbx template save and sbx template load

A template is a snapshot in the sandbox runtime's image store, reused with --pull never -t TAG, exported as a tar, and loaded on another host.

You spent an hour inside m101-demo installing a toolchain, and tomorrow a second sandbox needs the same tools without the hour. The capture leaves a marker file at /opt/marker-from-demo, saves the sandbox as m101-tpl:v1, and creates m101-from-tpl from it. When you finish this section, you can save a template, start a sandbox from it, and carry it to another host as a tar.

What a template holds

Template: a saved snapshot of a sandbox's container filesystem, stored as an image: "Templates are saved snapshots of sandboxes that can be reused to create new sandboxes with: sbx run --pull never -t TAG AGENT [WORKSPACE]" help-sbx sbx template. The docs draw the line between image and kit: "A template contains image content; the agent kit still supplies runtime settings such as credentials and network rules" docs-sbx Saving a sandbox as a template. Mounted filesystems are not in it, because "A saved template isn't a backup of the whole sandbox" docs-sbx Saving a sandbox as a template, and that excludes host workspaces and the Docker store at /var/lib/docker. Everything else in the container filesystem is in it, including any key an agent wrote to a file. Keep credentials in sbx secret and the proxy instead. Agent configuration files such as /home/agent/.claude/settings.json "are always recreated when a sandbox is created" docs-sbx Limitations, so a change there does not survive either.

Every built-in agent starts from docker/sandbox-templates:<variant>, and m101-demo pulled docker/sandbox-templates:shell-docker. Variants with a -docker suffix "include Docker Engine for building and running containers inside the sandbox" docs-sbx Choose a template, which is why docker version answers inside m101-demo. With platform.images.useDHI set, "the default template docker/sandbox-templates:claude-code-docker becomes dhi/sbx-templates:claude-code-docker" docs-sbx platform.images.useDHI, and the tag stays the same.

sbx template save

sbx template save SANDBOX TAG needs a stopped sandbox. The capture tries it on the running m101-demo, reads cannot save a running sandbox, stops it, and saves m101-tpl:v1. The image "is stored in the sandbox runtime's image store" help-sbx sbx template save, and that store is separate from the image store of the Docker daemon on the host docs-sbx Load a template. The 2025 plugin's docker sandbox save loaded the image into the host daemon by default, and --output was the way to a file, as conflict C32 records.

a save refused, a stop, and a savecapture/out/07-template-save.txttext
$ sbx template save m101-demo m101-tpl:v1 --output $CAPTURE/work/m101-tpl.tar
Sandbox m101-demo is running and must be stopped before saving. Stop it now? (y/N): error: cannot save a running sandbox; stop it first with:
  try: sbx stop m101-demo
[exit 1]

$ sbx stop m101-demo
Sandbox 'm101-demo' stopped; state preserved. Restart with: sbx run --name m101-demo
[exit 0]

$ sbx template save m101-demo m101-tpl:v1 --output $CAPTURE/work/m101-tpl.tar
Snapshotting image in sandbox ...
Exporting image to $CAPTURE/work/m101-tpl.tar ...
Exported to $CAPTURE/work/m101-tpl.tar

Save complete. To use the image as a template:
    sbx run --pull never -t docker.io/library/m101-tpl:v1 AGENT [WORKSPACE]
[exit 0]
The exec that creates the marker file is cut. The capture directory is masked as $CAPTURE.

sbx template ls --json then lists three images: the base docker.io/docker/sandbox-templates:shell-docker, the new docker.io/library/m101-tpl:v1 with flavor shell, and docker.io/sandboxes-swap/m101-demo:bf19e491. The last one is an image the runtime keeps for the stopped sandbox, and it is gone once every sandbox is removed. Each record has id, repository, tag, flavor, created_at, and size, and the capture masks the id, so this manual does not state its format.

the template in the image storecapture/out/07-template-ls.jsonjson
{
  "images": [
    …
    {
      "id": "<id>",
      "repository": "docker.io/library/m101-tpl",
      "tag": "v1",
      "flavor": "shell",
      "created_at": "<ts>",
      "size": "<n>"
    },
    …
  ]
}
Cut to the record of m101-tpl:v1.

Reuse with --pull never -t

Reuse is sbx run --pull never -t TAG AGENT [WORKSPACE], and the help explains the flag: "Use --pull never to use the saved image without trying to pull it from a registry" help-sbx sbx template save. The capture uses sbx create --pull never -t m101-tpl:v1 shell --name m101-from-tpl, reads Checking image instead of Pulling image, and finds /opt/marker-from-demo inside the new sandbox. Name the agent the template was built for, because a Claude template run with codex prints a warning that the sandbox "may not work correctly" docs-sbx Limitations.

a sandbox from the template, with the marker filecapture/out/07-template-run.txttext
$ sbx create --pull never -t m101-tpl:v1 shell --name m101-from-tpl

sandbox    m101-from-tpl
agent      shell
workspace  none · no workspace bind mount
image      m101-tpl:v1
cpu        10
memory     32 GiB

Checking image
✓ Image ready
✓ Created sandbox m101-from-tpl
…
$ sbx exec m101-from-tpl sh -c 'ls -l /opt/marker-from-demo'
-rw-r--r-- 1 root root 0 <date> /opt/marker-from-demo
[exit 0]
The sbx ls between the two commands is cut.

sbx template inspect is "Cloud-only in v1: requires --cloud" help-sbx sbx template inspect. The local attempt in the capture exits 1 with a hint to run docker image inspect -- m101-tpl:v1. That command reads the host daemon's store, which the runtime does not share, so use sbx template ls for a local template. sbx template rm m101-tpl:v1 --force prints Removed: m101-tpl:v1, and sbx reset clears the cached images too.

Export and load

--output FILE on save also writes a tar. work/m101-tpl.tar has 24 entries and starts with blobs/sha256/, the layout of an OCI image. On the other host, sbx template load FILE loads "an image from a tar file into the sandbox runtime's image store" help-sbx sbx template load, and sbx run --pull never -t m101-tpl:v1 shell starts from it. The capture did not load the tar on a second machine, so figure 2.2 draws that step from the help text. The same path imports an image you built yourself: docker image save writes the tar and sbx template load imports it, so the image "doesn't need to be reachable from a registry at sandbox creation time" docs-sbx Load a template.

Fig. 2.2from a stopped sandbox to a template, a tar, and a new sandboxflow
From a stopped sandbox to a template, a tar, and a new sandbox Top row, left to right: the stopped sandbox m101-demo with its marker file, sbx template save writes the image library/m101-tpl:v1 into the runtime image store, and --output exports it as the tar file m101-tpl.tar in OCI layout. Bottom row: sbx create --pull never -t m101-tpl:v1 makes m101-from-tpl from the store, with the marker file present, and a copy of the tar reaches another host where sbx template load imports it. ON THE RECORDING MAC m101-demo sbx stop m101-demo /opt/marker-from-demo runtime image store library/m101-tpl:v1 flavor shell m101-tpl.tar OCI layout blobs/sha256/ m101-from-tpl no workspace mount /opt/marker-from-demo another host sbx template load m101-tpl.tar template save stop first --output tar file sbx create shell --pull never -t m101-tpl:v1 copy by hand the load on another host follows the help text and is not recorded
A template moves from a stopped sandbox into the runtime image store, out as a tar, and into a new sandbox with --pull never -t. Read left to right, then down. Solid teal arrows are durable writes into a store or a file. The solid ink arrow is the create call, and the dashed olive arrow is a copy you make outside sbx. From capture/out/07-template-save.txt, 07-template-ls.json, 07-template-tar-head.txt, and 07-template-run.txt. The load on another host is not recorded and follows the help text of sbx template load.

In the cloud, sbx --cloud template load FILE NAME needs --cpus and --memory-mib that together name a billable shape help-sbx sbx template load. The shapes run from micro, 1 vCPU and 2048 MiB, to xl, 16 vCPU and 32768 MiB. --capture-mode all adds memory and a microVM checkpoint to the disk capture. Tier C was not recorded, so this manual shows no cloud listing, and sbx --cloud cites the help text instead.

Sources:help-sbx sbx template, sbx template save, sbx template load, sbx template inspect, sbx template rm (research/sources/help-sbx.md); docs-sbx usage, Saving a sandbox as a template, Load a template, Template caching, Choose a template, platform.images.useDHI (research/sources/docs-sandboxes.md); conflict C32 (research/conflicts-register.md); capture/out/04-create.txt, 04-guest.txt, 07-template-save.txt, 07-template-ls.json, 07-template-ls.txt, 07-template-run.txt, 07-template-inspect.txt, 07-template-rm.txt, 07-template-tar-head.txt, 99-final-state.txt

2.4

sandboxd, sbx diagnose, and sbx reset

One daemon owns every sandbox, its socket and log live under one state directory, and two commands check that daemon and wipe it.

sbx ls hangs, or a command fails with ensure daemon: daemon exited unexpectedly, and you need to know which process answers, where it writes, and how to start over. Every verb in this part talks to one host daemon, sandboxd, over a Unix socket. When you finish this section, you can find the socket and the log, read the thirteen checks of sbx diagnose, and say what sbx reset deletes.

sandboxd

sandboxd: the host daemon behind every local sbx verb, managed with sbx daemon start, stop, restart, status, and log-level set. The settings commands "use the local daemon to read evaluated values and manage overrides, starting it if necessary" help-sbx sbx settings, and the first probe of sbx settings list printed Starting sandboxd daemon... before its table. sbx daemon start -d runs it in the background. --policy allow-all, balanced, or deny-all initializes the global network policy at the same time, which sbx policy init covers. sbx daemon log-level set TARGET LEVEL changes one log category, proxy, general, or all.

sbx daemon status --json names the socket and the log, both under ~/Library/Application Support/com.docker.sandboxes/sandboxes/sandboxd/ on the recording Mac, where $HOME is a capture mask.

where the daemon listens and writescapture/out/02-daemon-status.jsonjson
{
  "status": "running",
  "socket": "$HOME/Library/Application Support/com.docker.sandboxes/sandboxes/sandboxd/sandboxd.sock",
  "logs": "$HOME/Library/Application Support/com.docker.sandboxes/sandboxes/sandboxd/daemon.log"
}
Nothing is cut.

The log is JSON lines with time, level, msg, version, and often a runtime, the sandbox name, and the lifecycle section reads two of them. Since v0.43.0 "The local daemon now verifies connecting operating-system users on Unix sockets and Windows named pipes" rel-sbx v0.43.0, and since v0.46.0 "Starting a second daemon against a state directory already in use fails with an error instead of disrupting the running daemon" docs-sbx release-notes.

The state directory

State directory: the tree where sandboxd keeps sandboxes, images, policies, and its socket. On macOS it is ~/Library/Application Support/com.docker.sandboxes/, on Windows %LOCALAPPDATA%\DockerSandboxes, and on Linux the state "is spread across three directories" docs-sbx Removing all state: ~/.local/state/sandboxes/, ~/.cache/sandboxes/, and ~/.config/sandboxes/. Release v0.29.0 added the SANDBOXES_STORAGE_ROOT override rel-sbx v0.29.0, and v0.31.0 moved "the state directory symlink from /tmp to ~/.sbx/run/" rel-sbx v0.31.0. The daemon binds its containerd socket under that symlink. The sbx kit builder history help pages, captured in a shell that could not bind sockets, end in "failed to create unix socket on $HOME/.sbx/run/d/containerd/containerd.sock.ttrpc" help-sbx sbx kit builder history.

Two more trees matter: the shared skills store at sandboxes/agent-skills under the state directory docs-sbx agent-skills, and the audit log under ~/Library/Logs/com.docker.sandboxes/sandboxes/auditkit/, where "Files are named audit-<utc-timestamp>-<process-uuid>-<seq>.jsonl" docs-sbx audit. The 2025 plugin kept its VMs under ~/.docker/sandboxes/vm/ and its images under ~/.docker/sandboxes/image-cache/, which the migration section maps. Figure 2.3 draws the macOS tree, and the settings table lists every path for the three systems.

Fig. 2.3the sandboxd state directory on macOStree
The sandboxd state directory on macOS Two roots at the top: com.docker.sandboxes under Application Support, which holds sandboxes/sandboxd with sandboxd.sock and daemon.log and sandboxes/agent-skills, and com.docker.sandboxes under Logs, which holds sandboxes/auditkit with the audit JSONL files. Below, the symlink ~/.sbx/run points at the state directory and holds the containerd ttrpc socket, and a greyed box shows ~/.docker/sandboxes, the tree of the 2025 plugin. com.docker.sandboxes/ ~/Library/Application Support/ com.docker.sandboxes/ ~/Library/Logs/ sandboxes/sandboxd/ sandboxd.sock daemon.log agent-skills/ sandboxes/agent-skills/ shared skills store sandboxes/auditkit/ audit-<utc-timestamp>- <process-uuid>-<seq>.jsonl ~/.sbx/run symlink to the state directory d/containerd/containerd.sock.ttrpc ~/.docker/sandboxes/ vm/<name>/, image-cache/ the 2025 docker sandbox plugin deleted by docker sandbox reset dashed border: a symlink, or the plugin tree that sbx does not use · teal: stored records
Everything sandboxd owns on macOS sits under one Application Support directory, with the audit log under Logs and a symlink at ~/.sbx/run. Read top down. Solid lines are containment, the dashed line is the symlink, and the grey dashed box is the 2025 plugin's tree, deleted by docker sandbox reset. From capture/out/02-daemon-status.json, research/sources/docs-sandboxes.md, help-sbx.md, and sbx-releases.md.

sbx diagnose

sbx diagnose runs thirteen checks in four groups and prints a pass, a warning, or a failure for each. Installation finds the binary, its version, the daemon, and a diagnostics bundle. Platform confirms kern.hv_support is 1 and finds mkfs.erofs with its default block size and the guest kernel page size. Storage checks the state directory, its permissions, and the free space. Connection checks the version match, the socket, the SSH client config, and the sign-in.

The page-size check exists because v0.43.0 made diagnose warn "if its default block size exceeds the sandbox kernel's page size" rel-sbx v0.43.0. On the recording Mac the block size is 4096 bytes and the guest page size is 16384 bytes, which getconf PAGESIZE inside m101-demo confirms.

the two platform checks and the summarycapture/out/02-diagnose.jsonjson
{
  "version": "1.0",
  "checks": [
    …
    {
      "name": "Virtualization",
      "status": "pass",
      "message": "supported",
      "detail": "kern.hv_support is 1",
      "hint": ""
    },
    {
      "name": "mkfs.erofs",
      "status": "pass",
      "message": "found",
      "detail": "/opt/homebrew/Caskroom/sbx/0.47.0/Sbx.app/Contents/libexec/mkfs.erofs, default block size <n> bytes, guest kernel page size <n> bytes",
      "hint": ""
    },
    …
  ],
  "summary": {
    "pass": 13,
    "warn": 0,
    "fail": 0,
    "skip": 0
  }
}
Cut to the Virtualization and mkfs.erofs checks and the summary. The byte counts are masked as <n>.

--json gives a version, a checks array with name, status, message, detail, and hint, and a summary with pass, warn, fail, and skip counts. -o github-issue formats the same report for a bug report, and --upload sends a bundle to Docker support. diagnostics.autoUpload in sbx settings list is the consent for automatic uploads.

sbx reset

sbx reset returns the install to a freshly installed state, and the help lists the steps help-sbx sbx reset:

  • stop running sandboxes, with a 30 s timeout
  • clear the image cache and the internal registries
  • delete all sandbox state and all policies
  • remove the managed SSH configuration
  • clear "the Gordon assistant's sessions and history" help-sbx sbx reset
  • delete stored secrets, unless --preserve-secrets
  • sign out, stop the daemon, and remove the state, cache, and config directories

The docs give Gordon no role in a sandbox, so this manual quotes that line and claims nothing more, as conflict C33 rules. The docs reach for the command after an upgrade: "A newer version of sbx upgraded the local database to a schema that older binaries don't understand" docs-sbx sbx reset, and --preserve-secrets keeps your secrets through that. The last resort is to delete the state directory by hand after sbx reset. Your workspaces stay where they are. The docs say host workspace files "remain on your host" when a sandbox is removed, and sbx reset lists only state, cache, and config directories. Commands that help text names but the help tree does not list, such as sbx mount, are collected in sbx settings list.

Sources:help-sbx sbx daemon, sbx daemon start, sbx daemon log-level set, sbx diagnose, sbx reset, sbx settings, sbx kit builder history (research/sources/help-sbx.md); rel-sbx v0.29.0, v0.31.0, v0.43.0 (research/sources/sbx-releases.md); docs-sbx release-notes, Removing all state, agent-skills, audit, usage (research/sources/docs-sandboxes.md); research/sources/probes/sbx-diagnose.txt, sbx-settings-list.txt; help-legacy-docker-sandbox.md (docker sandbox reset); conflict C33 (research/conflicts-register.md); capture/out/02-daemon-status.json, 02-daemon-status.txt, 02-diagnose.json, 02-diagnose.txt, 04-auto-stop.txt, 04-guest.txt

2.5

sbx settings list

Every setting has a default, a source, a user override, and a restart flag, some have an environment alias, and sbx settings list --json is the only complete list.

Your company routes every connection through a proxy, and your first sbx run cannot pull a template. The fix is a setting, proxy. The questions are where to set it, whether the daemon must restart, and which value wins when an environment variable disagrees. When you finish this section, you can read one record of sbx settings list --json, change a setting, and know when sbx daemon restart is needed.

One record

Setting: a key such as proxy with a type, a default, an evaluated value, and a source. sbx settings list --json printed 31 records on the recording Mac, each with key, type, default, value, source, and description, and some with env_var or requires_restart. The source is one of three: "The SOURCE column shows where the value came from (default, envvar, or override)" help-sbx sbx settings list. The type is bool, int, float, string, or json, and sbx settings set KEY VALUE parses the value by that type help-sbx sbx settings set. Thirteen of the 31 keys have no env_var, among them proxy, mcp.forceLocalGateway, and skills.defaultMode, so the environment cannot set them and only an override can.

five of the 31 recordscapture/out/02-settings.jsonjson
[
  …
  {
    "default": [
      {
        "identityRegexp": "^.*@docker\\.com$",
        "issuer": "https://accounts.google.com"
      }
    ],
    "description": "JSON array of trusted signer policies (key-based {\"key\":path} or keyless {\"issuer\":...,\"identity\":...}). Defaults to Docker employee identities (*@docker.com via https://accounts.google.com).",
    "env_var": "DOCKER_SANDBOXES_KIT_TRUSTED_SIGNERS",
    "key": "kit.trustedSigners",
    "source": "default",
    "type": "json",
    …
  },
  …
  {
    "default": "",
    "description": "Upstream proxy for sandbox, daemon, and supported CLI host egress (URL, PAC source, \"system\", or \"direct\"; empty = automatic: HTTP(S)_PROXY if set, otherwise the host OS proxy).",
    "key": "proxy",
    "requires_restart": true,
    "source": "default",
    "type": "string",
    "value": ""
  },
  …
  {
    "default": "",
    "description": "Upstream proxy for sandbox egress only (overrides proxy).",
    "env_var": "DOCKER_SANDBOXES_PROXY",
    "key": "proxy.sandbox",
    "requires_restart": true,
    "source": "default",
    "type": "string",
    "value": ""
  },
  …
  {
    "default": "readonly",
    "description": "Default for an omitted --skills flag or sbx.yaml `skills:` key: \"off\", \"readonly\", or \"readwrite\". Applies to sandboxes created after the change; existing sandboxes' mounts are never retroactively changed.",
    "key": "skills.defaultMode",
    "source": "default",
    "type": "string",
    "value": "readonly"
  },
  …
  {
    "default": "shell",
    "description": "Built-in agent used for SSH auto-created sandboxes.",
    "env_var": "DOCKER_SANDBOXES_SSH_DEFAULT_AGENT",
    "key": "ssh.defaultAgent",
    "requires_restart": true,
    "source": "default",
    "type": "string",
    "value": "shell"
  },
  …
]
Cut to kit.trustedSigners, proxy, proxy.sandbox, skills.defaultMode, and ssh.defaultAgent, in the order the file has them.

The proxy record shows the shape of a value. It takes a URL, a PAC source, system, or direct, and an empty string means automatic: HTTP(S)_PROXY if set, otherwise the host OS proxy. proxy.sandbox narrows that to sandbox egress only, and it is the one with an environment alias, DOCKER_SANDBOXES_PROXY.

Precedence and restart

"Environment variables take precedence over user overrides" help-sbx sbx settings set, so an exported DOCKER_SANDBOXES_PROXY beats sbx settings set proxy.sandbox. Above both sits the organization: "Administrator constraints apply to saved overrides. A conflicting value is rejected" help-sbx sbx settings set. sbx settings unset KEY removes the override, and "Administrator policy remains in effect" help-sbx sbx settings unset. After that "the setting evaluates from its environment variable, remote default, or built-in default" help-sbx sbx settings unset, and the remote default is a fourth origin that the SOURCE column does not name.

"Most changes take effect within about five seconds" help-sbx sbx settings, and the rest need sbx daemon restart. The table marks them in its RESTART column, and its footer says who needs the restart.

the two notes under the settings tablecapture/out/02-settings.txttext
Some fields were truncated; use --no-trunc or --json to see them in full.

RESTART=yes: existing daemon-side consumers require `sbx daemon restart`. Supported CLI clients and new sandboxes use their current proxy settings immediately.
Cut to the two lines after the table.

Fifteen keys carry "requires_restart": true on the recording Mac: the four proxy* keys, the three no_proxy* keys, the six ssh.* keys, tls.allowNegativeSerial, and mcp.forceLocalGateway. The other sixteen apply within the five seconds. Some of them, such as skills.defaultMode and sandbox.disk.dockerVolume, say in their description that only sandboxes created after the change see the new value.

Where the CLI and the docs disagree

The docs settings page and the CLI list different keys, and the settings table prints only the keys the CLI returned. Four keys are in the CLI and not in the docs: ssh.autoCreate, ssh.defaultAgent with default shell, ssh.defaultTemplate, and ssh.workspaceRoot, as conflict C20 records. Four keys are in the docs and not in the CLI output: feature.model, feature.sandbox-gpu, feature.udp-egress, and diagnostics.autoUploadErrorCooldownInDays, as conflict C19 records. The last one has a documented default of 1 docs-sbx diagnostics.autoUploadErrorCooldownInDays. The capture ran sbx settings get only on skills.defaultMode and kit.allowedSources, so what get answers for a feature.* key is open.

One default disagrees. The docs tell you to run sbx settings set platform.allowExperimentalFeatures true before feature.model docs-sbx Enable model selection, which implies false. The capture prints true with source default, as conflict C18 records.

the experimental flag as the CLI reports itcapture/out/02-settings-experimental.jsonjson
{
  "default": true,
  "description": "Allow experimental features.",
  "env_var": "DOCKER_SANDBOXES_ALLOW_EXPERIMENTAL_FEATURES",
  "key": "platform.allowExperimentalFeatures",
  "source": "default",
  "type": "bool",
  "value": true
}
Nothing is cut.

The same gap covers commands. The model.providers description names sbx run --model <model> --provider <id>, and the docs show sbx run --name <SANDBOX_NAME> --model <MODEL_NAME> --provider <PROVIDER_ID> docs-sbx configuration/models, but sbx run --help in v0.47.0 lists neither flag. The secret help text names "mounts added later with sbx mount" help-sbx sbx secret set, and the help tree has no sbx mount page. sbx ssh proxy, sbx policy approval, and --usb are named in the sources that conflict C16 lists, and none of them has a help page either. This manual documents what --help prints, and the command table is that list.

The kit defaults

Six kit.* keys decide which kits a sandbox accepts, and all six print default as their source. kit.allowedSources is ["docker.io/"], and kit.allowLocalKits and kit.allowExtractedAgents are true. kit.requireSignature and kit.ignoreTransparencyLog are false, and kit.trustedSigners trusts any @docker.com identity through https://accounts.google.com. Kit signing shows what a signature check does with them.

Sources:help-sbx sbx settings, sbx settings list, sbx settings set, sbx settings unset, sbx secret set (research/sources/help-sbx.md); docs-sbx configuration/settings, configuration/models (research/sources/docs-sandboxes.md); conflicts C16, C18, C19, C20 (research/conflicts-register.md); research/sources/probes/sbx-settings-list.txt; capture/out/02-settings.json, 02-settings.txt, 02-settings-get.txt, 02-settings-experimental.json

Part 3

sbx policy, sbx secret, sbx mcp

Five layers isolate the agent, and each layer has its own commands: the hypervisor, the workspace, the policy and its proxy, the secret store, and the MCP gateway.

  1. 3.1sbx diagnose and the microVM boundary
  2. 3.2--clone and /run/sandbox/source
  3. 3.3sbx policy init, allow network, deny network, and check network
  4. 3.4sbx policy log and the proxy
  5. 3.5sbx secret set, sbx secret import, and sbx secret set-custom
  6. 3.6sbx mcp add, sbx mcp load, and --static-mcp
3.1

sbx diagnose and the microVM boundary

The agent runs as a container inside a guest kernel on the host hypervisor, with a private Docker Engine, and nothing it does reaches the host daemon.

You started sbx run claude on a repository, and the agent is running docker build inside. On the host, docker ps shows nothing new, and you want to know where those images went and what else the agent can reach. sbx diagnose is the first command to run, because it names every layer between the agent and your machine.

When you finish this section, you can read sbx diagnose, name the five layers between the agent and the host, and name the doors through them.

What sbx diagnose checks

sbx diagnose: a read-only check of the installation, the platform, the storage, and the connection to the daemon, with --output json|github-issue and --upload help-sbx sbx diagnose. On the capture Mac it ran 13 checks, and all passed:

the platform and connection groups of sbx diagnosecapture/out/02-diagnose.txttext
  Platform
  ✓ Virtualization — supported
      kern.hv_support is 1
  ✓ mkfs.erofs — found
      /opt/homebrew/Caskroom/sbx/0.47.0/Sbx.app/Contents/libexec/mkfs.erofs, default block size <n> bytes, guest kernel page size <n> bytes
…
  Connection
  ✓ Version match — v0.47.0
  ✓ Socket — responsive
  ✓ SSH client config — not configured
  ✓ Authentication — authenticated
…
  13 passed
The Installation and Storage groups are cut. The capture kit masks byte and block sizes as <n>.

kern.hv_support is 1 is the macOS flag for hardware virtualization. The guest runs on the host hypervisor: Hypervisor.framework on macOS, Windows Hypervisor Platform on Windows, and KVM on Linux (conflict C3). The mkfs.erofs line names the guest root filesystem format and its page size, which getconf PAGESIZE inside reports as 16384 (conflict C45). The last line is the sign-in check. sbx runs without Docker Desktop, and it still refuses to create a sandbox without a Docker account (conflict C15).

The FAQ gives Docker's reasons: "Tie sandboxes to a real person" and "Authenticate against Docker infrastructure" docs-sbx FAQ. It also lists the product's own egress hosts, starting with login.docker.com.

Five layers, from the kernel up

Inside m101-demo, the capture ran uname, id, docker version, and mount:

the guest seen from inside m101-democapture/out/04-guest.txttext
Linux m101-demo 7.0.14 #1 SMP PREEMPT Mon Sep 21 06:45:23 UTC 2026 aarch64 GNU/Linux
…
PRETTY_NAME="Ubuntu 26.04.1 LTS"
…
uid=1000(agent) gid=1000(agent) groups=1000(agent),27(sudo),1001(docker)
16384
…
Server: Docker Engine - Community
 Engine:
  Version:          29.8.1
…
 containerd:
  Version:          v2.3.5
…
bind-<id> on /etc/resolv.conf type virtiofs (ro,relatime)
host on $CAPTURE/fixtures/repo type virtiofs (rw,nosuid,nodev,relatime)
The os-release block, the Docker client block, the df output, and /etc/hosts are cut.

The docs name five isolation layers: hypervisor, network, Docker Engine, workspace, and credential docs-sbx Isolation layers. The listing shows four of them from inside. The guest kernel is 7.0.14 on aarch64 with 16 KiB pages, on Ubuntu 26.04.1. A build that assumes 4 KiB pages fails here, as one Docker Captain reported on 2026-05-26 blog 2026-05-26 B16. The agent is user agent, uid 1000, in the sudo and docker groups, and the docs place the control elsewhere:

Docker Engine 29.8.1 and containerd v2.3.5 in the listing belong to the VM, and so does every image the agent builds. The docs state the consequence in one sentence: "The agent has no path to your host Docker daemon." docs-sbx Docker Engine isolation

The host side has no Docker API for the sandbox either. Kevin Wittek said on 2026-04-23 that the Moby API is not offered from the host talk 2026-04-23 T06. REST clients that expect it do not work against a sandbox, and sbx exec is the way in, as sbx exec, cp, and ports shows.

VMM: the virtual machine monitor that starts the guest. Docker wrote its own and said on Hacker News on 2026-08-10 that it is not Firecracker (conflict C4). The claim that it builds on libkrun stays unverified. The names the product exposes are few. sandboxd is the daemon, whose socket sbx daemon status prints. containerd runs inside the guest, EROFS appears in the diagnose line, virtiofs in the mount table, and nerdbox in the release assets.

The doors through the boundary

Kevin Wittek named four entry points into a sandbox on 2026-09-04: bind mounts, network, secret injection, and MCP talk 2026-09-04 T02. The capture shows each as one line in the guest. The workspace is host on $CAPTURE/fixtures/repo type virtiofs (rw,...), a mount at the same path as on the host. /etc/resolv.conf is a second, read-only virtiofs bind from the host, so the resolver is the host's. The network door is HTTPS_PROXY=http://gateway.docker.internal:3128 in the environment (capture/out/04-env.txt). The secret door is ANTHROPIC_API_KEY=proxy-managed, a sentinel.

The MCP door is MCP_GATEWAY_URL=http://mcp-gateway.docker.internal/mcp. A fifth line, SSH_AUTH_SOCK=/run/ssh-agent.sock with SSH_AUTH_SOCK_GATEWAY=gateway.docker.internal:3129, is the forwarded SSH agent, a door that the talk did not name. The docs turn that forwarding on by default docs-sbx Credential isolation, and secrets returns to it. Figure 3.1 stacks the layers and draws the doors.

The VM has limits of its own. --memory defaults to 50% of host memory, clamped to 512 MiB to 32 GiB help-sbx sbx create. --cpus 0 means all host CPUs, at most 16 on Linux arm64 help-sbx sbx create. m101-policy got cpu 10 and memory 32 GiB on the capture Mac (capture/out/09-create.txt).

Fig. 3.1the layers of m101-demo and its doorslayers
The layers of m101-demo and its doors A host row with the sbx CLI, the sandboxd daemon, the keychain, and the forward proxy on gateway.docker.internal:3128. Below a dashed hypervisor line sits the guest: the Linux 7.0.14 aarch64 kernel with 16384-byte pages on Ubuntu 26.04.1, containerd v2.3.5, a private Docker Engine 29.8.1, the agent container running as uid 1000 with sudo and docker groups, and the workspace mounted through virtiofs at the same path. A right column names five doors through the boundary: network, secret, MCP, SSH agent, and bind mount, each with the guest line that opens it. HOST · MACOS 26.2 ARM64 · SBX 0.47.0 sbx CLI sandboxd sandboxd.sock keychain secret store forward proxy :3128 gateway.docker.internal hypervisor · kern.hv_support is 1 GUEST · M101-DEMO kernel Linux 7.0.14 aarch64 Ubuntu 26.04.1 LTS · getconf PAGESIZE 16384 containerd v2.3.5 runc 1.5.1 · erofs root · overlay / Docker Engine 29.8.1, private images and containers stay in the VM agent container uid=1000(agent) groups sudo, docker $CAPTURE/fixtures/repo virtiofs rw at the same path /etc/resolv.conf: virtiofs ro bind DOORS network HTTPS_PROXY :3128 secret proxy-managed sentinel MCP MCP_GATEWAY_URL SSH agent /run/ssh-agent.sock bind mount host path, rw
The agent sits on a private Docker Engine inside a guest kernel, and five doors cross the hypervisor line: mount, network, secret, MCP, and SSH agent. Read from the host row down. The dashed line is the hypervisor, and every box below it lives in the VM. Each door on the right names the guest line that opens it. From capture/out/04-guest.txt and 04-env.txt.

Sources:help-sbx sbx diagnose, sbx create, sbx daemon start (research/sources/help-sbx.md); docs-sbx Security model, Isolation layers, Default security posture, Architecture, FAQ (research/sources/docs-sandboxes.md); research/conflicts-register.md rows C3, C4, C15, C45; talk T02 (2026-09-04), T06 (2026-04-23), blog B16 (2026-05-26), HN01 (2026-08-10) from research/plan.md; capture/out/02-diagnose.txt, 02-diagnose.json, 02-daemon-status.txt, 04-guest.txt, 04-env.txt, 09-create.txt

3.2

--clone and /run/sandbox/source

Bind mode gives the agent your files, :ro and mountless modes give it less, and --clone gives it a private clone whose commits come back through a git remote.

You let an agent work on a repository overnight. In the morning git diff is clean, and a new file sits in .git/hooks/pre-commit, where no diff ever shows it. The three workspace modes decide how much of your tree the agent can write. With --clone it can write none of it.

When you finish this section, you can pick a workspace mode for a repository and fetch an agent's commits out of a clone-mode sandbox.

Three workspace modes

Direct mount: the workspace path mounted inside the VM at the same absolute path, read-write help-sbx sbx create claude. In m101-demo the agent's pwd is $CAPTURE/fixtures/repo, the same string as on the host, and git log lists the host commits c7dfd56 second and 086df68 first (capture/out/04-workspace.txt). The mount is virtiofs, as the microVM boundary showed, and the file synchronization of the legacy plugin is gone (conflict C5). Extra paths follow the first one, and :ro makes one read-only: "a read-only argument may name a single file, which holds that one path out of reach inside a workspace the sandbox can otherwise write" help-sbx sbx create claude.

Mountless: no path on sbx create, so the VM has no host bind mount. The agent works in the template's working directory, /home/agent/workspace for Docker's templates docs-sbx Workspace isolation. m101-policy was created that way, and sbx create printed workspace none · no workspace bind mount (capture/out/09-create.txt). sbx run without a path mounts the current directory instead, which sbx run, create, stop, and rm warns about.

Direct mode needs a review step. The docs list what the agent can edit, including Git hooks, CI configuration, package.json scripts, and .claude/settings.json, and they warn that hooks "don't appear in git diff output" docs-sbx Workspace isolation. A Docker Captain described this covert channel on 2026-05-26, and it is the reason clone mode exists blog 2026-05-26 B16.

Clone mode

--clone: a create-time flag that makes the agent "Run the agent on a private in-container clone of the host Git repository (mounted read-only)" help-sbx sbx create. It replaced --branch in v0.31.0, and --branch now fails with --branch is no longer supported; use --clone instead rel-sbx v0.31.0 (conflict C6). The flag is a no-op when you re-attach to an existing clone-mode sandbox help-sbx sbx run. The capture created m101-clone with it:

creating a clone-mode sandboxcapture/out/08-clone-create.txttext
$ sbx create --clone shell $CAPTURE/fixtures/repo-clone --name m101-clone
✓ Git repository detected: $CAPTURE/fixtures/repo-clone
…
  Git daemon: git://127.0.0.1:<port>/repo-clone
  Remote: sandbox-m101-clone
✓ Created sandbox m101-clone
  mount  $CAPTURE/fixtures/repo-clone → /run/sandbox/source (ro, source)
…
SANDBOX      AGENT   STATUS    PORTS                        WORKSPACE
m101-clone   shell   running   127.0.0.1:<port>->9418/tcp4   $CAPTURE/fixtures/repo-clone
The image pull and the second sbx ls row are cut.

Inside, three facts stand out (capture/out/08-clone-inside.txt). The clone sits at the same path, $CAPTURE/fixtures/repo-clone, on /dev/vde, an ext4 volume, and its origin is /run/sandbox/source. The source mount is host on /run/sandbox/source type virtiofs (ro,nosuid,nodev,relatime), and touch /run/sandbox/source/x fails with Read-only file system. A commit made inside, 81e11df from sandbox, cannot be pushed to origin either: git push ends with remote unpack failed: unable to create temporary object directory (capture/out/08-clone-commit.txt). The docs name the limit: clone mode "protects your host repository from modification" docs-sbx Clone mode, and inspection stays open, so an untracked .env under the repository is readable inside.

Getting commits back

The git-daemon in the VM listens on 9418, and sbx publishes it on a loopback port. The CLI then writes a remote into the host repository's .git/config:

the sandbox remote on the host, before and after a fetchcapture/out/08-clone-host.txttext
$ git -C $CAPTURE/fixtures/repo-clone remote -v
sandbox-m101-clone	git://127.0.0.1:<port>/repo-clone (fetch)
sandbox-m101-clone	git://127.0.0.1:<port>/repo-clone (push)
…
file:.git/config	remote.sandbox-m101-clone.fetch=+refs/heads/*:refs/remotes/sandbox-m101-clone/*
file:.git/config	remote.sandbox-m101-clone.fetch=+refs/heads/*:refs/sandboxes/m101-clone/*
…
$ git -C $CAPTURE/fixtures/repo-clone fetch sandbox-m101-clone
…
   c7dfd56..81e11df  main       -> sandbox-m101-clone/main
   c7dfd56..81e11df  main       -> refs/sandboxes/m101-clone/main
…
81e11df from sandbox
c7dfd56 second
086df68 first
The core.* config lines, the ls-remote output, and the fetch warning are cut.

Two fetch refspecs mean one fetch updates two places: refs/remotes/sandbox-m101-clone/main and refs/sandboxes/m101-clone/main. sbx rm removes the remote and the daemon and prints the recovery command git branch <local-name> refs/sandboxes/m101-clone/<branch>. In the capture, refs/sandboxes/m101-clone/main survived the removal (capture/out/08-clone-rm.txt). sbx stop stops the daemon, and a restart assigns a new port and rewrites the remote URL docs-sbx Use Git with sandboxes. The /root/.config/git/attributes warning in the fetch output comes from the daemon's user inside the VM and changes nothing. sbx kit add keeps the clone "via a named workspace volume" help-sbx sbx kit add, which is the ext4 device above, and sbx rm "cleans up any Git worktrees" help-sbx sbx rm.

Figure 3.2 follows one commit from the clone to the host refs.

Fig. 3.2one commit from the clone to the hostflow
One commit from the clone to the host On the left, the host repository at $CAPTURE/fixtures/repo-clone with main at c7dfd56. A read-only virtiofs mount carries it into the sandbox m101-clone at /run/sandbox/source. The agent works in a private clone on an ext4 volume whose origin is that mount, and commits 81e11df. A git push to origin is refused by the read-only mount. git-daemon serves the clone on port 9418, published on a loopback port, and the host remote sandbox-m101-clone fetches it into refs/remotes/sandbox-m101-clone/main and refs/sandboxes/m101-clone/main. After sbx rm, the remote is gone and the refs/sandboxes entry survives. HOST · $CAPTURE/FIXTURES/REPO-CLONE SANDBOX M101-CLONE .git and working tree main at c7dfd56 second /run/sandbox/source virtiofs ro · Read-only file system bind mount, ro private clone, same path /dev/vde ext4 rw · origin = the mount 81e11df from sandbox git clone git push: unpacker error git-daemon :9418 published as 127.0.0.1:<port>->9418/tcp4 serves the clone remote sandbox-m101-clone git://127.0.0.1:<port>/repo-clone two fetch refspecs in .git/config git fetch sandbox-m101-clone after the fetch sandbox-m101-clone/main 81e11df refs/sandboxes/m101-clone/main 81e11df sbx rm: remote and daemon removed, refs/sandboxes/m101-clone/main survives
The host repository enters the VM read-only at /run/sandbox/source, the agent commits to a private clone, and git-daemon serves that clone back as a host remote. Read from the host repository across to the clone and back down to the host refs. Solid teal arrows are the fetch that writes two refs. The dashed rose arrow is the push that the read-only mount refuses. From capture/out/08-clone-inside.txt, 08-clone-commit.txt, and 08-clone-host.txt.

Sources:help-sbx sbx create, sbx create claude, sbx run, sbx rm, sbx kit add (research/sources/help-sbx.md); docs-sbx Isolation layers, Architecture, Use Git with sandboxes, Usage (research/sources/docs-sandboxes.md); rel-sbx v0.31.0 (research/sources/sbx-releases.md); research/conflicts-register.md rows C5, C6; blog B16 (2026-05-26) from research/plan.md; capture/out/04-workspace.txt, 08-clone-create.txt, 08-clone-inside.txt, 08-clone-commit.txt, 08-clone-host.txt, 08-clone-rm.txt, 09-create.txt

3.3

sbx policy init, allow network, deny network, and check network

A global preset plus allow and deny rules in two scopes decide every connection, deny wins, and check network asks the same authorizer without sending anything.

Your agent reports that npm install failed with a connection error, and the sandbox keeps no shell history to say why. The question is which rule decided. sbx policy answers it in two halves: ls for the rules that exist, and check network for the decision one host would get.

When you finish this section, you can initialize a policy, add a rule in the right scope, and predict the decision for any host.

The preset and the global policy

policy init: sets "the initial global policy, not a per-sandbox default" help-sbx sbx policy init. It runs once, before the first sandbox, with allow-all, balanced, or deny-all, and sbx policy reset or sbx daemon start --policy starts over help-sbx sbx daemon start. The capture host chose balanced on v0.47.0, and sbx policy ls then showed one policy, local-policy, with network: 194 allow (capture/out/03-policy-ls.txt). The wide JSON lists eight rules with created_via: default: six network groups and two filesystem rules that allow every path. The list changes between releases without a changelog (conflict C23), so this is the list as recorded on 2026-10-08:

two of the six balanced network groupscapture/out/03-policy-balanced.jsonjson
      "id": "default-ai-services",
      "name": "default-ai-services",
      "policy_id": "local-policy",
      "scope": "global",
      "applies_to": "all",
      "resource_type": "network",
      "decision": "allow",
      "resources": [
        "api.anthropic.com:443",
        "statsig.anthropic.com:443",
        "platform.claude.com:443",
…
        "**.openai.com:443",
…
      "id": "default-package-managers",
…
        "registry.npmjs.org:443",
…
        "pypi.org:443",
…
      "provenance": {
        "created_via": "default"
      },
      "actions": [
        "net:connect:tcp"
      ]
Each group is cut after its first resources. The other groups are default-code-and-containers, default-os-packages, default-cloud-infrastructure, and default-cert-validation.

Rule grammar and scope

Allow rule: a comma-separated list "of hostnames, domains, IP addresses, or CIDR prefixes" help-sbx sbx policy allow network. The forms are example.com, *.example.com, **.example.com, api?.example.com, api[12].example.com, example.com:443, [2001:db8::1]:443, a CIDR, and ** for every host. A lone * and an escaped glob are rejected help-sbx sbx policy allow network. An allow rule covers TCP unless --protocol says otherwise, and a deny rule covers TCP and UDP help-sbx sbx policy deny network. The capture shows both: the allow came back as (example.com [tcp]) and the deny as (example.com [tcp,udp]) (capture/out/09-allow.txt, 09-deny.txt). The deny also warned that UDP egress stays off until feature.udp-egress is on.

Two scopes have existed since v0.29.0 (conflict C41): global, the default, and local, which --sandbox scopes to one sandbox help-sbx sbx policy allow network. A rule added with --sandbox m101-policy printed Rule added to policy local (scope: sandbox:m101-policy): <uuid>, and sbx policy inspect shows its scope, layer, origin, and provenance:

the sandbox-scoped allow rulecapture/out/09-inspect-rule.jsonjson
      "id": "<uuid>",
      "name": "<uuid>",
      "policy_id": "<uuid>",
      "scope": "sandbox:m101-policy",
      "applies_to": "sandbox:m101-policy",
      "resource_type": "network",
      "decision": "allow",
      "resources": [
        "example.com"
      ],
      "origin": "scoped",
      "layer": "local",
      "status": "active",
      "editable": true,
      "sandbox_id": "m101-policy",
      "provenance": {
        "created_via": "added"
      },
      "actions": [
        "net:connect:tcp"
      ]
The outer policy object and the remove_command field are cut.

--deny-network HOST on create or run adds the same kind of per-sandbox deny at creation. The help says why that is safe under governance: "a local deny can only narrow, never widen, egress" help-sbx sbx create. A kit adds rules with created_via: provisioned, and sbx policy ls --source org on the capture host printed No policies match the selected filters. (capture/out/03-policy-org.txt).

Deny wins: "Deny rules take precedence over allow rules for the same hostname or CIDR. An allowed hostname isn't checked against CIDR rules for its resolved IP address." help-sbx sbx policy deny network

The CLI refuses a deny that conflicts with an allow in the same scope: deny: "example.com" conflicts with existing allow rule "<uuid>" (capture/out/09-deny-conflict.txt). The capture removed the allow with sbx policy rm network --sandbox m101-policy --id <uuid> --force before the deny was accepted. Under organization governance only organization allow rules grant access, while local and kit deny rules still apply docs-sbx Precedence, and org policies prints that table.

policy ls filters with --source local|org|kit, --decision, --type, --created-via default|added|provisioned|approval, --protocol, --wide for RULE_ID, and --json help-sbx sbx policy ls. A wide row carries METHOD and PATH columns. The docs fill them with --method and --path flags. The v0.47.0 help of sbx policy allow network lists neither flag, so this manual marks local HTTP rules as unverified. The proxy returns to them.

check network

policy check network: a read-only call that "evaluates the same daemon-side policy authorizer used by sandbox network enforcement" help-sbx sbx policy check. A host without a port is evaluated with port 443, and the command "evaluates network authorization, not HTTP method or path" help-sbx sbx policy check network. It exits 1 on a denial. The capture ran it on example.com in the m101-policy context before any rule, after the allow, and after the deny:

the explicit denial after the deny rulecapture/out/09-check-deny.jsonjson
{
  "action": "net:connect:tcp",
  "allowed": false,
  "context": "sandbox:m101-policy",
  "deny_kind": "explicit",
  "governance": {
    "active": false
  },
  "origin": "local",
  "reason": "Denied by local rule",
  "resource_type": "net:domain",
  "resource_value": "example.com:443",
  "rule": "local:<uuid>",
  "target": "example.com:443",
  "type": "network"
}
The implicit denial in 09-check-verbose.json has deny_kind implicit, no origin, no rule, and the reason No matching allow rule (default deny).

After the allow, the same call returned "allowed": true with no rule field (capture/out/09-check-allowed.json). The global check of api.anthropic.com did the same under "context": "global" (capture/out/03-policy-check-anthropic.json). The two denials differ in deny_kind: implicit names no rule, and explicit names local:<uuid>. An implicit denial is also what a sandbox turns into an approval request, which the next section shows. Figure 3.3 walks the request through the same order.

Fig. 3.3how one request to example.com:443 is decideddecision
How one request to example.com:443 is decided The authorizer walks five questions for op(action=net:connect:tcp, resource=net:domain:example.com:443) in the sandbox m101-policy. A matching deny rule ends in Denied by local rule with deny_kind explicit and rule local:<uuid>. Organization governance was inactive on the capture host, and when active only organization allow rules grant access. A sandbox-scoped allow or a global or kit allow returns allowed true, as the checks for example.com and api.anthropic.com show. When nothing matches, the result is No matching allow rule with deny_kind implicit, and the sandbox sees an approval request. THE REQUEST op(action=net:connect:tcp, resource=net:domain:example.com:443) · context sandbox:m101-policy a deny rule matches? any scope, any source Denied by local rule deny_kind: explicit · origin: local rule: local:<uuid> · 09-check-deny.json no org governance active? governance.active false on the capture host when true, only org allow rules grant local and kit allow rules go inactive a sandbox allow matches? scope sandbox:m101-policy allowed: true local <uuid> example.com [tcp] 09-check-allowed.json a global or kit allow matches? local-policy, kit:<name> allowed: true default-ai-services api.anthropic.com:443 03-policy-check-anthropic.json nothing matched default deny No matching allow rule (default deny) deny_kind: implicit · 09-check-verbose.json inside: Approval required for example.com:443
A matching deny ends the walk at once, an allow from any active scope admits the host, and a request that matches nothing is denied as implicit. Read top down. Each left box is one question the authorizer asks, and the right box is the answer the capture recorded for it. Dashed rose arrows are denials. From capture/out/09-check-deny.json, 09-check-allowed.json, 03-policy-check-anthropic.json, 09-check-verbose.json, and 09-blocked.txt.

Sources:help-sbx sbx policy init, sbx daemon start, sbx policy allow network, sbx policy deny network, sbx policy ls, sbx policy inspect, sbx policy rm network, sbx policy check, sbx policy check network, sbx create (research/sources/help-sbx.md); docs-sbx Policy concepts, Local policy, Monitoring policies (research/sources/docs-sandboxes.md); research/conflicts-register.md rows C23, C41; capture/out/03-policy-init.txt, 03-policy-ls.txt, 03-policy-org.txt, 03-policy-balanced.json, 03-policy-check-anthropic.json, 09-allow.txt, 09-deny.txt, 09-deny-conflict.txt, 09-inspect-rule.json, 09-rm-allow.txt, 09-check-verbose.json, 09-check-allowed.json, 09-check-deny.json, 12-policy-kit.txt

3.4

sbx policy log and the proxy

Every connection leaves through a host proxy, which answers a blocked HTTPS request with its own certificate and writes one policy log row per host with the matching rule.

The agent says that https://example.com timed out. It did not time out: the proxy answered it in 90 bytes. Every connection from a sandbox passes a proxy on the host. sbx policy log keeps one row per host with the decision, the proxy path, and the rule.

When you finish this section, you can read a policy log row, name its proxy path, and tell a blocked host from a slow one.

Two proxies and one CA

Inside m101-demo, the environment carries the proxy address and its certificate:

the proxy lines in the environment of m101-democapture/out/04-env.txttext
HTTPS_PROXY=http://gateway.docker.internal:3128
HTTP_PROXY=http://gateway.docker.internal:3128
…
NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
NODE_USE_ENV_PROXY=1
NO_PROXY=localhost,127.0.0.1,::1,gateway.docker.internal
…
PROXY_CA_CERT_B64=<base64>
…
REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
…
SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
Every line that is not about the proxy or its CA is cut. The CA value is masked as <base64>.

The address is gateway.docker.internal:3128, not the host.docker.internal:3128 of the legacy plugin (conflict C26). The docs describe two paths: "Agents use a forward proxy for HTTP and HTTPS; other TCP traffic is forwarded transparently. Both paths enforce network access policies." docs-sbx Networking

The first prototype was only an environment variable, and an agent bypassed it with no_proxy, as Kevin Wittek said on 2026-01-14 talk 2026-01-14 T01. The capture sent a request to gateway.docker.internal:18080, a name in NO_PROXY, so curl skipped the forward proxy and opened a plain TCP connection. The transparent path caught that connection, and the log recorded it as transparent and blocked (capture/out/10-policy-log.json).

TLS interception: the proxy's own certificate authority, carried as PROXY_CA_CERT_B64 and merged into /etc/ssl/certs/ca-certificates.crt rel-sbx v0.35.0. The capture shows where it is used:

a blocked HTTPS request, with and without headerscapture/out/09-blocked.txttext
$ sbx exec m101-policy sh -c 'curl -sS --max-time 10 -I https://example.com; echo exit=$?'
HTTP/1.1 200 OK

HTTP/1.1 403 Forbidden
Content-Length: 90
Content-Type: text/plain

exit=0
[exit 0]

$ sbx exec m101-policy sh -c 'curl -sS --max-time 10 https://example.com; echo; echo exit=$?'
Approval required for example.com:443.

Review and respond with:
  sbx policy approval ls

exit=0
[exit 0]

HTTP/1.1 200 OK is the proxy accepting the CONNECT. The 403 Forbidden that follows arrived inside the TLS session, under a certificate the sandbox trusts, and its 90-byte body is the approval message. After sbx policy allow network --sandbox m101-policy example.com, the same request got HTTP/1.0 200 Connection established and then HTTP/2 200 with server: cloudflare (capture/out/09-allowed.txt).

The log classed that one as forward-bypass, a tunnel without inspection and without credential injection docs-sbx Monitoring policies. So the ruling for C26 reads: the proxy terminates TLS only when it has to speak. That is a blocked host or a host with a bound credential, and it tunnels the rest. The v0.47.0 notes name both paths, a "non-MITM CONNECT tunnel" and "the transparent proxy's late handshake check" rel-sbx v0.47.0.

Reading policy log

policy log: shows "which hosts were allowed or blocked by the proxy, along with the matching rule, proxy type, and request count" help-sbx sbx policy log. It takes [SANDBOX], --json, --limit, and --type, and the help admits that "filesystem logs are not supported yet" (conflict C28). The JSON has two arrays:

the log of m101-policy after the deny rulecapture/out/09-policy-log-deny.jsonjson
{
  "blocked_hosts": [
    {
      "host": "example.com:443",
      "vm_name": "m101-policy",
      "proxy_type": "forward",
      "rule": "denied: rule \"local:<uuid>\" matched op(action=net:connect:tcp, resource=net:domain:example.com:443)",
      "last_seen": "<ts>",
      "since": "<ts>",
      "count_since": "<n>",
      "reason": "Denied by local rule"
    },
…
  "allowed_hosts": [
…
    {
      "host": "example.com:443",
      "vm_name": "m101-policy",
      "proxy_type": "forward-bypass",
      "rule": "",
      "last_seen": "<ts>",
      "since": "<ts>",
      "count_since": "<n>"
    },
The second blocked row, the download.docker.com row, and the ports.ubuntu.com row are cut.

Each row has host, vm_name, proxy_type, rule, reason, last_seen, since, and count_since. A blocked row names the operation, op(action=net:connect:tcp, resource=net:domain:example.com:443), and either no applicable policies or the rule that matched. An allowed row in this capture carries an empty rule, so the log says that a host passed, and check network says why. PROXY takes five values, forward, forward-bypass, transparent, network, and browser-open docs-sbx Monitoring policies. Figure 3.4 animates both requests.

The 403 body names sbx policy approval ls. The docs describe approval ls, inspect, and respond. Under balanced and deny-all, a request no rule matches "asks for your approval instead of being denied outright" docs-sbx Local policy. The v0.47.0 help tree has no sbx policy approval command, while sbx policy ls --created-via approval exists (conflict C16).

What a host rule cannot see

A deny covers more than TCP (conflict C27). A 2026-05-26 post said UDP and ICMP are blocked and cannot be allowed blog 2026-05-26 B16. Since v0.33.0 a sandboxed process cannot resolve a domain that policy denies, loopback names excepted, and outgoing ICMP stays blocked rel-sbx v0.33.0. Since v0.45.0 UDP follows policy behind the experimental setting feature.udp-egress, and DNS resolution stops when no rule permits it rel-sbx v0.45.0. The ruling: a deny covers TCP, UDP, and the name lookup itself, and ICMP cannot be allowed. The guest's /etc/resolv.conf is a read-only bind from the host.

A host allow permits every request to that host, with any method, path, and body. The balanced preset allows github.com:443 and **.github.com:443 (capture/out/03-policy-balanced.json). Docker staff said on 2026-04-07 and on 2026-08-10 that an issue body or a gist on an allowed host is not blocked talk 2026-04-07 T05.

The product's answer is the HTTP rule. A kit's network-policy@2 entry can deny hosts: [api.github.com] with methods: [DELETE] kitcap network-policy@2. A network allow is a ceiling that HTTP rules carve into docs-sbx HTTP method and path, and neither check network nor policy log evaluates a method or a path docs-sbx Local policy. Figure 3.5 puts the two rules side by side.

Upstream proxies are a separate setting: proxy, proxy.sandbox, proxy.daemon, and the no_proxy family, with SOCKS5 since v0.35.0 rel-sbx v0.35.0. The settings table lists the keys.

Fig. 3.4one blocked and one allowed request to example.comsequence
One blocked and one allowed request to example.com Five lifelines: curl inside m101-policy, the forward proxy on gateway.docker.internal:3128, sandboxd with its authorizer, the policy store, and example.com. The first CONNECT is checked by sandboxd, finds no applicable policy, and the proxy answers 403 Forbidden inside the TLS session with a 90-byte approval message, then logs the host as blocked with proxy type forward. An allow rule for example.com is written in the sandbox scope. The second CONNECT is allowed as forward-bypass, the proxy opens a tunnel without inspection, example.com answers HTTP/2 200, and the host is logged as allowed. curl inside proxy :3128 sandboxd policy store example.com 1 CONNECT example.com:443 HTTPS_PROXY in the environment 2 op(net:connect:tcp, example.com:443) 3 match rules for sandbox:m101-policy 4 no applicable policies 5 No matching allow rule (default deny) 6 HTTP/1.1 403 Forbidden, 90 bytes inside TLS, under the proxy CA 7 log: blocked example.com:443 forward 8 policy allow network --sandbox m101-policy rule <uuid> example.com [tcp] 9 CONNECT example.com:443 10 allowed, forward-bypass 11 TLS tunnel, no inspection HTTP/1.0 200 Connection established 12 HTTP/2 200, server: cloudflare 13 log: allowed example.com:443 forward-bypass
The first CONNECT is denied inside TLS by the proxy itself, one allow rule later the same CONNECT becomes a forward-bypass tunnel, and both leave a log row. Read top down. Lifelines are curl inside m101-policy, the forward proxy, sandboxd with its authorizer, the policy store, and example.com. Dashed rose arrows are denials, and solid teal arrows are writes to rules and the log. From capture/out/09-blocked.txt, 09-policy-log.json, 09-allow.txt, 09-allowed.txt, and 09-policy-log-after.json.
Fig. 3.5a host allow and a kit HTTP rule on api.github.comcomparison
A host allow and a kit HTTP rule on api.github.com Two columns. On the left, the default-code-and-containers group of the balanced preset allows github.com:443 and every subdomain, so a POST to api.github.com /repos/o/r/issues carrying data in its body is allowed, because a host rule reads host and port only. On the right, a network-policy@2 deny entry with hosts api.github.com and methods DELETE blocks DELETE /repos/o/r while the same POST still passes, because deny wins only where the method matches. A · HOST ALLOW, BALANCED PRESET B · KIT HTTP RULE, NETWORK-POLICY@2 default-code-and-containers github.com:443 · **.github.com:443 allow · net:connect:tcp POST api.github.com /repos/o/r/issues body: your data allowed: any method, path, body a host rule reads host and port only runtime.deny entry hosts: [api.github.com] methods: [DELETE] DELETE api.github.com /repos/o/r blocked by the HTTP rule POST /repos/o/r/issues still passes deny wins only where it matches
A host allow passes any request to github.com, body included, while a network-policy@2 entry denies one method on one host and leaves the rest open. Left, the default-code-and-containers group of the balanced preset and a POST that it admits. Right, the deny entry from the kit specification and the DELETE that it stops. From capture/out/03-policy-balanced.json and research/sources/kit-capabilities.md.

Sources:help-sbx sbx policy log, sbx policy allow network (research/sources/help-sbx.md); docs-sbx Architecture, Isolation layers, Default security posture, Monitoring policies, Local policy, Network access policies, Policy concepts, Upstream proxy (research/sources/docs-sandboxes.md); kitcap network-policy@2 (research/sources/kit-capabilities.md); rel-sbx v0.33.0, v0.35.0, v0.45.0, v0.47.0 (research/sources/sbx-releases.md); research/conflicts-register.md rows C16, C26, C27, C28; talk T01 (2026-01-14), T05 (2026-04-07), blog B16 (2026-05-26), HN01 (2026-08-10) from research/plan.md; capture/out/04-env.txt, 09-blocked.txt, 09-allowed.txt, 09-allow.txt, 09-policy-log.json, 09-policy-log-after.json, 09-policy-log-deny.json, 09-policy-log.txt, 10-policy-log.json, 03-policy-balanced.json

3.5

sbx secret set, sbx secret import, and sbx secret set-custom

A secret never enters the VM: the agent sees a sentinel, the host keychain holds the value, and the proxy swaps it into requests to the bound host.

You exported ANTHROPIC_API_KEY in ~/.zshrc, as a 2026 tutorial said, and the agent inside the sandbox still has no key. Since v0.35.0 the host environment is not read rel-sbx v0.35.0 (conflict C7). -e KEY passes a plain variable, not a secret, and the three sbx secret commands are the only way in.

When you finish this section, you can store a service secret, bind a custom one to a host, and read what the proxy sent.

Service secrets

Service secret: a value stored under one of 13 service names, anthropic, copilot, cursor, devin, droid, github, google, groq, mistral, nebius, openai, openrouter, xai help-sbx sbx secret set, two more than the docs table (conflict C21). The value comes from -t, from stdin, from --command, or from --ref with a 1Password op:// reference or an AWS Secrets Manager ARN. --oauth is the fifth source, and locally it is "openai/global only" help-sbx sbx secret set (conflict C22). Anthropic OAuth comes from the agent's own login instead. A command helper runs "from a fresh temporary directory on the host" help-sbx sbx secret set since v0.46.0, so a relative helper path no longer resolves (conflict C38). --refresh sets the cache time, default 55 minutes.

The scope is global unless --sandbox narrows it. secret ls filters with -g, --sandbox, --service, and --json, and secret rm takes --all, --placeholder, and --registry help-sbx sbx secret rm. The store is the macOS Keychain, the Windows Credential Manager, or the Linux Secret Service docs-sbx Where secrets are stored. Without a keyring, Linux uses a file under ~/.config/com.docker.sandboxes at mode 0700. Kit approvals live apart from the values, in ~/.config/sbx/credentials.yaml docs-sbx Credential bindings.

secret import: reads the host environment once, offers each variable with a last-4 preview, and takes --all, --force, and --dry-run help-sbx sbx secret import. A service that already holds an OAuth token is skipped. On the capture host nothing was exported:

an import with nothing to importcapture/out/10-import-dry-run.txttext
$ sbx secret import --dry-run
No credential env vars detected on the host. Set e.g. OPENAI_API_KEY in your shell and re-run, or use `sbx secret set` to enter a value directly.

Registry credentials are a third kind, "host-only by default", and reach a sandbox only with --all-sandboxes or --sandbox help-sbx sbx secret set.

What the agent sees

Every service variable inside a sandbox is a sentinel, and the GitHub one is shaped like a token:

sentinels in the environment of m101-democapture/out/04-env.txttext
ANTHROPIC_API_KEY=proxy-managed
…
GH_TOKEN=gho_sbxproxymanaged000000000000000000000
…
OPENAI_API_KEY=proxy-managed
…
SBX_CRED_ANTHROPIC_MODE=none
SBX_CRED_GITHUB_MODE=none
Every line that is not a sentinel or a credential mode is cut.

Three formats exist in v0.47.0 (conflict C35): proxy-managed for the eight provider keys, gho_sbxproxymanaged000000000000000000000 for GH_TOKEN, and sbx-cs-<rand> for custom secrets. The GitHub one is lower case, where a 2026-09-04 demo showed GHO_SBX_PROXY_MANAGED talk 2026-09-04 T02. No GitHub request was captured: that needs a real token.

Custom secrets and the receiver

set-custom: an experimental secret keyed to --host targets and an --env name instead of a service help-sbx sbx secret set-custom. The value comes from --value, --command, or --ref, and --placeholder sk-{rand} sets a chosen prefix. The --header and --format flags apply with --cloud only, as the help states for each (conflict C34). The capture bound M101_RECV_KEY to host.docker.internal and localhost, and started a receiver on the host at 127.0.0.1:18080. It allowed localhost:18080 for m101-secret and found M101_RECV_KEY=sbx-cs-<rand> inside (capture/out/10-env-sentinel.txt). Then it sent three plain HTTP requests:

what the receiver on the host loggedcapture/out/10-receiver.logtext
GET /from-variable HTTP/1.1
Host: localhost:18080
User-Agent: curl/8.18.0
Accept: */*
Authorization: Bearer m101-dummy-receiver-0000
X-Demo: m101-dummy-receiver-0000
Accept-Encoding: gzip
…
GET /no-scheme HTTP/1.1
Host: localhost:18080
…
Authorization: m101-dummy-receiver-0000
The second request, sent with the placeholder typed as a literal, is cut. It matched the first.

The receiver saw the real value in every place the placeholder appeared: inside Bearer, in X-Demo, and as the whole Authorization value. Its Host was localhost:18080: the proxy maps host.docker.internal to localhost docs-sbx Accessing host services from a sandbox. So the swap happens on plain HTTP, and it is a substring replacement. That settles conflict C34 for custom secrets. The Hacker News claim that the secret must be the whole header describes service secrets, where the kit declares the header and its format docs-sbx Services declared by kits.

The fourth request went to gateway.docker.internal:18080, a name in NO_PROXY, so it bypassed the forward proxy. The transparent proxy blocked it with Empty reply from server and swapped nothing (capture/out/10-swap-curl.txt). Figure 3.6 animates the four steps. The existing sandbox m101-demo did not receive M101_RECV_KEY (capture/out/10-env-existing.txt), so a custom variable reaches new sandboxes only. Removal is immediate: sbx secret rm --placeholder sbx-cs-<rand> -f printed Applied secret updates for <n> running sandbox(es) (capture/out/10-secret-rm.txt).

The SSH agent

One credential path does cross the boundary. "SSH agent forwarding is enabled by default" docs-sbx Credential isolation, and the guest shows SSH_AUTH_SOCK=/run/ssh-agent.sock with SSH_AUTH_SOCK_GATEWAY=gateway.docker.internal:3129 (capture/out/04-env.txt). Keys stay on the host, and any process inside can ask that agent to sign. ssh.agentForwardingEnabled in sbx settings list turns it off, followed by sbx daemon restart docs-sbx SSH agent. MCP secrets stay on the host too, under mcp:<server>:client_secret, as sbx mcp add explains.

Fig. 3.6one custom secret from the keychain to the receiversequence
One custom secret from the keychain to the receiver Five lifelines: the sbx CLI, the secret store, the sandbox m101-secret, the forward proxy on gateway.docker.internal:3128, and the receiver on 127.0.0.1:18080. The CLI stores M101_RECV_KEY for host.docker.internal and localhost and gets the placeholder sbx-cs-<rand>, which the new sandbox carries in its environment. curl inside sends the placeholder as a Bearer token to host.docker.internal:18080. The proxy reads the stored value and forwards the request to localhost:18080 with the real value, and the receiver answers 204. A direct connection to gateway.docker.internal:18080 bypasses the forward proxy and is blocked by the transparent proxy with no swap. Removing the secret updates running sandboxes at once. sbx CLI secret store m101-secret proxy :3128 receiver 1 set-custom --env M101_RECV_KEY hosts host.docker.internal, localhost 2 placeholder sbx-cs-<rand> 3 create m101-secret M101_RECV_KEY=sbx-cs-<rand> inside 4 GET host.docker.internal:18080/from-variable Authorization: Bearer sbx-cs-<rand> 5 value for the bound host 6 m101-dummy-receiver-0000 7 forward, Host: localhost:18080 Bearer m101-dummy-receiver-0000 8 204 9 direct to gateway.docker.internal:18080 NO_PROXY, transparent proxy: blocked, no swap 10 secret rm --placeholder sbx-cs-<rand> Applied secret updates for running sandboxes
The sandbox only ever holds sbx-cs-<rand>, the forward proxy swaps it for the stored value on the bound host, and a direct connection gets no swap. Read top down. Lifelines are the sbx CLI, the secret store, m101-secret, the forward proxy, and the receiver on 127.0.0.1:18080. Solid teal arrows are writes to the store. The dashed rose arrow is the direct connection that the transparent proxy blocks. From capture/out/10-set-custom.txt, 10-env-sentinel.txt, 10-swap-curl.txt, and 10-receiver.log.

Sources:help-sbx sbx secret, sbx secret set, sbx secret import, sbx secret ls, sbx secret rm, sbx secret set-custom (research/sources/help-sbx.md); docs-sbx Manage credentials, Isolation layers, Usage (research/sources/docs-sandboxes.md); rel-sbx v0.35.0, v0.46.0 (research/sources/sbx-releases.md); research/conflicts-register.md rows C7, C21, C22, C34, C35, C38; talk T02 (2026-09-04), HN01 (2026-08-10) from research/plan.md; capture/out/04-env.txt, 10-set-custom.txt, 10-secret-ls.json, 10-env-existing.txt, 10-secret-create.txt, 10-env-sentinel.txt, 10-swap-curl.txt, 10-receiver.log, 10-policy-log.json, 10-placeholder.txt, 10-import-dry-run.txt, 10-secret-rm.txt

3.6

sbx mcp add, sbx mcp load, and --static-mcp

MCP servers are registered on the host, served to the sandbox through one gateway endpoint, and either fixed at creation or loaded live with a tools/list_changed notice.

You want the agent to read documentation through an MCP server, and you do not want that server's token inside the VM. sbx mcp keeps the registration, the OAuth tokens, and any local server process on the host, and gives the sandbox one URL.

When you finish this section, you can register a server, pick static or dynamic mode, and load a server into a running sandbox.

Register on the host

sbx mcp add: "Register an MCP server by name. The server is validated and its specification is stored for use with sbx create/run --static-mcp." help-sbx sbx mcp add

--url takes four forms: a remote endpoint, a community-registry URL, a server.json or server.yaml manifest URL, and a dhi.io/ image reference. Other image references are rejected, and --local runs a registry OCI server on the host with docker run help-sbx sbx mcp add. The capture registered one remote server and listed it:

the registered server and its gatewaycapture/out/11-mcp-ls.jsonjson
{
  "gateway": {
    "name": "LOCAL",
    "local": true,
    "operator": "managed by you",
    "decision": "local",
    "signed_in_as": "<docker-user>"
  },
  "servers": [
    {
      "name": "m101-deepwiki",
      "transport": "remote http",
      "status": "ready",
      "type": "remote"
    }
  ]
}

The help's own registry example did not resolve on 2026-10-08: the add of https://registry.modelcontextprotocol.io/v0/servers/fetch-mcp/versions/latest failed with registry returned status 404 (capture/out/11-mcp-add-registry.txt). --command is the other input, and it runs on the host: "The process runs with your host user's full permissions" help-sbx sbx mcp add. A local server that starts a container uses host Docker, outside the engine boundary of the microVM docs-sbx Local stdio server.

OAuth metadata is discovered through RFC 9728 and RFC 8414, or supplied by hand with --oauth-authorization-server. --client-id names a pre-registered client, and dynamic registration follows RFC 7591. --scope records default scopes with a documented precedence, --resource sets the RFC 8707 indicator, and --callback-port pins the listener help-sbx sbx mcp add. Tokens stay on the host, and sbx mcp auth [server|--all], auth status, and auth rm manage them help-sbx sbx mcp auth.

The deepwiki server needs none: sbx mcp inspect reports requires_oauth: false, and auth status --all --json printed [], so no OAuth flow was captured. A confidential client's secret and any --header 'Name: ${placeholder}' value come from the secret store as mcp:<server>:client_secret and mcp:<server>:<placeholder>. A header-bearing registration is rejected on the hosted gateway help-sbx sbx mcp add.

One gateway per sandbox

MCP gateway: a host-side endpoint that "brokers access to registered MCP servers" docs-sbx MCP gateway. Inside m101-mcp the environment holds MCP_GATEWAY_URL=http://mcp-gateway.docker.internal/mcp and MCP_SENTINEL_TOKEN_NAME=proxy-managed (capture/out/11-static-inside.txt). The docs list the agents that read that URL at start: Claude Code, Codex, Devin, Gemini, Kiro, and OpenCode docs-sbx Prerequisites. Docker Agent is absent from that list. A plain shell sandbox reached the gateway with curl all the same (capture/fixtures/mcp-probe.sh):

the initialize answer of the gateway inside m101-mcpcapture/out/11-gateway-initialize.httphttp
HTTP/1.1 200 OK
…
Content-Type: text/event-stream
…
Mcp-Session-Id: <session>
…
event: message
id: <event-id>
data: {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"logging":{},"prompts":{"listChanged":true},"resources":{"listChanged":true},"tools":{"listChanged":true}},"instructions":"### m101-deepwiki…"protocolVersion":"2025-11-25","serverInfo":{"name":"mcp-gateway-m101-mcp","version":"0.1.0"}}}
Three headers and the instructions text are cut.

The gateway names itself mcp-gateway-m101-mcp, one per sandbox, answers protocol version 2025-11-25, and declares tools.listChanged: true. sbx mcp ls adds a GATEWAY column, LOCAL, managed by you, with signed_in_as in the JSON help-sbx sbx mcp ls. mcp.forceLocalGateway, default false, and SBX_MCP_URL=none select the local data plane when an account would otherwise use the hosted gateway docs-sbx mcp.forceLocalGateway.

The docs separate it from the Desktop product: "You don't need the Docker Desktop MCP Toolkit to use sbx mcp" docs-sbx MCP gateway. Network policy does not apply to a server the host registered, as Kevin Wittek said on 2026-09-04 talk 2026-09-04 T02. A server the agent starts inside the VM is subject to it. DMR, Compose models, and the MCP gateway service covers the Toolkit gateway.

Static and dynamic

Static mode: --static-mcp a,b at create or run fixes the set once at creation help-sbx sbx create. The static sandbox exposed the three deepwiki tools plus code-mode and mcp-exec, and no discovery tool. The dynamic sandbox started with discovery tools only, and sbx mcp load m101-deepwiki --sandbox m101-mcp-dyn added the three:

Sandbox and momenttools/list namesCapture
m101-mcp, staticask_wiki_question code-mode mcp-exec read_wiki_contents read_wiki_structurecapture/out/11-gateway-probe-static.txt
m101-mcp-dyn, before loadcode-mode mcp-add mcp-config-set mcp-exec mcp-findcapture/out/11-gateway-probe-dynamic-before.txt
m101-mcp-dyn, after loadask_wiki_question code-mode mcp-add mcp-config-set mcp-exec mcp-find read_wiki_contents read_wiki_structurecapture/out/11-gateway-probe-dynamic-after.txt
loading a registered server into a running sandboxcapture/out/11-load.txttext
$ sbx mcp load m101-deepwiki --sandbox m101-mcp-dyn
MCP server "m101-deepwiki" loaded into sandbox "m101-mcp-dyn" (live)

The help promises the notice: "Connected agents see the new server's tools immediately via the standard MCP tools/list_changed notification" help-sbx sbx mcp load. The probe opened a new session for each run, so it recorded the two lists and not the notification itself. The built-in tools belong to the gateway: mcp-exec, code-mode, mcp-find, mcp-add, mcp-config-set, and <server>-authorize for OAuth servers docs-sbx Built-in gateway tools. In Cedar MCP policies those are MCP::Primordial resources and a server's tools are MCP::Tool resources docs-sbx Built-in gateway tools, which org policies returns to.

sbx mcp catalog was removed in v0.45.0 rel-sbx v0.45.0, and the sbx mcp enable of the product page never existed (conflict C11). Figure 3.7 draws the store, the two gateways, and the load. For the protocol itself, read the MCP lesson.

Fig. 3.7one registration, two gateways, two tool listsflow
One registration, two gateways, two tool lists On the host, the MCP store holds m101-deepwiki, registered with sbx mcp add from https://mcp.deepwiki.com/mcp as a remote streamable-http server that needs no OAuth. Each sandbox gets its own gateway at MCP_GATEWAY_URL http://mcp-gateway.docker.internal/mcp. The sandbox m101-mcp was created with --static-mcp m101-deepwiki and lists ask_wiki_question, read_wiki_contents, read_wiki_structure, code-mode, and mcp-exec. The sandbox m101-mcp-dyn started with code-mode, mcp-add, mcp-config-set, mcp-exec, and mcp-find, and sbx mcp load added the three deepwiki tools with a tools/list_changed notice. The remote server runs outside the VM, and no OAuth server was captured. HOST SANDBOXES mcp.deepwiki.com/mcp remote endpoint, outside the VM sbx mcp add m101-deepwiki --url https://mcp.deepwiki.com/mcp remote · streamable-http requires_oauth: false · auth status: [] no policy mcp-gateway-m101-mcp static · --static-mcp gateway of m101-mcp-dyn dynamic · sbx mcp load fixed at create load, live m101-mcp · tools/list ask_wiki_question read_wiki_contents read_wiki_structure code-mode mcp-exec no mcp-find, mcp-add, mcp-config-set m101-mcp-dyn · tools/list before: code-mode mcp-add mcp-config-set mcp-exec mcp-find after load: + ask_wiki_question read_wiki_contents read_wiki_structure gateway URL list_changed <server>-authorize and sbx mcp auth: no OAuth server in this capture
The host store holds m101-deepwiki once, each sandbox gets its own gateway at one URL, and a static set is fixed while load changes a dynamic one live. Read from the store on the left to the two sandboxes on the right. Solid ink arrows are the registration reaching a gateway. The dotted indigo arrow is the tools/list_changed notice, and the dashed olive arrow is the remote server outside the VM. From capture/out/11-mcp-inspect.json, 11-static-inside.txt, 11-gateway-probe-static.txt, 11-gateway-probe-dynamic-before.txt, 11-load.txt, and 11-gateway-probe-dynamic-after.txt.

Sources:help-sbx sbx mcp, sbx mcp add, sbx mcp auth, sbx mcp inspect, sbx mcp load, sbx mcp ls, sbx create (research/sources/help-sbx.md); docs-sbx MCP gateway, Architecture, Security model, Settings (research/sources/docs-sandboxes.md); rel-sbx v0.45.0 (research/sources/sbx-releases.md); research/conflicts-register.md rows C11, C91; talk T02 (2026-09-04), T23 (2026-09-29) from research/plan.md; capture/fixtures/mcp-probe.sh; capture/out/11-mcp-add.txt, 11-mcp-add-registry.txt, 11-mcp-ls.json, 11-mcp-inspect.json, 11-mcp-auth-status.json, 11-static-create.txt, 11-static-inside.txt, 11-gateway-initialize.http, 11-gateway-probe-static.txt, 11-dynamic-create.txt, 11-gateway-probe-dynamic-before.txt, 11-load.txt, 11-gateway-probe-dynamic-after.txt, 11-mcp-rm.txt

Part 4

Kits and sbxenv.yaml

A kit declares what a sandbox contains and can reach, and an environment file declares the sandbox and its secrets for approval before anything runs.

  1. 4.1`# syntax=docker/sandbox-kit:3` and kit.yaml
  2. 4.2`com.docker.sandbox/*` capabilities
  3. 4.3`sbx kit pack`, `sbx kit push --sign`, and `sbx kit verify`
  4. 4.4sbxenv.yaml and `sbx env plan`
  5. 4.5`sbx skills add` and `skills: true`
4.1

`# syntax=docker/sandbox-kit:3` and kit.yaml

A v3 kit is an ordinary OCI image whose manifest annotation carries a strict YAML descriptor, which sbx resolves when it creates the sandbox.

Your team wants every Codex sandbox to carry the same three tools, the same two allowed hosts, and the same instructions. A kit declares the tools, the hosts, and the credentials in one file, and the file travels as an image.

When you finish this section, you can read a v3 descriptor line by line and say what the frontend writes into the image. You can also tell which commands build, inspect, and run it.

One image, one annotation

Kit: one OCI image whose manifest annotation vnd.docker.sandbox.kit.descriptor carries the kit's declarations, while its layers carry the content kitspec §1. "a Kit pulls, inspects, and FROMs with stock tooling, and an engine that does not read the annotation runs it as an ordinary image" kitspec §1.

Workload: a kit whose layers are a root filesystem and whose image config carries the launch command. A composition has exactly one kitspec §1.

Mixin: a kit whose layers are an overlay applied on a workload's filesystem, zero or more per composition kitspec §1. A third kind, set, exists only while authoring: "publishing derives workload or mixin from the Kits it lists" kitspec §4.

The descriptor, line by line

The capture kit wrote one v3 descriptor, a shell workload with an inline recipe and a single network grant:

the v3 descriptor the capture kit wrotecapture/fixtures/kits/hello-kit/kit.yamlyaml
# syntax=docker/sandbox-kit:3
schemaVersion: "3"
kind: workload
displayName: hello-kit
description: A shell workload with one network grant, in the v3 descriptor form.
version: "1.0.0"
licenses: [Apache-2.0]
build: |
  FROM docker/sandbox-templates:shell
  COPY HELLO.md /home/agent/HELLO.md
capabilities:
  - type: com.docker.sandbox/network-policy@2
    config:
      runtime:
        allow:
          - example.com

The frontend is "dispatched by the descriptor's first line, # syntax=docker/sandbox-kit:3" kitspec §1.1. schemaVersion must be exactly the string "3", and kind is workload, mixin, or set kitspec §4. displayName, description, version, and licenses are optional display metadata. No name field exists, because identity is the reference that a kit is consumed by kitspec §1.

The build: field "carries literal Dockerfile text" kitspec §3.2. capabilities is a list of entries with type and config kitspec §7. Here one network-policy@2 entry allows example.com in the runtime phase, the agent's steady state kitcap network-policy@2.

Decoding is strict: "Any unrecognized field anywhere in the document is an error" kitspec §1.2. The reason is policy: "A misspelled key silently ignored would be a policy silently absent" kitspec §1.2. Figure 4.1 puts the file beside the manifest that the build pushed.

Fig. 4.1a v3 descriptor and the image manifest that carries itstructure
A v3 descriptor and the image manifest that carries it Left: the nine top-level entries of capture/fixtures/kits/hello-kit/kit.yaml, from the syntax line to the capabilities list, each with a note on what it becomes. Right: what the local registry returned for m101/hello-kit:v1, an OCI image index with no annotations and the linux/arm64 image manifest. The manifest carries four vnd.docker.sandbox.kit annotations, the descriptor as JSON with 402 derived deb provides, schema-version 3, the network-policy@2 capability, and built-by sandbox-kit 3.0.0-m.8, plus four org.opencontainers.image keys, the OCI config, and 16 layers with the sources staged at /usr/share/sandbox/kit/kit. Teal lines connect each descriptor row to the annotation or layer it becomes. KIT.YAML OF HELLO-KIT, AS WRITTEN WHAT THE REGISTRY RETURNED capture/fixtures/kits/hello-kit/kit.yaml # syntax=docker/sandbox-kit:3 dispatches the frontend, sandbox-kit 3.0.0-m.8 schemaVersion: "3" REQUIRED, exactly the string "3" (kitspec 4) kind: workload workload, mixin, or set (kitspec 4) displayName: hello-kit becomes org.opencontainers.image.title description: A shell workload with one network grant, in the v3 descriptor form. becomes org.opencontainers.image.description version: "1.0.0" becomes org.opencontainers.image.version licenses: [Apache-2.0] becomes org.opencontainers.image.licenses build: | FROM docker/sandbox-templates:shell COPY HELLO.md /home/agent/HELLO.md the recipe of the layers (kitspec 3.2) capabilities: - type: com.docker.sandbox/network-policy@2 config: runtime: allow: - example.com a list of type and config (kitspec 7) OCI image index, tag v1 application/vnd.oci.image.index.v1+json linux/arm64 and an attestation manifest annotations: none platform manifest, linux/arm64 application/vnd.oci.image.manifest.v1+json annotations vnd.docker.sandbox.kit.descriptor kit.yaml as JSON, plus 402 deb/ provides vnd.docker.sandbox.kit.schema-version "3" vnd.docker.sandbox.kit.capabilities com.docker.sandbox/network-policy@2 vnd.docker.sandbox.kit.built-by docker/sandbox-kit 3.0.0-m.8, 129be2ff org.opencontainers.image.* title hello-kit, version 1.0.0, licenses Apache-2.0, description config application/vnd.oci.image.config.v1+json 16 layers shell template, HELLO.md, and sources staged at /usr/share/sandbox/kit/kit docker buildx build -f fixtures/kits/hello-kit/kit.yaml --builder m101-builder -t m101-registry:5000/m101/hello-kit:v1 --push fixtures/kits/hello-kit With the default docker driver, the same push kept no annotation (28-manifest-docker-driver.json). The index carries none of the eight, although kitspec 9.3 promotes them there (28-index.json).
The frontend copies the hello-kit descriptor into one manifest annotation, derives six more annotations from its fields, and records its own release in built-by. Read left to right. The left column is capture/fixtures/kits/hello-kit/kit.yaml, and each note says what the row becomes. The right column is the index and the arm64 manifest that the local registry returned, from capture/out/28-index.json and 28-manifest.json. Teal lines are the writes of the frontend.

What the frontend publishes

docker buildx build ./my-kit -f ./my-kit/my-kit.yaml -t docker.io/<NAMESPACE>/my-kit:1.0.0 --push builds and publishes a kit docs-sbx Publish an image. The capture kit ran it against a local registry with a docker-container builder (28-builder.txt, 28-buildx.txt), and the registry returned this manifest:

the annotations of the hello-kit manifestcapture/out/28-manifest.jsonjson
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
…
  "annotations": {
    "org.opencontainers.image.description": "A shell workload with one network grant, in the v3 descriptor form.",
    "org.opencontainers.image.licenses": "Apache-2.0",
    "org.opencontainers.image.title": "hello-kit",
    "org.opencontainers.image.version": "1.0.0",
    "vnd.docker.sandbox.kit.built-by": "{\"name\":\"docker/sandbox-kit\",\"version\":\"3.0.0-m.8\",\"revision\":\"129be2ff45e8f9463450eb3cf04ddcb52c2b76e5\"}",
    "vnd.docker.sandbox.kit.capabilities": "com.docker.sandbox/network-policy@2",
    "vnd.docker.sandbox.kit.descriptor": {
      "schemaVersion": "3",
…
      "provides": [
        "deb/adduser@3.153",
…
        "... 397 more derived provides entries"
      ],
…
    "vnd.docker.sandbox.kit.schema-version": "3"
The config, the layers, and most of the descriptor are cut. The registry stores the descriptor as one JSON string, and the capture kit decodes it and keeps five of its 402 provides entries.

The descriptor annotation is "The published descriptor as compact JSON" kitspec §9.3, with the build: text kept. For a workload, the frontend also reads the package database and adds one deb/ entry per installed package kitspec §9.6. schema-version and capabilities repeat two fields, and the org.opencontainers.image.* keys copy the display fields kitspec §9.3. The build staged the sources at /usr/share/sandbox/kit/kit, after the stem of kit.yaml kitspec §10.

Which commands accept a v3 kit

Conflict C13 asks which generation each command accepts, and the captures settle it:

the v2 tooling refuses the v3 directorycapture/out/13-kit-v3-validate.txttext
$ sbx kit validate ./fixtures/kits/hello-kit
error: kit ./fixtures/kits/hello-kit is a v3 source kit and this load path has no kit builder configured; artifact validation failed
[exit 1]
inspect builds a v3 source with the host Docker daemoncapture/out/13-kit-v3-inspect.txttext
$ sbx kit inspect ./fixtures/kits/hello-kit --json
   → build kit ./fixtures/kits/hello-kit (sbx-kit-src:kit-<id>)
error: build kit ./fixtures/kits/hello-kit: exit status 1 ERROR: failed to build: OCI exporter is not supported for the docker driver. Switch to a different driver, or turn on the containerd image store, and try again. …
[exit 1]
The documentation link at the end of the error is cut. 28-kit-inspect-source.txt repeats the same error.

The ruling: sbx kit validate, pack, push, and pull are v1 and v2 tooling. pack refused the directory too, for lack of a spec.yaml (13-kit-v3-pack.txt). The documentation agrees: "Use Buildx for v3 kits. The sbx kit pack, push, and pull commands are for v1 and v2 kits" docs-sbx Publish an image.

A v3 kit is consumed by sbx run and --kit, since "sbx run and sbx create now accept sandbox kit references as the agent positional" rel-sbx v0.42.0. The pushed hello-kit still never reached a sandbox, because kit.allowedSources defaults to ["docker.io/"] (C40, 02-settings.txt) rel-sbx v0.34.0:

sbx refuses a kit from outside docker.iocapture/out/28-run-kit.txttext
$ sbx create localhost:15000/m101/hello-kit:v1 fixtures/repo-kit --name m101-v3
error: resolve kits: kit "localhost:15000/m101/hello-kit:v1": kit "localhost:15000/m101/hello-kit:v1" cannot be installed — its source is not in your allowlist; current kit.allowedSources: docker.io/; …
  try: sbx settings set kit.allowedSources '["docker.io/","localhost:15000/m101/"]'
…
[exit 1]
The advice after the allowlist value and the second suggestion are cut. 28-kit-inspect.txt shows the same refusal for sbx kit inspect.

The capture kit never changes sbx settings, so no capture shows sbx running a v3 kit. Two rules bound the mix. "V3 kits cannot be combined with v1 or v2 kits in the same sandbox" docs-sbx Version compatibility. And "The built-in agent names, such as claude and codex, select v2 kits" docs-sbx Version compatibility. Docker publishes its v3 workloads as docker/sbx-kit-*, such as docker.io/docker/sbx-kit-codex:0.155.1 docs-sbx Run a kit.

Where the run and the pages disagree

Five results of the run contradict the specification or the documentation, and rows C113 to C117 of the register cover them. The table prints both sides:

The page saysThe capture showsFiles
docker buildx build -f kit.yaml --push publishes the kit docs-sbx Publish an imagethe default docker driver of Desktop 4.94.0 pushed a Docker schema 2 manifest with no annotations28-buildx-docker-driver.txt, 28-manifest-docker-driver.json
the frontend "promotes all four onto the image index whenever the export produces one" kitspec §9.3the index has an attestation manifest and no annotations, and the arm64 manifest carries all eight28-index.json, 28-manifest.json
the floating docker/sandbox-kit:3 never moves for a milestone (the README of the specification):3 resolved to the milestone 3.0.0-m.8 of 2026-10-0228-buildx.txt
"During development, you can pass a local source directory to sbx instead" docs-sbx Publish an imagesbx kit inspect of the directory failed: the OCI exporter is not supported for the docker driver13-kit-v3-inspect.txt, 28-kit-inspect-source.txt
source-form builds "run inside a shared builder sandbox named sbx-kit-builder" help-sbx sbx kit builderthat build ran in the host Docker daemon, and the builder status recorded after it reads "not created"13-kit-v3-inspect.txt, 13-kit-builder-status.txt

An image without the descriptor annotation is not a kit to any consumer kitspec §10, so build with a docker-container builder. A consumer that finds no annotation on the index reads the platform manifest kitspec §9.3. The specification calls itself experimental, with a final version targeted for Q4 2026.

The default size of a kit volume stays in dispute (C12). The v0.39.0 notes say 512 MB rel-sbx v0.39.0, the research notes say 20 GiB, and this manual prints both. kit-tck validate judges a published artifact, kit-tck inspect reads it back, and this manual ran neither. Section 7.4 lists the claims it checked instead.

Sources:kitspec §1, §1.1, §1.2, §3.2, §4, §7, §9.3, §9.6, §10 (research/sources/SPEC-v3-at-v3.0.0-m.8.md); kitcap network-policy@2 (research/sources/kit-capabilities.md); research/sources/kit-spec-extras.md (README and RELEASES.md of docker/sandbox-kit-spec, for the frontend tag and the milestone dates); docs-sbx Kits, Use kits, Build and distribute kits (research/sources/docs-sandboxes.md); help-sbx sbx kit builder (research/sources/help-sbx.md); rel-sbx v0.34.0, v0.39.0, v0.42.0 (research/sources/sbx-releases.md); research/conflicts-register.md rows C12, C13, C40, and C113 to C117; capture/README.md (the findings of K28); capture/fixtures/kits/hello-kit/kit.yaml; capture/out/02-settings.txt, 13-kit-v3-validate.txt, 13-kit-v3-pack.txt, 13-kit-v3-inspect.txt, 13-kit-builder-status.txt, 28-builder.txt, 28-buildx.txt, 28-buildx-docker-driver.txt, 28-manifest.json, 28-manifest-docker-driver.json, 28-index.json, 28-kit-inspect-source.txt, 28-kit-inspect.txt, 28-run-kit.txt

4.2

`com.docker.sandbox/*` capabilities

A kit grants itself nothing: each capability is typed and versioned, the resolver unions one workload with its mixins, and any widening on update stops for approval.

The gh mixin a colleague published reaches api.github.com with your token. You want to know exactly which requests it can make with that token, and what changes when version 2 arrives. Both answers are in its capabilities list.

When you finish this section, you can read that list, predict the merged grant set, and say which update will stop and ask.

Typed, versioned requests

Capability: one typed request in a kit's capabilities list, "Everything the Kit needs but cannot supply itself" kitspec §7. The host answers each one: granted, refused, or prompted.

Each entry has a type of the form <namespace>/<name>@<version>, an optional display name, and an optional flag kitspec §7. Its config is decoded strictly for the types the specification defines, so an unknown key is an error kitspec §7. The version names the config schema, so network-policy@1 and @2 both exist and a descriptor states one of them kitspec §7.1. "Policy-shaped types are singletons" kitspec §7.1, while instance-shaped types repeat once per thing requested, such as credential@1 per service and phase.

Unknown types are allowed by design: "An unknown type is the extension point working as designed" kitspec §7.3. A required unknown type fails resolution, and an optional one is skipped and recorded.

The nineteen types at the pin

TypeShapeWhat it asks for
network-policy@1singletonhosts the sandbox can reach, per phase
network-policy@2singleton, exclusive with @1hosts plus HTTP methods and paths
credential@1per service and phaseone service the workload authenticates to
ssh-agent@1per phasewhat the forwarded SSH agent signs
volume@1per pathpersistent or tmpfs storage
host-mount@1per patha host directory, sharing the storage key with volume@1
port@1per container port and transporta published port
usb-device@1instancea USB device match
resources@1singletonCPU, memory, and GPU limits, a constraint rather than a grant
privileged@1singleton, no configa privileged container
long-running@1singleton, no configkeep running with no session attached
lifecycle@1singletoninstall hooks, startup hooks, files, the interactive argv
agent-context@1singletonthe instruction file for the agent
agent-sessions@1singletonheadless prompt and resume verbs
agent-skills@1per pathwhere the agent reads skills
agent-skill@1per effective nameone bundled skill
git-identity@1singleton, no configthe runtime's git name and email
kit-registry@1singleton, no configreach the runtime's own kit registry, where builds push and pull
sbx@1singleton, no configlaunch the workload a particular way, with the identity the image states

The table is kitspec §7.2 at the v3.0.0-m.8 tag. The main branch adds a twentieth, com.docker.sandbox/agent-interactive-sessions@1, as a singleton kitspec-main §7.2, unreleased at the pin. The newest type in sbx itself arrived with the pinned release: "Kits can declare com.docker.sandbox/long-running@1 to keep local sandboxes running after all sessions disconnect" rel-sbx v0.47.0.

network-policy@2 and credential@1

A network-policy@2 entry is a plain host string or an object with hosts, methods, and paths kitcap network-policy@2. A host string grants the connection for any protocol, as @1 did. An entry with methods or paths is bounded: it grants those HTTP requests and nothing else on that host. Deny entries are "Entries to refuse. Deny wins" kitcap network-policy@2. A refused request gets a status, because a runtime "MUST answer a request these entries refuse with HTTP status 403" kitcap network-policy@2. The proxy that enforces this is the one in the policy log section.

A credential@1 entry names a service and a phase, "never where the secret lives" kitcap credential@1. With apiKey.proxyManaged, "The real value stays on the host" kitcap credential@1. Every inject domain must appear in the allow list of the same phase kitcap credential@1. The binding that answers it is the one the secret section stores.

Resolution and the merged set

A runtime "MUST include exactly one workload Kit per composition" kitspec §5.3. It "MUST fail when two Kits provide the same normalized name" kitspec §5.3, so claude plus claude-mixin is refused. Across the set, allow entries union per phase, deny entries union per phase, and deny wins on the result kitcap network-policy@2. When one kit allows a host outright, another kit's bounded entry for that host is dropped, because the union is the unbounded grant kitspec §9.5. Figure 4.2 runs that arithmetic on three kits.

Fig. 4.2three kits resolve into one grant settree
Three kits resolve into one grant set Top: three kits, the captured hello-kit workload with one network-policy@2 allow for example.com, the gh mixin from the specification example with network-policy@1 allows and a GitHub credential, and a mixin that carries the network-policy@2 config example with a bounded allow and two deny entries. Middle: sbx resolves the closed set with exactly one workload. Below: the merged grant set stored in the lock, five rows of allow, deny, install, and credential entries. Bottom: two branches for the next version of a kit. A version whose grants stay inside the stored set applies silently. A version that removes the deny on DELETE widens the set and stops for approval. THE DECLARED KITS hello-kit, workload fixtures/kits/hello-kit network-policy@2, runtime: allow example.com gh, mixin kitspec 2 example network-policy@1 runtime allow github.com, api.github.com, uploads.github.com credential@1: github, runtime, GH_TOKEN proxyManaged bounds, mixin kitcap network-policy@2 config install: registry.npmjs.org runtime: allow api.github.com GET and HEAD on /repos/** deny api.github.com DELETE deny telemetry.example.com sbx resolves the set (kitspec 5.3) closed set, one workload, one provider per name, deny wins the lock records the grant set the merged grant set for the sandbox (kitspec 7.4) runtime allow example.com, github.com, uploads.github.com runtime allow api.github.com, every method and path (bounded entry dropped, kitspec 9.5) runtime deny api.github.com DELETE, telemetry.example.com (deny wins, kitcap) install allow registry.npmjs.org (closed again before the agent starts, kitspec 12) credential github at runtime, GH_TOKEN proxyManaged, inject on api.github.com the next version of a kit stays inside the set a bounded allow on a host already granted applies silently (kitspec 7.4) removes deny api.github.com DELETE a removed deny entry is a widening stops and asks before it applies same grants wider grants A kit grants itself nothing: each row is a request the host answered. The lock judges the next version. From hello-kit/kit.yaml, kitspec 2, 5.3, 7.4 and 9.5, and kitcap network-policy@2 and credential@1.
One workload and two mixins merge into one grant set where allows union, deny wins, and a removed deny on the next version stops for approval. Read top down. The three kits are the captured hello-kit, the gh example of kitspec §2, and the network-policy@2 config example of its capability page. The teal block is the lock. From capture/fixtures/kits/hello-kit/kit.yaml, kitspec §5.3, §7.4, §9.5, and the kitcap pages.

What a grant looks like inside sbx

The recording host ran a v2 mixin, so the captured rows come from permissions.network.allow rather than a v3 entry. The daemon stores the grant as a policy rule the sandbox owner cannot edit:

the kit's allow rule on sandbox m101-kitcapture/out/12-policy-kit.jsonjson
      "name": "kit:m101-kit",
…
      "scope": "sandbox:m101-kit",
…
      "resource_type": "network",
      "decision": "allow",
      "resources": [
        "example.com"
      ],
…
      "editable": false,
…
        "created_via": "provisioned",
The rules array has one element, cut to the fields named in the text.

sbx policy check network example.com --sandbox m101-kit --json then answers "allowed": true (capture/out/12-check-kit.json). The rule carries created_via: provisioned and answers to --source kit, which is how the policy section tells a kit rule from one you added.

Updates and the lock

Every grant projects onto one normalized set, and "Consumers that gate updates store a Kit's surface in the lock and diff a candidate's against it" kitspec §7.4. A new version whose set stays inside the stored one can apply silently. Any widening must stop for approval. The list is explicit: a new allow entry, "a removed deny entry (the deny was part of what made the grant acceptable)" kitspec §7.4, a new credential, path, or port, or write access over a read-only skills path. optional does not change the set kitspec §7.4. Six types contribute nothing to it: resources@1, lifecycle@1, agent-context@1, agent-sessions@1, sbx@1, and long-running@1 kitspec §7.4.

Sources:kitspec §5.3, §7, §7.1, §7.2, §7.3, §7.4, §9.5 (research/sources/SPEC-v3-at-v3.0.0-m.8.md); kitspec-main §7.2 (research/sources/SPEC-v3.md, unreleased); kitcap network-policy@2, credential@1 (research/sources/kit-capabilities.md); rel-sbx v0.47.0 (research/sources/sbx-releases.md); capture/fixtures/kits/hello-kit/kit.yaml; capture/out/12-policy-kit.json, 12-policy-kit.txt, 12-check-kit.json

4.3

`sbx kit pack`, `sbx kit push --sign`, and `sbx kit verify`

The sbx kit commands package, sign, push, verify, and show provenance for v1 and v2 artifacts, and kit add appends a mixin to a running sandbox.

You wrote a v2 mixin that installs tree and drops a HELLO.md into the workspace. A colleague wants it without cloning your repository, and your security team wants to know who signed it. The sbx kit commands cover both, as long as the kit is v1 or v2.

When you finish this section, you can validate and pack a v2 kit, and describe what push --sign attaches to the manifest. You can also verify a signature and add a mixin to a running sandbox.

spec.yaml and files/

Mixin (v2): a directory with a spec.yaml whose schemaVersion is "2" and kind is mixin, plus an optional files/ tree docs-sbx Use existing kits. The capture kit wrote one:

the v2 mixin the capture kit wrotecapture/fixtures/kits/hello-mixin/spec.yamlyaml
schemaVersion: "2"
kind: mixin
name: hello-mixin
description: Installs tree and adds a greeting file to the workspace.
setup:
  install:
    - command: apt-get update && apt-get install -y tree
      user: "0"
      description: Install tree
permissions:
  network:
    allow:
      - example.com

files/workspace/HELLO.md holds one line, and sbx kit inspect reports it as a workspace file with mode 420 (capture/out/12-inspect.json). The v2 grammar is the one the built-in agents use, and its normative text lives in SPEC-v2.md of docker/sbx-kits-contrib docs-sbx Top-level fields.

validate and pack

sbx kit validate REFERENCE accepts a directory, a ZIP, or a git reference, and --json reports a verdict and warnings help-sbx sbx kit validate:

validate, then packcapture/out/12-validate.jsonjson
{
  "reference": "./fixtures/kits/hello-mixin",
  "kind": "directory",
  "valid": true,
  "warnings": []
}
the ZIP that pack wrotecapture/out/12-pack.txttext
$ sbx kit pack ./fixtures/kits/hello-mixin -o $CAPTURE/work/hello-mixin.zip
Packed artifact to $CAPTURE/work/hello-mixin.zip
[exit 0]

$ zip listing of work/hello-mixin.zip (size, name)
       0  files/
       0  files/workspace/
      22  files/workspace/HELLO.md
     297  spec.yaml

For pack, "The directory must contain a valid spec.yaml and an optional files/ directory" help-sbx sbx kit pack. The same ZIP validates with kind: "zip" (capture/out/12-validate-zip.json). A v3 directory fails both commands, as the descriptor section showed. Figure 4.3 draws the whole path, with the steps the capture kit did not run in dashed boxes.

Fig. 4.3from spec.yaml to a signed artifact and into a sandboxflow
From spec.yaml to a signed artifact and into a sandbox Row one, captured: the hello-mixin directory with spec.yaml and files, validated by sbx kit validate and packed by sbx kit pack into hello-mixin.zip. Row two, from the help text and not run: sbx kit sign with a key writes kit.sig.bundle beside spec.yaml, and sbx kit push with the sign flag pushes the manifest to an HTTPS registry with a signature referrer and a provenance referrer. Row three, not run: sbx kit verify and sbx kit provenance read those referrers back, beside the four settings that admit a kit. Row four, captured: sbx create with the kit flag builds m101-kit with tree installed and HELLO.md copied, sbx kit add appends env-mixin to m101-demo in a swap container, and sbx kit add refuses hello-mixin because it declares files. AUTHOR AND PACKAGE (12-VALIDATE.TXT, 12-PACK.TXT) hello-mixin/ spec.yaml, files/ sbx kit validate VALID (directory) sbx kit pack -o Packed artifact to hello-mixin.zip spec.yaml and files/ SIGN AND PUBLISH (HELP-SBX, NOT RUN) sbx kit sign --key writes kit.sig.bundle beside spec.yaml sbx kit push --sign DIR REF, form chosen by schemaVersion 1 or 2 HTTPS registry manifest, config, one layer signature + provenance referrers push READ BACK (NOT RUN) sbx kit verify --key cosign.pub sbx kit provenance UNSIGNED or VERIFIED settings that admit a kit kit.allowedSources, kit.requireSignature, kit.trustedSigners, kit.allowLocalKits CONSUME (12-RUN-KIT.TXT, 12-KIT-ADD.TXT, 12-KIT-ADD-FILES.TXT) sbx create shell --kit m101-kit: tree installed, HELLO.md, allow example.com sbx kit add env-mixin m101-demo recreated in a swap container, variable set sbx kit add hello-mixin refused: the kit declares files Dashed boxes come from the help text and were not run. Every solid box is in capture/out/12-*.
A v2 kit is validated and packed on the host, signed and pushed with two referrers, verified from the registry, and consumed by create or kit add. Read row by row. Solid boxes are captured in capture/out/12-validate.txt, 12-pack.txt, 12-run-kit.txt, 12-kit-add.txt, and 12-kit-add-files.txt. Dashed boxes follow the help text of sbx kit sign, push, verify, and provenance, which were not run.

sign, push --sign, and pull

The capture kit ran none of these three, because it has no registry and no signing identity. The help text is the source. sbx kit sign is keyless by default, with an OIDC token from the CI provider or a browser login. "A token is never read from SIGSTORE_ID_TOKEN, so it cannot be chosen by anything that can set an environment variable" help-sbx sbx kit sign. With --key it signs with an unencrypted PEM private key. "For a local directory, a detached signature bundle is written to kit.sig.bundle next to spec.yaml" help-sbx sbx kit sign.

sbx kit push DIRECTORY REFERENCE chooses the artifact form from schemaVersion: a ZIP for "1", a tar+gzip layer with the spec in the config blob for "2" help-sbx sbx kit push. "With --sign, the pushed manifest is signed and the Sigstore bundle is attached to the kit as an OCI referrer" help-sbx sbx kit push. Signed or not, "Every push also attaches a SLSA provenance attestation as an OCI referrer" help-sbx sbx kit push, and "The provenance is unsigned unless --sign is given" help-sbx sbx kit push. --tlog-upload=false keeps a private kit out of the Rekor log. For pull, "The registry must support HTTPS" help-sbx sbx kit pull.

A published v3 image can be signed by reference with the same command, but "V3 kits shared as source directories or Git references can't be signed" docs-sbx Sign and verify kits.

verify, provenance, and the settings that admit a kit

sbx kit verify takes --key PUB for a key, or --certificate-identity with --certificate-oidc-issuer for a keyless signature. --insecure-ignore-tlog accepts a private keyless signature, and "It has no effect on key-based verification" help-sbx sbx kit verify. sbx kit provenance REFERENCE prints the attestation, and "only attestations that verify and whose subject matches the kit's own digest are reported as VERIFIED" help-sbx sbx kit provenance.

Six settings decide what a sandbox accepts, all at their defaults on the recording host:

the kit settings at their defaultscapture/out/02-settings.txttext
kit.allowExtractedAgents             true                               bool     default             Admit the pinned kit references that replace fo…
kit.allowLocalKits                   true                               bool     default             Allow installing kits from local directories or…
kit.allowedSources                   ["docker.io/"]                     json     default             JSON array of allowed kit source prefixes (e.g.…
kit.ignoreTransparencyLog            false                              bool     default             Verify keyless kit signatures without requiring…
kit.requireSignature                 false                              bool     default             Require a valid signature from a trusted signer…
kit.trustedSigners                   [{"identityRegexp":"^.*@docker\…   json     default             JSON array of trusted signer policies (key-base…
The description column is cut.

"When kit.requireSignature is true, sbx rejects unsigned kits, signatures that don't match kit.trustedSigners, and ZIP kits" docs-sbx Require signed kits. For kit.trustedSigners, "The default policy trusts Docker employee identities ending in @docker.com, attested by Google's issuer" docs-sbx kit.trustedSigners. The settings table lists the environment variable of each key.

kit add and the builder

sbx kit add SANDBOX REFERENCE recreates the container with the mixin appended, "preserving kit-owned volumes (e.g. agent session state) across the swap" help-sbx sbx kit add. The capture shows the accepted case and the refused one:

kit add accepts env-mixin and refuses hello-mixincapture/out/12-kit-add.txttext
$ sbx kit add m101-demo ./fixtures/kits/env-mixin
Recreating sandbox "m101-demo" to apply augmented kit list...
  Swap container m101-demo-swap-<id> started (id=<sha256>).
Kit "env-mixin" added to sandbox "m101-demo"
[exit 0]

$ sbx exec m101-demo sh -c 'env | grep M101_FROM_KIT'
M101_FROM_KIT=yes
[exit 0]
a mixin with files is refusedcapture/out/12-kit-add-files.txttext
$ sbx kit add m101-demo ./fixtures/kits/hello-mixin
error: kit "hello-mixin" declares files, which the kit-add recreate flow does not yet apply; recreate the sandbox from scratch via `sbx rm` + `sbx create --kit` to use this kit
[exit 1]

Sandboxes created before the recreate-aware label are refused with an error help-sbx sbx kit add, and long-running@1 cannot be added this way rel-sbx v0.47.0. sbx create shell --kit ./fixtures/kits/hello-mixin applied the same mixin in full: one install command, one workspace file, and one allow rule (capture/out/12-run-kit.txt, 12-policy-kit.txt).

Source-form builds of v3 kits "run inside a shared builder sandbox named sbx-kit-builder, created on first use" help-sbx sbx kit builder. sbx kit builder status reports it, and history passes through to docker buildx history:

the builder before any buildcapture/out/13-kit-builder-status.txttext
$ sbx kit builder status
Builder:      not created (the first source-form kit build creates it)
Builder kit:  docker.io/docker/sbx-kit-builder:1
Kit registry: 127.0.0.1:5411 — reachable
[exit 0]

Sources:help-sbx sbx kit add, builder, pack, provenance, pull, push, sign, validate, verify (research/sources/help-sbx.md); docs-sbx Kits v2, Build and distribute kits, kit settings (research/sources/docs-sandboxes.md); rel-sbx v0.47.0 (research/sources/sbx-releases.md); research/conflicts-register.md row C13; capture/fixtures/kits/hello-mixin/spec.yaml, capture/fixtures/kits/env-mixin/spec.yaml; capture/out/02-settings.txt, 12-inspect.json, 12-validate.json, 12-validate-zip.json, 12-pack.txt, 12-run-kit.txt, 12-policy-kit.txt, 12-kit-add.txt, 12-kit-add-files.txt, 13-kit-builder-status.txt

4.4

sbxenv.yaml and `sbx env plan`

An environment file declares the agent, kits, workspace, secrets, ports, MCP servers, and host commands, and sbx env plan prints every change before create or run applies it.

A new contributor clones your repository and needs the sandbox you use. That means the shell workload, the hello-mixin, one published port, a greeting variable, and a host command that prepares the workspace. You could send a list of flags. An environment file sends the same thing as one checked-in file, and the contributor sees a plan before anything runs.

When you finish this section, you can write an sbxenv.yaml, read its plan symbol by symbol, and say which edits wait for the next create.

The file

Environment file: a sbxenv.yaml that declares one sandbox and the host resources around it, read by the five sbx env commands help-sbx sbx env. The capture kit wrote this one:

the environment file the capture kit wrotecapture/fixtures/env/sbxenv.yamlyaml
schemaVersion: "1"
name: m101-env
agent: shell
workspace: .
args:
  greeting:
    default: hello
    description: Greeting word
kits:
  - source: ../kits/hello-mixin
env:
  GREETING: ${{ env.args.greeting }}
lifecycle:
  initialize:
    - command: echo init
ports:
  - sandbox: 8080
    host: 18083
KeyWhat it declares
schemaVersion, name, agentthe file version "1", the sandbox name, the built-in agent or kit
argsinputs with default or required: true, read as ${{ env.args.NAME }}
kitsmixins, as a reference or as source plus args
workspace, additionalWorkspacesthe host directories to mount, with an object form for clone mode
envvariables for the sandbox
secrets, bindings, registriescredentials with value, ref, or command, and the domains they reach
mcp.serversMCP servers registered with the gateway
portspublished ports, sandbox, host, protocol, hostIP
lifecyclehost commands under initialize, postCreate, preRemove
sandboxOptionstemplate, memory, cpus, skills, writableEnvFiles, and more

The keys come from the file reference docs-sbx Top-level fields and from the command's own help. Three placeholders expand, ${{ env.args.NAME }}, ${{ env.projectDir }}, and ${{ env.fileDir }}, and a $ anywhere else is literal text help-sbx sbx env create. A relative kit path or workspace resolves against the directory of the file that declares it, so workspace: . mounts the file's own directory help-sbx sbx env. A file with no workspace mounts nothing rel-sbx v0.42.0.

The plan

sbx env plan reads the file and prints what applying it would set up. "Nothing is applied, approved, or recorded" help-sbx sbx env plan.

the first plan for m101-envcapture/out/14-env-plan.txttext
── ENVIRONMENT PLAN
   m101-env

   kits:
+    - source: $CAPTURE/fixtures/kits/hello-mixin

   workspace:  (present, not recorded as applied, needs your approval)
     path: $CAPTURE/fixtures/env

   env:
+    GREETING: hello

+  sandbox:
+    name: m101-env
+    agent: shell

   ports:
+    - sandbox: 8080
+      protocol: tcp4
+      host: 18083

   lifecycle:
     initialize:
+      - command: echo init
+        workdir: $CAPTURE/fixtures/env
+        envFiles:
+          - $CAPTURE/fixtures/env/sbxenv.yaml

   Plan: + 5 to add, ~ 0 to change, - 0 to destroy.

   this plan runs commands on this machine, outside the sandbox, with your own privileges
   ✓ not approved yet; applying asks once
The LOAD ENVIRONMENT block is cut.

The plan has the shape of your file, and the margin carries the verdict. Five symbols exist help-sbx sbx env. + adds, ~ changes, and - destroys. > marks a command that runs again, and ! marks a resource the environment applied and no longer declares.

A row that is unchanged and already approved is left out. "Literal secret values appear as SHA-256 digests" docs-sbx Review an environment plan. The tcp4 protocol was not in the file: published ports default to it since v0.42.0 rel-sbx v0.42.0. Figure 4.4 follows the file through the plan to the state record.

Fig. 4.4an environment file becomes a plan, an approval, and a state recordflow
An environment file becomes a plan, an approval, and a state record Top: the captured sbxenv.yaml with its agent, workspace, args, kits, env, lifecycle, and ports keys. An arrow for sbx env plan leads to the plan it printed, eight rows with a plus sign in the margin for each addition, the workspace row that needs approval, the totals line, and the warning that host commands run outside the sandbox. Below, two branches. Left: approval on sbx env create writes the state record under the sbx state directory, and a later plan prints only the rows that moved. Right: the same plan with the env-arg greeting set to servus still prints every row as an addition because nothing was applied. THE FILE (CAPTURE/FIXTURES/ENV/SBXENV.YAML) sbxenv.yaml schemaVersion 1, name m101-env, agent shell, workspace ., args greeting (default hello), kits ../kits/hello-mixin, env GREETING: ${{ env.args.greeting }}, lifecycle initialize echo init, ports sandbox 8080 host 18083 sbx env plan ./fixtures/env ENVIRONMENT PLAN m101-env (14-env-plan.txt) + kits: source $CAPTURE/fixtures/kits/hello-mixin workspace: $CAPTURE/fixtures/env (not recorded as applied, needs your approval) + env: GREETING: hello + sandbox: name m101-env, agent shell + ports: sandbox 8080, protocol tcp4, host 18083 + lifecycle: initialize: command echo init, workdir $CAPTURE/fixtures/env Plan: + 5 to add, ~ 0 to change, - 0 to destroy. runs commands on this machine, outside the sandbox, with your own privileges margin symbols: + add, ~ change, - destroy, > run, ! forget (help-sbx sbx env) AFTER APPROVAL (NOT CAPTURED) ONE ARGUMENT CHANGED (CAPTURED) you answer y on sbx env create host commands are asked on every invocation state record, per environment in the sbx state directory, not by the file a later plan shows what moved ~ GREETING: hello -> servus, nothing else sbx env create records the plan sbx env plan --env-arg greeting=servus the arg replaces the default hello + env: GREETING: servus still + 5 to add: nothing was applied one argument
sbx env plan turns the file into margin-marked rows, approval on create records them, and a later plan prints only the rows that moved. Read top down. The plan rows are capture/out/14-env-plan.txt, and the right branch is 14-env-plan-arg.txt with the greeting argument changed. The left branch after approval follows the help text of sbx env, because the kit never applied the plan.

Host commands and approval

The lifecycle block runs on the host, outside the sandbox, with your own privileges. initialize runs on every create and every run, postCreate once the sandbox exists, and preRemove after you confirm sbx env rm help-sbx sbx env. Commands run through your shell from the project directory, with workdir and timeout per command. An environment with any of them "asks on every invocation, whether or not this one is what runs them" help-sbx sbx env, because approving a command also trusts the script it calls. --skip-host-commands runs none of them, --auto-approve answers yes once, and sbx settings set env.rememberHostCommands true asks again only when a command changes.

The state record and the second plan

"What was approved is recorded per environment under sbx's state directory, not next to the file" help-sbx sbx env, "so a later invocation asks only about what moved" help-sbx sbx env. The exact path was not captured, because the kit ran plan and never create. The second capture changed one input instead:

the same file with one argument changedcapture/out/14-env-plan-arg.txttext
$ sbx env plan --env-arg greeting=servus ./fixtures/env
…
+    GREETING: servus
…
   Plan: + 5 to add, ~ 0 to change, - 0 to destroy.
Only the changed row and the totals are shown.

Every row is still +, because nothing was applied. After a create, the same edit prints ~ GREETING: hello -> servus and nothing else, in the form the help text shows for a changed kit argument help-sbx sbx env. Changes to workspaces, kits, ports, secrets, bindings, and sandboxOptions wait for the next create. New env values reach the next session docs-sbx Update an environment.

Several PATH arguments deep-merge in order, "later files override earlier ones" help-sbx sbx env create, and lists such as ports concatenate rather than override. With no PATH, a .sbxenv.yaml in your home directory merges underneath as a base layer help-sbx sbx env create. A hidden .sbxenv.yaml in the project is no longer read.

rm, the read-only file, and --cloud

sbx env rm removes the sandbox and the secrets provisioned at its scope. Bindings stay, since they are user-wide, so "pass --prune-bindings to also remove the bindings this environment declares" help-sbx sbx env rm. The file itself is bound read-only inside the sandbox, because an agent that can edit it decides what the next plan asks about. sandboxOptions.writableEnvFiles: true lifts that help-sbx sbx env.

With --cloud, "workspace, additionalWorkspaces and clone name host directories, which a cloud sandbox cannot mount" help-sbx sbx env, and host port bindings, MCP definitions, and dynamic secret sources are rejected before any host command runs. The cloud section shows what remains.

Sources:help-sbx sbx env, sbx env create, sbx env plan, sbx env rm (research/sources/help-sbx.md); docs-sbx Sandbox environment files (research/sources/docs-sandboxes.md); rel-sbx v0.42.0 (research/sources/sbx-releases.md); capture/fixtures/env/sbxenv.yaml; capture/out/14-env-plan.txt, 14-env-plan-arg.txt

4.5

`sbx skills add` and `skills: true`

One shared skills store serves every sandbox of a supported agent read-only by default, and docker-agent reads its own skill directories, not the store, with skills: true.

You keep a pdf skill under ~/.claude/skills on your laptop. Inside a sandbox the agent has its own home directory and never sees it. The shared skills store is the one place you fill, and every sandbox for a supported agent reads it.

When you finish this section, you can fill the store, choose how each sandbox mounts it, and turn the same skills on for docker-agent.

The store

Skill: a directory with a SKILL.md whose metadata an agent reads into its system prompt, and whose body it loads when a task matches docs-agent How Skills Work.

Shared skills store: the host directory that sbx links into every sandbox for a supported agent. On the recording host it was empty:

the store before any skill is addedcapture/out/15-skills-ls.jsonjson
{
  "store": "$HOME/Library/Application Support/com.docker.sandboxes/sandboxes/agent-skills",
  "skills": []
}

On Linux the store is ~/.local/state/sandboxes/sandboxes/agent-skills, and on Windows %LOCALAPPDATA%\DockerSandboxes\sandboxes\state\agent-skills docs-sbx Import skills from the host. "Running sbx reset clears the shared store" docs-sbx Shared store behavior.

add, import, ls, rm, and update

sbx skills add <repository> installs from a Git URL or a GitHub owner/repository, and "The repository must contain one or more valid SKILL.md files" help-sbx sbx skills add. --skill NAME picks skills by name, repeatable or comma-separated, so sbx skills add anthropics/skills --skill pdf installs one. The capture kit did not record an add, because the step needs GitHub. sbx skills update refreshes only skills that add installed, and sbx skills rm asks before it removes one help-sbx sbx skills update.

sbx skills import copies skills already installed on the host, checking six directories in order, and the first copy of a duplicate name wins help-sbx sbx skills import:

Host sourceAgentMount target in the sandbox
~/.agents/skillsCodex and Devin/home/agent/.agents/skills
~/.claude/skillsClaude Code/home/agent/.claude/skills
~/.config/opencode/skillsOpenCodenot listed in the docs table
~/.copilot/skillsCopilot/home/agent/.copilot/skills
~/.cursor/skillsCursor/home/agent/.cursor/skills
~/.factory/skillsDroid/home/agent/.factory/skills

The order comes from the help text and the mount targets from the docs docs-sbx Import skills from the host. "Imported skills are available to Claude, Codex, Copilot, Cursor, Droid, and OpenCode" help-sbx sbx skills import. sbx skills import arrived in v0.37.0, and add, update, and rm in v0.42.0 rel-sbx v0.42.0.

How a sandbox sees the store

By default "the store's entries are linked into the agent's skills directory read-only, which stays writable so kits can install skills beside them" help-sbx sbx skills. "Linking happens at container start" help-sbx sbx skills, so an edit to an existing skill is live, while "adding a store entry reaches a running sandbox only on its next start" help-sbx sbx skills. Removing a skill breaks its link at once.

--skills off|readonly|readwrite on sbx run or sbx create chooses the mode per sandbox, and readwrite mounts the store over the directory so the sandbox's writes are shared. The default is readonly, or the skills.defaultMode setting, which the recording host had at its default (capture/out/02-settings.txt). The three-way flag replaced --no-share-skills in v0.43.0 rel-sbx v0.43.0. The capture kit did not list that directory from inside a sandbox, so the link form is not shown.

In a v3 kit the agent declares the path itself, with agent-skills@1, because "A runtime cannot know where an arbitrary agent reads skills" kitcap agent-skills@1. A runtime "MUST default an omitted mode to readonly" kitcap agent-skills@1. The effective access is the narrower of the host setting and the kit's mode. Raising a path to readwrite is a widening that stops for approval, as the capabilities section explains.

docker-agent and skills: true

docker-agent does not read the store. It scans its own directories, and two of them, ~/.claude/skills/ and ~/.agents/skills/, are also import sources. "Docker Agent scans standard directories for SKILL.md files" docs-agent How Skills Work, and "Skill metadata (name, description) is injected into the agent's system prompt" docs-agent How Skills Work:

PathSearch
~/.codex/skills/recursive
~/.claude/skills/immediate children only
~/.agents/skills/recursive
.claude/skills/the current directory only
.github/skills/each directory from the git root to the current one
.agents/skills/each directory from the git root to the current one

In the agent file, skills: true loads every discovered skill, a list restricts it, and false turns it off docs-agent Filtering Skills. A list item that is local or an http:// or https:// URL is a source, and any other string is a skill name. "A name that doesn't match any discovered skill is logged as a warning at startup but is otherwise ignored" docs-agent Filtering Skills. The agent needs the filesystem toolset to read skill files, as the toolsets section lists. context: fork in a skill's front matter "tells the agent to run the skill in an isolated sub-agent instead" docs-agent Running a Skill as a Sub-Agent.

In a docker-agent sandbox

With docker-agent run --sandbox, the host paths above are invisible from the VM. Docker Agent builds a kit before the sandbox starts, "bind-mounted read-only into the VM at the same path" docs-agent Auto-Kit. Every SKILL.md found on the host "is copied under <kit>/skills/<skill-name>/" docs-agent What gets staged, every text file passes a secret redaction step, and --no-kit turns the staging off. The end-to-end section shows the printed summary of what was staged.

Sources:help-sbx sbx skills, sbx skills add, sbx skills import, sbx skills update (research/sources/help-sbx.md); docs-sbx Share agent skills, skills.defaultMode (research/sources/docs-sandboxes.md); docs-agent Skills, Sandbox Mode Auto-Kit (research/sources/docs-docker-agent.md); kitcap agent-skills@1 (research/sources/kit-capabilities.md); rel-sbx v0.37.0, v0.42.0, v0.43.0 (research/sources/sbx-releases.md); capture/out/02-settings.txt, 15-skills-ls.json, 15-skills-ls.txt

Part 5

docker-agent run and the Agent File

An agent file is agents, models, toolsets, and the rules between them, and docker-agent run drives the loop, records it, and replays it.

  1. 5.1docker-agent run, new, and doctor
  2. 5.2agents, models, and providers
  3. 5.3toolsets and mcps
  4. 5.4sub_agents, transfer_task, and background_agents
  5. 5.5permissions, --safety, and hooks
  6. 5.6session.db, sessions diff, and eval
5.1

docker-agent run, new, and doctor

docker-agent run loads an agent file and drives the loop with or without a terminal, doctor says which model auto would pick, and new needs a terminal.

A colleague sends you an agent file and one command to run it. You have no API key, only Docker Desktop with Model Runner, and you want proof that the file runs before you open a chat window.

When you finish this section, you can run an agent file with no terminal, read its event stream, and ask doctor which model auto picks.

What run takes

Agent reference: the first argument of docker-agent run. It is a .yaml, .yml, or .hcl file, a registry reference, an alias, or nothing help-agent docker-agent run. With nothing, run uses docker-agent.yaml, docker-agent.yml, or docker-agent.hcl from the current directory, or else a built-in default agent docs-agent CLI Reference. coder is a second built-in agent, and alias add saves a name for a file or a reference together with run options such as --safety help-agent docker-agent alias add.

A registry reference behaves like a file. 24-run-ref.txt runs localhost:15000/m101/agent:v1 and prints the same answer as the local file, as share push and share pull shows. Each further argument is one user message, and the messages run as turns in order. A - reads the message from stdin help-agent docker-agent run.

--exec, --json, and --last

Headless run: run --exec, which writes to stdout and opens no TUI. --json writes one JSON event per line, and --last prints only the final answer help-agent docker-agent run. The capture runs capture/fixtures/agents/files.yaml with the same two messages in each form.

run --exec --last with two messagescapture/out/18-run-last.txttext
$ docker-agent run --exec --working-dir fixtures/repo --last --fake work/cassettes/18-files fixtures/agents/files.yaml 'List the files in the working directory.' 'How many lines does README.md have? Count them with a shell command.'
README.md has 1 line.
[exit 0]
Nothing is cut. The --fake flag replays the recorded model answers, as the session section explains.
three of the 67 events of the same run with --jsoncapture/out/18-run-json.ndjsonjsonl
{"message": "List the files in the working directory.", "session_id": "<uuid>", "session_position": 0, "timestamp": "<ts>", "type": "user_message"}
{"agent_name": "root", "timestamp": "<ts>", "tool_call": {"function": {"arguments": "{\"path\": \".\"}", "name": "list_directory"}, "id": "<call-id>", "type": "function"}, "tool_definition": …
{"agent_name": "root", "content": "The working directory contains 1 file: README.md (1 line).", "message_id": "<uuid>", "session_id": "<uuid>", "timestamp": "<ts>", "type": "agent_choice"}
Lines 5, 13, and 24 of the file. The tool_call event is cut after the call, before its tool_definition.

The other event types of the file include team_info, toolset_info, tool_call_response, token_usage, and stream_stopped. The second turn shows the limit of a headless run. The model asked for shell with wc -l README.md, and the runtime raised tool_call_confirmation. With no terminal to answer it, the tool result was "The user rejected the tool call." The model then used read_file (18-transcript.txt). Permissions, --safety, and hooks explains why that call asked.

The model in every recording

The docs call ai/qwen3 "the model Docker Agent reaches for by default" docs-agent Set Up a Model. With no Model Runner, doctor resolves auto to the 8B tag ai/qwen3:latest (16-doctor.txt). On the recording Mac, two pulls of that tag ended with a digest mismatch after the last 5.03 GB blob (25-model-pull-latest.txt). Every agent file of the kit therefore names ai/qwen3:4b, the 4B tag of the same repository. It is a thinking model, so it streams a few thousand reasoning tokens before each answer. --exec prints them, and the kit cuts them to one line such as [... 299 lines of model reasoning cut by run.py ...] (18-run-exec.txt).

doctor and models

doctor reports provider credentials, whether Docker Model Runner answers, the auto pick, and, with a file, the variables that file needs. It exits non-zero on an issue help-agent docker-agent doctor. The kit ran it once with no Docker daemon and once with Model Runner up.

doctor with no Docker daemoncapture/out/16-doctor.txttext
$ docker-agent doctor
…
Docker Model Runner
  Status: unreachable: docker --config=$HOME/.docker --context=m101-no-daemon model status --json: …
…
Model auto-selection
  auto -> dmr/ai/qwen3:latest

Issues
  - no usable model: no provider credential was found and Docker Model Runner is unreachable; …
Error: 1 issue(s) found
[exit 1]
The 20 provider rows, all "not set", and the full error of the runner check are cut.
doctor with Model Runner up and one model pulledcapture/out/25-doctor.txttext
$ docker-agent doctor
…
Docker Model Runner
  Status: reachable, 1 model(s) pulled:
    - docker.io/ai/qwen3:4b

Model auto-selection
  auto -> dmr/docker.io/ai/qwen3:4b

No issues found.
[exit 0]
The user configuration line and the 20 provider rows are cut.

With no runner, auto still names dmr/ai/qwen3:latest, and the issue line says why nothing can run. With the runner up, auto takes the one pulled model, as the provider page says: auto-selection "prefers a locally-installed model" docs-agent Docker Model Runner. models list printed one row, dmr ai/qwen3:latest, even with no daemon (16-models.txt). setup is the interactive fix. Its four paths are a provider key in ~/.config/cagent/.env, a Model Runner pull, a custom OpenAI-compatible endpoint, and the Claude Code harness help-agent docker-agent setup.

new

new asks questions and writes an agent file, and a description argument skips "the initial prompt" help-agent docker-agent new. Its --model takes anthropic, openai, google, dmr, or a custom provider, and --max-iterations defaults to 20 for DMR (conflict C66). In the capture, new with a description and no controlling terminal stopped at /dev/tty and wrote no file (17-new-agent.yaml).

new with a description and no terminalcapture/out/17-new.txttext
$ docker-agent new --model dmr/ai/qwen3:4b 'an agent that greets the user and names one fact about Docker sandboxes'  (in work/, without a controlling terminal)
…
Error: bubbletea: error opening TTY: bubbletea: could not open TTY: open /dev/tty: device not configured
[exit 1]
The welcome banner and the telemetry notice are cut.

Figure 5.1 puts the pieces of one run on one page.

Fig. 5.1one docker-agent run from the file to its outputsflow
One docker-agent run from the agent file to its outputs Top row: capture/fixtures/agents/files.yaml with one agent root, the model local, and two toolsets is loaded by docker-agent run with the exec flag and two messages; without exec the same run draws the TUI. Middle row: the agent loop of root sends model calls to dmr/ai/qwen3:4b on Model Runner at localhost:12434 and tool calls to the ten tools of filesystem and shell. Bottom row: the loop writes the final answer to stdout, 67 NDJSON events with the json flag, and one session to session.db. The record flag writes the model exchanges to the cassette 18-files.yaml, and the fake flag replays them in place of Model Runner. ONE RUN OF CAPTURE/FIXTURES/AGENTS/FILES.YAML files.yaml one agent, root model local, 2 toolsets docker-agent run --exec, two messages TUI without --exec loads dmr/ai/qwen3:4b Model Runner localhost:12434 agent loop: root one turn per model call filesystem, shell 10 tools, such as list_directory, shell turn 1, turn 2 18-files.yaml the cassette stdout final answer only with --last --json NDJSON, 67 events session.db a session per run --record --fake Dashed box: not run in the capture. Every solid box is in capture/out/18-*.
run loads the agent file, alternates model calls and tool calls in one loop, and writes the answer, the events, a session, and with --record a cassette. Read from the top left. Solid plum is a model call and dashed olive a tool effect. Dotted indigo is the event stream, and solid teal is a stored record. With --fake, the cassette answers in place of Model Runner. From capture/fixtures/agents/files.yaml, capture/out/18-run-json.ndjson, and capture/out/18-cassette-head.txt.

Sources:help-agent docker-agent run, setup, doctor, new, alias add (research/sources/help-docker-agent.md); docs-agent CLI Reference, Set Up a Model, Docker Model Runner (research/sources/docs-docker-agent.md, pages features/cli, getting-started/set-up-a-model, providers/dmr); conflict C66 (research/conflicts-register.md); capture/README.md; capture/fixtures/agents/files.yaml; capture/out/16-doctor.txt, 16-models.txt, 17-new.txt, 17-new-agent.yaml, 18-run-exec.txt, 18-run-last.txt, 18-run-json.ndjson, 18-transcript.txt, 18-cassette-head.txt, 24-run-ref.txt, 25-doctor.txt, 25-model-pull-latest.txt

5.2

agents, models, and providers

agents is the only required block, a model reference is provider/model or a name from models, and providers sets a reusable endpoint such as Docker Model Runner.

You open capture/fixtures/agents/files.yaml and find model: local on the agent, a local entry under models, and provider: dmr inside that entry. A second file, dmr.yaml, adds a providers block with a URL, and its run never reaches the recorder.

When you finish this section, you can follow an agent's model value to the endpoint it calls, and predict what auto picks.

The file and its schema

Agent file: a YAML or HCL document that matches agent-schema.json, the schema of Docker Agent v16. Its root allows 16 keys and requires one: "Map of agent configurations. At least one agent is required" schema agents. A misspelled top-level key fails the load, because "the parser rejects unknown top-level keys" docs-agent Configuration Overview.

version is a string from "0" to "16" in the schema enum. The same docs page still says "The current version is 15" docs-agent Configuration Overview. The capture files declare "16" and load, so this manual follows the schema. "When you load an older config, Docker Agent automatically migrates it to the latest schema" docs-agent Configuration Overview. The agent that share pull fetched from agentcatalog/pirate declares version: "2" and names openai/gpt-4.1 inline (16-share-pull-agent.yaml).

the agent file of the single-agent runscapture/fixtures/agents/files.yamlyaml
version: "16"

agents:
  root:
    model: local
    description: Lists the files of a small repository and counts lines.
    instruction: |
      You answer questions about the files in the working directory.
      Use the tools to look; never guess. Answer in one sentence that
      names the files and gives the line count.
    max_iterations: 8
    toolsets:
      - type: filesystem
      - type: shell

models:
  local:
    provider: dmr
    model: ai/qwen3:4b
    temperature: 0
Nothing is cut. The same file drives the runs in the session, eval, and share sections.

Agent keys

Agent: one entry under agents, named by its key. The first agent of the team runs unless --agent names another help-agent docker-agent run. The schema AgentConfig lists the keys, and these are the ones the capture files use or rely on.

KeyIn the captureSchema rule
modellocal, qwen, dmr/ai/qwen3, openai/gpt-4.1a model name or provider/model schema AgentConfig
instructiona block string in every filethe system prompt: a string, or a list joined with blank lines
max_iterations8 in files.yaml, 3 for writeran integer from 0
max_consecutive_tool_callsnot setidentical calls before the agent stops, and 0 means the default of 5
redact_secretsnot settrue by default: a builtin scrubs secrets on three hook events
toolsets, sub_agents, handoffs, hooksfiles.yaml, team.yaml, guarded.yamltoolsets, delegation, permissions

Model references

Model reference: the value of model on an agent. It takes five forms:

  • provider/model inline, such as dmr/ai/qwen3 in greeter.yaml docs-agent Models.
  • a name under models, such as local, with provider, model, and parameters such as temperature: 0.
  • a first_available list: "At load time, Docker Agent selects the first candidate whose credentials are configured" docs-agent Models.
  • an alloy: two references with a comma between them, which the runtime alternates in one conversation.
  • auto: "the first cloud provider with a configured credential", then a pulled Model Runner model docs-agent Set Up a Model.

run --model [agent=]provider/model replaces the reference for one run help-agent docker-agent run. On the recording Mac, doctor resolved auto to dmr/docker.io/ai/qwen3:4b with only the 4B tag pulled (25-doctor.txt). With no Model Runner, it named dmr/ai/qwen3:latest and reported no usable model (16-doctor.txt).

providers and the Model Runner endpoint

Provider definition: an entry under providers with an underlying provider (default openai), a base_url, a token_key, and defaults that its models inherit docs-agent Provider Definitions.

a named provider for Docker Model Runnercapture/fixtures/agents/dmr.yamlyaml
version: "16"

providers:
  runner:
    provider: dmr
    base_url: http://localhost:12434/engines/llama.cpp/v1

models:
  qwen:
    provider: runner
    model: ai/qwen3:4b
    temperature: 0
…
The agents block is cut.

files.yaml names provider: dmr with no base_url, and then "Docker Agent auto-discovers the DMR endpoint" docs-agent Docker Model Runner. It runs docker model status --json, which 16-dry-run.txt prints inside its error when no Docker daemon answers. An eval container has no docker CLI, so the same discovery fails there, as eval shows.

With an explicit base_url, dmr.yaml answered Hello (25-run-dmr.txt). That run is live in every capture, because an explicit base_url bypasses the --record proxy and leaves the cassette empty (capture/README.md). Set base_url when discovery cannot work, and leave it out when you want a cassette.

Provider ids changed too. doctor prints fireworks-ai, togetherai, and moonshotai, and the docs Models table lists fireworks, together, and moonshot. This manual prints the ids that doctor prints (conflict C58), and the providers table has the rest. A named patch under flavors changes any of these blocks at run time with --flavor help-agent docker-agent run, and the capture ran none.

Fig. 5.2two ways a model reference reaches Model Runnerstructure
Two ways a model reference reaches Model Runner Left column, capture/fixtures/agents/files.yaml: agents.root with model local resolves to models.local with provider dmr, model ai/qwen3:4b and temperature 0; with no base_url the endpoint is found by running docker model status --json. Right column, capture/fixtures/agents/dmr.yaml: agents.root with model qwen resolves to models.qwen with provider runner, which resolves to providers.runner with provider dmr and base_url http://localhost:12434/engines/llama.cpp/v1. Bottom row: the auto decision as doctor printed it. No cloud provider has a credential, so auto looks for a pulled Model Runner model: with the runner up it picks dmr/docker.io/ai/qwen3:4b, and with no runner it names dmr/ai/qwen3:latest and reports no usable model. capture/fixtures/agents/files.yaml capture/fixtures/agents/dmr.yaml agents.root description, instruction max_iterations: 8 toolsets: filesystem, shell models.local provider: dmr model: ai/qwen3:4b temperature: 0 endpoint discovery docker model status --json run by docker-agent itself model: local no base_url agents.root description, instruction no toolsets models.qwen provider: runner model: ai/qwen3:4b temperature: 0 providers.runner provider: dmr base_url: http://localhost:12434 /engines/llama.cpp/v1 model: qwen provider: runner MODEL: AUTO, AS DOCTOR PRINTED IT cloud credential? 20 providers: not set pulled DMR model? docker model status dmr/docker.io/ai/qwen3:4b 25-doctor.txt, exit 0 dmr/ai/qwen3:latest 16-doctor.txt, exit 1 no
files.yaml resolves local to the dmr provider and finds the endpoint itself, while dmr.yaml resolves qwen through providers.runner to a fixed URL. Read each column top down, from the agent to the endpoint. Violet blocks are agent entries and plum blocks are model and provider entries. The bottom row is the auto decision as doctor printed it. From capture/fixtures/agents/files.yaml, capture/fixtures/agents/dmr.yaml, capture/out/16-doctor.txt, 16-dry-run.txt, and 25-doctor.txt.

Sources:schema agents, AgentConfig (research/sources/agent-schema.json); docs-agent Configuration Overview, Models, Set Up a Model, Provider Definitions, Docker Model Runner (research/sources/docs-docker-agent.md, pages configuration/overview, concepts/models, getting-started/set-up-a-model, providers/custom, providers/dmr); help-agent docker-agent run (research/sources/help-docker-agent.md); conflict C58 (research/conflicts-register.md); capture/README.md; capture/fixtures/agents/files.yaml, dmr.yaml, greeter.yaml; capture/out/16-doctor.txt, 16-dry-run.txt, 16-share-pull-agent.yaml, 25-doctor.txt, 25-run-dmr.txt

5.3

toolsets and mcps

A toolset is one type from a fixed list of 27 that gives an agent tools, and mcp reaches a server by Docker reference, command, or URL.

Your agent answers questions about a repository from memory, and you suspect it never received a file tool. The agent file says filesystem, but the model sees tool names, not toolsets.

When you finish this section, you can list the tools an agent gives its model, call one without a model, and choose an MCP form.

The 27 types

Toolset: one entry under an agent's toolsets, with a type and options, that the runtime turns into one or more tools. docker-agent toolsets prints 27 types, and the schema Toolset enum has the same 27 schema Toolset.

six of the 27 toolset typescapture/out/16-toolsets.txttext
$ docker-agent toolsets
TYPE                SUMMARY
…
background_agents   Dispatch work to sub-agents concurrently and collect results
…
filesystem          Read, write, list, search, and navigate files and directories
…
mcp                 Extend agents with external tools via the Model Context Protocol
mcp_catalog         Discover and activate remote MCP servers from the Docker MCP Catalog
…
plan                Shared persistent scratchpad for multi-agent collaboration
…
shell               Execute shell commands in the user's environment
…
[exit 0]
Cut to the header and six rows. The other 21 types are listed in the toolsets reference table.

transfer_task and handoff are not types. The docs built-in table lists both, the schema enum and the CLI list leave them out, and sub_agents and handoffs inject them (conflict C56). The same docs table lists session_plan, which the CLI does not print, and leaves out environment and file docs-agent Tool Configuration. The toolsets table follows the CLI.

From toolsets to tools

files.yaml declares two toolsets, filesystem and shell. Every toolset_info event of its run reports "available_tools": 10 (18-run-json.ndjson). The request in the cassette 18-files.yaml lists them: nine filesystem tools from directory_tree to remove_directory, then shell. The model chose list_directory and read_file from that list.

debug toolsets FILE --json prints the same names and schemas with no model help-agent docker-agent debug toolsets. For guarded.yaml it prints one tool:

the one tool of guarded.yamlcapture/out/20-debug-toolsets.jsonjson
[
  {
    "agent": "root",
    "tools": [
      {
        "name": "shell",
        "category": "shell",
…
        "parameters": {
          "additionalProperties": false,
          "properties": {
            "cmd": {
              "description": "Shell command",
              "type": "string"
            },
            "cwd": {
              "description": "Working directory (default \".\")",
              "type": "string"
            },
            "timeout": {
              "description": "Timeout in seconds (default 30)",
              "type": "integer"
            }
          },
…
        "annotations": {
          "idempotentHint": false,
          "readOnlyHint": false,
          "title": "Shell"
        },
…
The description and the output schema are cut.

debug tool FILE TOOL JSON calls one tool with no model turn, and "Calls have real side effects and bypass other hooks and approval checks" help-agent docker-agent debug tool. In 20-debug-tool.txt, shell with {"cmd":"echo m101-direct"} printed m101-direct. Use it to test a tool before any model calls it.

Keys every toolset shares

Tool filter: a key that narrows what a toolset gives the model. tools keeps only the listed names, and readonly keeps only tools whose annotations carry a read-only hint schema Toolset. defer hides tools until the model finds them with search_tool and add_tool. instruction replaces the toolset's built-in instructions unless the text contains {ORIGINAL_INSTRUCTIONS}. model names the model for the turn after a tool result. Since v1.148.0 a complete tool result is bounded to 50 KiB rel-agent v1.148.0.

The readOnlyHint: false on shell matters again in permissions, --safety, and hooks.

The mcp type

MCP toolset: a toolset with type: mcp that connects to one MCP server and gives its tools to the agent. The docs name three forms docs-agent Tool Configuration:

  • ref: docker:duckduckgo runs a catalog server in a container through the MCP Gateway.
  • command, args, and env start a local process over stdio, and a missing binary is installed into ~/.cagent/tools/bin/ from the aqua registry.
  • remote.url with transport_type set to streamable or sse reaches a server over the network, with optional headers.

lifecycle.profile sets reconnects for each mcp toolset: resilient by default, strict, or best-effort. A top-level mcps entry holds a server definition that agents reference as {type: mcp, ref: <name>} schema mcps. A top-level toolsets entry, named in use_toolsets, does the same for any type schema toolsets.

No mcp toolset ran in this edition's captures, so the three forms come from the docs and the schema. Whether ref: docker: needs Docker Desktop's MCP Toolkit stays open (conflict C73). The sandbox side of MCP is sbx mcp add, load, and --static-mcp. An agent served as an MCP server is serve mcp and serve acp.

Fig. 5.3what each toolset entry gives the modelflow
What each toolset entry gives the model Three columns: the toolsets entry in the agent file, what the runtime starts for it, and the tool names that arrive in the model request. Captured rows: type filesystem is a built-in toolset in the docker-agent process and gives nine tools, directory_tree, edit_file, list_directory, read_file, read_multiple_files, search_files_content, write_file, create_directory and remove_directory; type shell gives one tool, shell, with cmd, cwd and timeout, one command per call. Rows from the docs, not run: type mcp with ref docker:duckduckgo runs a catalog server in a container through the MCP Gateway, type mcp with command, args and env starts a child process over stdio with aqua auto-install, and type mcp with remote.url reaches a network endpoint over streamable HTTP or SSE; each gives the tools its server lists. AGENT FILE ENTRY WHAT RUNS TOOL NAMES THE MODEL GETS type: filesystem files.yaml built-in toolset inside the docker-agent process directory_tree, edit_file, list_directory, read_file, read_multiple_files, search_files_content, write_file, create_directory, remove_directory type: shell files.yaml, guarded.yaml built-in toolset one command per call shell cmd, cwd, timeout (30 s) THREE FORMS OF TYPE: MCP, FROM THE DOCS, NOT RUN type: mcp ref: docker:duckduckgo MCP Gateway server in a container the server's tools names from tools/list type: mcp command, args, env child process stdio, aqua install the server's tools names from tools/list type: mcp remote.url network endpoint streamable or sse the server's tools names from tools/list
Each toolsets entry becomes named tools in the model request: built-in types run in process, and mcp goes through a gateway, a child process, or a URL. Read each row left to right, from the agent file entry to the tool names in the model request. Solid rows ran in capture/out/18-run-json.ndjson, with names from the tools array of capture/cassettes/18-files.yaml.gz. Dashed rows come from the docs Tool Configuration page and were not run.

Sources:schema Toolset, mcps (research/sources/agent-schema.json); docs-agent Tool Configuration (research/sources/docs-docker-agent.md, page configuration/tools); help-agent docker-agent toolsets, debug toolsets, debug tool (research/sources/help-docker-agent.md); rel-agent v1.148.0 (research/sources/docker-agent-CHANGELOG.md); conflicts C56, C73 (research/conflicts-register.md); capture/fixtures/agents/files.yaml, guarded.yaml; capture/cassettes/18-files.yaml.gz; capture/out/16-toolsets.txt, 18-run-json.ndjson, 20-debug-toolsets.json, 20-debug-tool.txt

5.4

sub_agents, transfer_task, and background_agents

transfer_task runs a sub-agent in a clean sub-session and returns its answer, handoff moves the whole session to another agent, and background agents need approval to start.

You want a coordinator that asks a writer for one sentence and then lets a reviewer answer the user. One agent file can say both things: a delegation that comes back, and a move that does not.

When you finish this section, you can choose between sub_agents, handoffs, and background_agents, and read each one in an event stream.

The team file

a coordinator, a writer, and a reviewercapture/fixtures/agents/team.yamlyaml
version: "16"

agents:
  root:
    model: local
    description: Coordinates a writer and a reviewer.
    instruction: |
      You coordinate two colleagues and never write text yourself.
      For every request: first call transfer_task to ask the writer for
      the text, then call handoff to pass the conversation to the reviewer.
    max_iterations: 6
    sub_agents: [writer]
    handoffs: [reviewer]
    toolsets:
      - type: background_agents

  writer:
    model: local
    description: Writes one sentence on a given topic.
…
    max_iterations: 3

  reviewer:
    model: local
    description: Reviews the sentence it receives.
…
The writer and reviewer instructions and the models block are cut.

All three agents use the model local, which is dmr/ai/qwen3:4b. Neither transfer_task nor handoff appears under toolsets, because the two lists inject them (conflict C56).

transfer_task: a child in a sub-session

Delegation: a call of transfer_task with agent, task, and expected_output, which sub_agents adds to the parent. "The call blocks until the sub-agent returns its result, which becomes the tool's response" docs-agent Transfer Task Tool.

one delegation and one session move, as eventscapture/out/19-transcript.txttext
user: Ask the writer for one sentence about microVMs.
root -> tool_call transfer_task {"agent": "writer", "task": "Generate one sentence about microVMs", "expected_output": "A single sentence describing microVMs"}
agent_switching: {"agent_name": "writer", "switching": true, "from_agent": "root", "to_agent": "writer"}
writer: MicroVMs are lightweight virtual machines that provide strong isolation and security for applications with minimal resource overhead.
stream_stopped (stop, normal)
sub_session_completed: {"agent_name": "root", "parent_session_id": "<uuid>", "sub_session": {"id": "<uuid>", "origin": "run", "title": "Transferred task", "messages": [{"message": {"agent_name": "", "message": {"role": "system", "content": "You are a member of a team of agents. Your goal is to complete the following task:\n\n<task>\nGenerate one sentence about microVMs\n</task>…
agent_switching: {"agent_name": "root", "switching": false, "from_agent": "writer", "to_agent": "root"}
root <- tool_call_response Transfer Task: MicroVMs are lightweight virtual machines that provide strong isolation and security for applications with minimal resource overhead.
root -> tool_call handoff {"agent": "reviewer"}
root <- tool_call_response Handoff Conversation: The agent root handed off the conversation to you. …
reviewer: APPROVED: MicroVMs are lightweight virtual machines that provide strong isolation and security for applications with minimal resource overhead.
stream_stopped (stop, normal)
user: Now hand the conversation to the reviewer.
reviewer: APPROVED: MicroVMs are lightweight virtual machines that provide strong isolation and security for applications with minimal resource overhead.
stream_stopped (stop, normal)
The sub_session_completed line is cut after the task, and the long tool response of the session move is cut after its first sentence.

The writer never saw the user's message. Its sub-session starts with a system message that holds <task> and <expected_output>. An implicit user message follows, "Please proceed." The sub-session ran under the writer's own max_iterations: 3, with "tools_approved": false. Then agent_switching returned control to root, and the sentence came back as the tool response.

"Unlike other tools, transfer_task is always auto-approved" docs-agent Multi-Agent Systems, and the capture agrees: the call raised no confirmation under --exec. A delegation to an agent already in the chain fails, and the depth is capped at 10 nested delegations docs-agent Transfer Task Tool. A sub_agents entry can also be a registry reference. Its tag is resolved again on every run unless you pin it to a digest schema AgentConfig.

The session move

Session move: a call of handoff with one argument, agent, which handoffs adds. The named agent "becomes the active agent and sees the full conversation history" docs-agent Multi-Agent Systems. In the capture, root called it in its next model turn, after the sentence came back. The tool response told reviewer which tools and agents it can use, and reviewer answered the user.

The second message shows the move. It went straight to reviewer, and the transcript has no root line after the move. force_handoff makes the same move on every final response without a tool call schema AgentConfig.

background_agents

Background agent: a sub-agent task that run_background_agent starts, which returns a task id at once. list_background_agents, view_background_agent, and stop_background_agent follow it, and the target must be in the caller's sub_agents docs-agent Background Agents Tool.

a background agent that never startedcapture/out/19-background.txttext
user: Run the writer as a background agent on the topic microVMs, wait for it, and repeat its sentence.
tool_call_confirmation: {"agent_name": "root", "tool_call": {"id": "<call-id>", "type": "function", "function": {"name": "run_background_agent", "arguments": "{\"agent\": \"writer\", \"task\": \"Write one sentence on the topic microVMs\", \"expected_output\": \"A single sentence about microVMs\"}"}}, "metadata": {"safety_label": "unknown"}}
root <- tool_call_response Run Background Agent: The user rejected the tool call.
…
stderr: Error: Agent terminated: detected 5 consecutive identical calls to run_background_agent. This indicates a degenerate loop where the model is not making progress.
Lines 4 to 13, five more confirmations and rejections with the same arguments, are cut.

Under --exec with no --safety, the call itself asked for approval, and with no terminal it was rejected. The model sent the same call again until the runtime stopped the run with exit 1. The limit is max_consecutive_tool_calls, and 0 "uses the default of 5" schema AgentConfig. Inside a running task, "any tool call that would normally prompt the user for approval will be automatically denied" docs-agent Background Agents Tool. A headless coordinator therefore needs an allow rule for run_background_agent and for the tools its sub-agents call. The capture did not test that.

The kit asks for one tool call per message. A turn with two parallel calls cannot be replayed from a cassette, as session.db, sessions diff, and eval explains.

Fig. 5.4a delegation that returns, then a move that stayssequence
A delegation that returns, then a move that stays Four lifelines: you running docker-agent run with exec, root, writer, and reviewer from capture/fixtures/agents/team.yaml. Message 1 asks root for one sentence from the writer. Root calls transfer_task with agent writer, a task and an expected output, the writer runs in its own sub-session that holds only the task, and its sentence, MicroVMs are lightweight virtual machines, returns to root as the tool response. Root then calls the session-moving tool with agent reviewer, and reviewer answers APPROVED with the sentence. Message 2 goes straight to reviewer, which answers the same way, and root is not asked again. you, --exec root writer reviewer 1 Ask the writer for one sentence message 1 2 transfer_task agent: writer, task, expected_output sub-session task only 3 MicroVMs are lightweight the sentence returns to root 4 {"agent": "reviewer"} the session moves to reviewer 5 APPROVED: MicroVMs are lightweight answered by reviewer 6 Now hand the conversation to the reviewer. message 2 7 APPROVED: MicroVMs are lightweight root is not asked again
transfer_task sends writer only the task and returns its sentence to root, while the session move makes reviewer answer every later message. Read top down, one step per beat. Solid ink is a call, dashed ink a reply, and dashed amber the handoff call that changes which agent owns the session. The amber box is the writer's sub-session. From capture/out/19-transcript.txt and 19-transfer-task.json.

Sources:docs-agent Multi-Agent Systems, Transfer Task Tool, Background Agents Tool (research/sources/docs-docker-agent.md, pages concepts/multi-agent, tools/transfer-task, tools/background-agents); schema AgentConfig (research/sources/agent-schema.json); conflict C56 (research/conflicts-register.md); capture/README.md; capture/fixtures/agents/team.yaml; capture/out/19-team.txt, 19-transcript.txt, 19-transfer-task.json, 19-handoff.json, 19-background.txt, 19-background.ndjson

5.5

permissions, --safety, and hooks

Deny, allow, and ask patterns, then a safety mode, then hooks decide whether a tool call runs, and the docs say none of them is a security boundary.

Your agent runs in CI with --exec, and nobody watches the terminal. You want echo to run, rm never to run, and every other call to fail closed unless its label is safe. The agent file and one flag can say that, but only for calls that go through docker-agent.

When you finish this section, you can write permissions patterns, pick a --safety mode for an unattended run, and predict what a pre_tool_use hook changes.

The same page says the restricted mode "is defense in depth against unwanted tool calls, not a security boundary" docs-agent Permissions. For isolation, run the agent in a sandbox, as docker-agent run --sandbox shows.

The guarded agent

one shell toolset, a hook, and two patternscapture/fixtures/agents/guarded.yamlyaml
version: "16"

agents:
  root:
    model: local
    description: Runs shell commands under a permission list and a hook.
    instruction: |
      Run exactly the shell commands the user lists, one tool call per
      command, in the given order. Then report each command and its
      result or refusal in one line each.
    max_iterations: 8
    toolsets:
      - type: shell
    hooks:
      pre_tool_use:
        - matcher: shell
          preempt_yolo: true
          hooks:
            - type: command
              command: ./fixtures/hooks/log-hook.sh
              env:
                M101_HOOK_LOG: ./work/hook-stdin.jsonl

permissions:
  allow:
    - "shell:cmd=echo*"
  deny:
    - "shell:cmd=rm*"
…
The models block is cut.

Permission pattern: a tool name glob with optional argument conditions, such as shell:cmd=rm*, in an allow, ask, or deny list. Patterns from the agent file and from settings.permissions in the user config merge, and a deny from either side wins docs-agent Permissions.

Safety mode: what the runtime does with a call that no pattern matched. It reads the call's label, safe, destructive, or unknown, and the mode decides docs-agent Permissions:

Modesafedestructiveunknown
strictaskaskask
balancedallowaskask
restrictedallowdenydeny
autonomousallowallowallow

--yolo is the same as --safety autonomous help-agent docker-agent run. A session that never chooses a mode keeps "the historical default: read-only tools auto-approve, everything else asks" docs-agent Permissions. That is why the wc -l call in run, new, and doctor asked although its label was safe, because shell carries readOnlyHint: false.

Three calls in two modes

The kit sent the same three messages under --safety strict and --safety restricted, one command per message, with no terminal to answer a prompt.

CommandWhat decidedstrictrestricted
echo m101-okallow: shell:cmd=echo*ran, m101-okran, m101-ok
pwdno pattern, label safeasked, then "The user rejected the tool call."ran, $CAPTURE
rm -rf work/m101-nothingdeny: shell:cmd=rm*"Tool 'shell' is denied by permissions configuration."the same denial

The patterns behaved the same in both modes, and only the unmatched pwd changed. Under restricted, the mode allowed the safe label. Under strict, the runtime raised tool_call_confirmation, and the empty terminal turned it into a rejection (20-strict.txt, 20-restricted.txt).

The order, and what a hook can change

The docs give one order for every call docs-agent Permissions:

  1. preempt_yolo pre_tool_use hooks run first, and no mode or allow rule can bypass their deny or ask.
  2. A deny pattern blocks the call.
  3. An allow pattern approves it.
  4. An ask pattern prompts the user.
  5. With no match, the safety mode applies to the call's label.
  6. On a mode ask, default pre_tool_use hooks can allow, deny, or ask.
  7. With no decision, the user is asked.

Hook: a command, builtin, model, or evaluator entry that runs at a named event schema HookDefinition. A command hook reads one JSON object on stdin and can answer with JSON on stdout. Exit code 2 blocks, and the default timeout is 60 seconds docs-agent Hooks.

what the hook received for the rm callcapture/out/20-hook-stdin.jsonljsonl
{"agent_name": "root", "cwd": "$CAPTURE", "hook_event_name": "pre_tool_use", "safety_policy": "restricted", "session_id": "<uuid>", "tool_input": {"cmd": "rm -rf work/m101-nothing"}, "tool_name": "shell", "tool_use_id": "<call-id>"}
The third of three lines, one per call of the restricted run.

log-hook.sh appends that line to a file and prints "permission_decision":"allow". The stream shows it as pre_tool_use_pre_yolo with "allowed": true on all three calls, yet rm was denied and strict still asked for pwd. From a preempting hook, "an allow verdict is advisory" schema HookMatcherConfig. Its deny or ask would have ended the call.

Two more hooks run on every call: tool_input_transform and tool_response_transform appear in each stream, although no file declares them. redact_secrets, true by default, installs a builtin on those events schema AgentConfig. A hook can also run several times at once. When one message asked for three commands, the hook ran three times at once, and two runs appended to the log file together (capture/README.md). Write each record in one write.

Fig. 5.5three shell calls through the approval orderdecision
Three shell calls through the approval order The approval order for the shell calls of capture/fixtures/agents/guarded.yaml, top down. The preempt_yolo hook log-hook.sh runs on every call and returns allow, which is advice only. A deny pattern shell:cmd=rm* blocks rm -rf work/m101-nothing with Tool shell is denied by permissions configuration. An allow pattern shell:cmd=echo* runs echo m101-ok in both modes. The file has no ask patterns. The safety mode then decides pwd, which has the label safe: restricted runs it and prints the capture directory, and strict asks the user, which under exec with no terminal ends as The user rejected the tool call. SHELL CALLS OF GUARDED.YAML, --SAFETY STRICT AND RESTRICTED 20-STRICT.NDJSON, 20-RESTRICTED.NDJSON preempt_yolo hook log-hook.sh, on every call allow, as advice only pre_tool_use_pre_yolo, allowed: true a deny or ask here would end the call go on a deny pattern matches? shell:cmd=rm* rm -rf work/m101-nothing Tool 'shell' is denied by permissions configuration. no an allow pattern matches? shell:cmd=echo* echo m101-ok runs result m101-ok in both modes no an ask pattern matches? guarded.yaml has none no safety mode on the label pwd has the label safe restricted: pwd runs result $CAPTURE strict strict: ask the user default hooks first, none here no terminal under --exec The user rejected the tool call.
The preempting hook allows every call only as advice, the patterns decide rm and echo, and the safety mode alone decides pwd. Read top down. Each question box is one stage of the order, and the box to its right is what the capture saw at that stage. Rose is a denial or rejection, and olive a command that ran. From capture/fixtures/agents/guarded.yaml, capture/out/20-strict.ndjson, 20-restricted.ndjson, and 20-hook-stdin.jsonl.

Sources:docs-agent Permissions, Hooks (research/sources/docs-docker-agent.md, pages configuration/permissions, configuration/hooks); schema AgentConfig, HookMatcherConfig, HookDefinition (research/sources/agent-schema.json); help-agent docker-agent run (research/sources/help-docker-agent.md); capture/README.md; capture/fixtures/agents/guarded.yaml; capture/fixtures/hooks/log-hook.sh; capture/out/18-transcript.txt, 20-strict.txt, 20-strict.ndjson, 20-restricted.txt, 20-restricted.ndjson, 20-hook-stdin.jsonl

5.6

session.db, sessions diff, and eval

Every run is rows in one SQLite file, a cassette replays its model calls, sessions diff finds the first different tool call, and eval scores saved sessions in containers.

You changed one line of an agent's instruction, and you want to know whether it still does the same work. A live model words every answer differently, so you need records that you can replay and compare.

When you finish this section, you can replay a run from a cassette, compare two runs by tool calls, and score an agent with eval.

session.db

Session: "the record of a conversation, including every message, tool call, sub-agent run, and cost" docs-agent Sessions. It lives in session.db under the data directory, ~/.cagent unless --data-dir or -s points elsewhere help-agent docker-agent run. --session -1 resumes the newest session by creation time docs-agent Sessions.

the session store after the runs of this partcapture/out/21-session-db.txttext
$ sqlite3 work/data/session.db .tables  (python sqlite3)
generated_media_blobs generated_media_manifest migrations session_items sessions sqlite_sequence
…
$ sqlite3 work/data/session.db 'pragma table_info(sessions)'  (12 rows)
id created_at tools_approved input_tokens output_tokens title cost send_user_message max_iterations working_dir starred permissions agent_model_overrides custom_models_used thinking parent_id instruction_context safety_policy attributes origin

$ sqlite3 work/data/session.db 'select id, title from sessions order by created_at desc limit 5'
<uuid> | Running agent
…
Cut to the table list, the columns of sessions, and the five newest titles.

The table held 12 sessions: ten runs, and the two Transferred task sub-sessions of the team runs, which carry a parent id (19-transcript.txt). Each session stores its safety_policy, and session_items holds the messages. The five newest, all --exec runs, are titled Running agent, not a title made from the first message as the docs describe docs-agent Sessions.

Cassettes: --record and --fake

Cassette: a YAML file of the HTTP exchanges between docker-agent and the model. --record writes it, and --fake replays it with no model at all help-agent docker-agent run. 18-cassette-head.txt shows the format: version: 2, then interactions, each with a request to localhost:12434 and the streamed reply.

the flags the kit records and replays withcapture/run.pypython
    def start_recording(self, name):
        if not self.record_cassettes and os.path.exists(cassette_path(name)):
            return None
        self.recorded.append(name)
        return ["--models-gateway", f"{DMR_URL}/engines", f"--record={CASSETTE_WORK}/{name}"]
…
def replay(name):
    return ["--fake", f"{CASSETTE_WORK}/{name}"]
A method of the Kit class and a module function, cut between them. DMR_URL is http://localhost:12434.

The recording found four rules that the help does not state (capture/README.md). --record takes its value only as --record=PATH, and a separate word is read as the agent reference. The path is relative to --working-dir, and .yaml is appended. With the dmr provider the recording proxy answers 400 unless the run also has --models-gateway http://localhost:12434/engines.

Replay matches each request by its body. When one turn issues two tool calls, the runtime runs them in parallel and appends the results in completion order. The next request body then differs, and the replay answers 500, "requested interaction not found". The kit therefore sends one tool call per message.

sessions diff

"Comparison is over the sequence of tool calls, not over the assistant's prose" help-agent docker-agent sessions diff, and the report stops at the first divergence.

the help's example form, the working form, and a divergencecapture/out/21-sessions-diff.txttext
$ docker-agent sessions diff -1 -2
Error: unknown shorthand flag: '1' in -1
[exit 1]

$ docker-agent sessions diff -- -1 -2
Comparing -1 (5 turns) against -2 (5 turns)

✅ Identical behaviour across all 5 turns.
[exit 0]

$ docker-agent sessions diff --fail-on-divergence -- -1 -3
Comparing -1 (5 turns) against -3 (6 turns)

❌ First divergence at turn 0 (after 0 matching turn(s)).
   -1 called:
     list_directory({"path": "."})
   -3 called:
     shell({"cmd": "echo m101-ok"})

Everything after this point is downstream of the divergence and is not compared.
Error: sessions diverged
[exit 1]
Nothing is cut.

The help's own example, sessions diff -1 -2, fails, because the parser reads -1 as a flag. Put -- before the references. -1 and -2 are two replays of files.yaml (21-two-runs.txt), with five turns each, one per model reply in 18-transcript.txt. -3 is the restricted run of guarded.yaml, which called shell first. --json printed {"turns_a": 5, "turns_b": 5, "turns_matched": 5} for the identical pair (21-sessions-diff.json).

eval

Eval session: a JSON session with a user message, the expected tool calls, and an evals block docs-agent Evaluation. The block holds relevance statements, a size, and a working_dir. eval replays each one in a container and scores tool-call F1, relevance by a judge model, and size.

The capture needed three tries with --judge-model dmr/ai/qwen3:4b. Plain, both evals failed with exec: "docker": executable file not found in $PATH, because the image docker/docker-agent:1.149.0 has no docker CLI to find Model Runner (22-eval.txt). With --models-gateway http://model-runner.docker.internal/engines, the judge check failed first with HTTP 403. The docs explain why: "the LLM judge runs on the host, not inside the eval container" docs-agent Evaluation, and the host cannot reach that name (22-eval-gateway.txt). With -e DOCKER_AGENT_MODELS_GATEWAY=…, only the containers changed, and both evals ran.

the eval run that workedcapture/out/22-eval-container-env.txttext
$ docker-agent eval fixtures/agents/files.yaml fixtures/evals --judge-model dmr/ai/qwen3:4b -c 1 -e DOCKER_AGENT_MODELS_GATEWAY=http://model-runner.docker.internal/engines --output work/eval-results-container-env
…
✓ Count the lines of README.md ($0.000000)
  ✓ size S
  ✓ tool calls
  ✓ relevance 1/1
✗ List the files in the working directory ($0.000000)
  ✓ size S
  ✓ relevance 1/1
  ✗ tool calls score 0.67
…
✅          Sizes: 2/2 passed (100.0%)
✅     Tool Calls: 83.3% avg F1 (2 evals)
✅      Relevance: 2/2 passed (100.0%)
…
The loading lines and the output paths are cut.

The second eval expected one list_directory call, and the model added a shell call after it, so F1 fell to 0.67 (22-eval-run-container-env.json). Both eval sessions ran with "safety_policy": "autonomous". The help defaults are -c 10 and the judge openai/gpt-5.6-terra, and the docs table says the number of CPUs and anthropic/claude-opus-5. This manual follows the help (conflict C65). The output directory held <run>.db, <run>.json, and <run>.log, without the -sessions.json file that the docs list (22-eval-results-ls-container-env.txt).

Fig. 5.6two runs on one axis of turns, and two eval scorestimeline
Two runs on one axis of turns, and two eval scores Top: three sessions on an axis of turns 0 to 4, one cell per model reply. Sessions -1 and -2 are two replays of files.yaml and match on every turn: list_directory, an answer naming one file, a shell call with wc -l that was rejected, read_file of README.md, and an answer of one line. Session -3 is the restricted run of guarded.yaml and calls shell with echo m101-ok at turn 0, the first divergence; its later turns are not compared. sessions diff reports identical behaviour for -1 against -2 and exits 1 with fail-on-divergence for -1 against -3. Bottom: the eval of files.yaml. Count the lines of README.md expected and got one shell call, F1 1.0, relevance 1/1, size S. List the files in the working directory expected list_directory and got list_directory and shell, F1 0.67, relevance 1/1, size S. SESSIONS DIFF, 21-SESSIONS-DIFF.TXT turn 0 turn 1 turn 2 turn 3 turn 4 -1 files.yaml list_directory path . answer 1 file shell wc -l rejected read_file README.md answer 1 line -2 files.yaml list_directory path . answer 1 file shell wc -l rejected read_file README.md answer 1 line -3 guarded shell echo m101-ok turns 1 to 5 are not compared downstream of the divergence first divergence at turn 0: list_directory against shell -1 against -2: Identical behaviour across all 5 turns, exit 0 -1 against -3: sessions diverged, exit 1 with --fail-on-divergence EVAL OF FILES.YAML, 22-EVAL-CONTAINER-ENV.TXT eval session tool calls F1 relevance size Count the lines of README.md expected: shell actual: shell 1.0 1/1 S List the files in the working directory expected: list_directory actual: list_directory, shell 0.67 1/1 S Tool Calls: 83.3% avg F1 (2 evals). Sizes 2/2 and relevance 2/2 passed.
The two replays of files.yaml match on all five turns, the guarded run differs at turn 0, and eval scores the same agent per tool call. Read the top tracks left to right along the turn axis, one cell per model reply. The rose cell is the first divergence, and dashed cells are not compared. The bottom rows are the eval scores. From capture/out/18-transcript.txt, 20-restricted.txt, 21-sessions-diff.txt, and 22-eval-container-env.txt.

Sources:docs-agent Sessions, Evaluation (research/sources/docs-docker-agent.md, pages features/sessions, features/evaluation); help-agent docker-agent run, sessions diff, eval (research/sources/help-docker-agent.md); conflict C65 (research/conflicts-register.md); capture/README.md; capture/run.py; capture/fixtures/agents/files.yaml, guarded.yaml; capture/fixtures/evals/count-lines.json, list-files.json; capture/out/18-cassette-head.txt, 18-transcript.txt, 19-transcript.txt, 20-restricted.txt, 21-session-db.txt, 21-sessions-diff.txt, 21-sessions-diff.json, 21-two-runs.txt, 22-eval.txt, 22-eval-gateway.txt, 22-eval-container-env.txt, 22-eval-run.json, 22-eval-run-container-env.json, 22-eval-results-ls-container-env.txt

Part 6

docker-agent serve and share

One agent file answers over REST and SSE, an OpenAI-compatible endpoint, MCP, ACP, and A2A, travels as a signed OCI artifact, and runs on a local model.

  1. 6.1serve api and serve chat
  2. 6.2serve mcp and serve acp
  3. 6.3serve a2a
  4. 6.4share push and share pull
  5. 6.5DMR, Compose models, and the docker/mcp-gateway service
6.1

serve api and serve chat

serve api is the native control plane, with sessions and an SSE run stream on port 8080, and serve chat is the OpenAI-compatible subset on port 8083.

Your agent file answers well in a terminal, and now a web page and a CI job must call it over HTTP. The page wants every event of a turn, and the CI job speaks only the OpenAI chat format.

When you finish this section, you can start both servers on one agent file, run one turn through each, and read the event stream.

One agent file, five servers

The a2a, acp, api, and mcp commands moved under serve in v1.23.4 rel-agent v1.23.4, and v1.53.0 added chat rel-agent v1.53.0. The help of v1.149.0 lists all five help-agent docker-agent serve. This part serves one small file all five ways. Its one agent, root, answers every message with the word pong:

the agent file served in sections 6.1 to 6.3capture/fixtures/agents/pong.yamlyaml
version: "16"

agents:
  root:
    model: local
    description: Answers every message with the word pong.
    instruction: Reply with exactly the word pong and nothing else.

models:
  local:
    provider: dmr
    model: ai/qwen3:4b
    temperature: 0

serve api: a session, then a run

serve api: the command that "exposes your agents through a REST-style API with Server-Sent Events (SSE) streaming" docs-agent API Server. It listens on 127.0.0.1:8080, and an empty --auth-token means no authentication help-agent docker-agent serve api. --max-request-size rejects a body over 1 MiB with HTTP 413, and --session-workingdir-root confines the working_dir of new sessions help-agent docker-agent serve api.

The session database has a different default here. serve api -s writes session.db in the current directory, while run, serve a2a, and serve acp use <data-dir>/session.db help-agent docker-agent serve api (conflict C63). The capture passed -s work/api-session.db and --fake work/cassettes/23-api, so a recorded cassette gave the model answer (capture/out/23-serve-api.log).

list the agents, create a session, start a runcapture/out/23-api.httphttp
GET /api/agents HTTP/1.1
…
    "name": "pong",
    "description": "Answers every message with the word pong.",
    "multi": false
…
POST /api/sessions HTTP/1.1
…
{}
…
  "id": "<uuid>",
  "origin": "run",
…
  "tools_approved": false,
…
POST /api/sessions/<uuid>/agent/pong HTTP/1.1
…
Accept: text/event-stream
…
      "role": "user",
      "content": "ping"
…
HTTP/1.1 200 OK
…
Content-Type: text/event-stream
The ping exchange, the final session read, the Host and Date headers, and most fields of the session object are cut.

The agent identifier in the path is the file name without .yaml, so pong, while the events name the agent root docs-agent API Server. The run body is a messages array with an optional model field, which sets a model override for that agent in the session docs-agent API Server. A new session has tools_approved: false. A tool call then raises tool_call_confirmation, and the client answers with POST /api/sessions/:id/resume docs-agent API Server.

The run stream

the ten frames of one turncapture/out/23-api-run.ssesse
data: {"agent_name": "root", "available_agents": [{"description": "Answers every message with the word pong.", "model": "ai/qwen3:4b", "name": "root", "provider": "dmr"}], "current_agent": "root", "timestamp": "<ts>", "type": "team_info"}
data: {"agent_name": "root", "available_tools": 0, "loading": false, "timestamp": "<ts>", "type": "toolset_info"}
data: {"message": "ping", "session_id": "<uuid>", "session_position": 0, "timestamp": "<ts>", "type": "user_message"}
data: {"agent_name": "root", "session_id": "<uuid>", "timestamp": "<ts>", "type": "stream_started"}
data: {"agent_name": "root", "available_tools": 0, "loading": false, "timestamp": "<ts>", "type": "toolset_info"}
data: {"agent_name": "root", "description": "Answers every message with the word pong.", "model": "dmr/ai/qwen3:4b", "timestamp": "<ts>", "type": "agent_info"}
data: {"agent_name": "root", "content": "pong", "message_id": "<uuid>", "session_id": "<uuid>", "timestamp": "<ts>", "type": "agent_choice"}
data: {"agent_name": "root", "session_id": "<uuid>", "timestamp": "<ts>", "type": "message_added"}
data: {"agent_name": "root", "session_id": "<uuid>", "timestamp": "<ts>", "type": "token_usage", …}
data: {"agent_name": "root", "finish_reason": "stop", "reason": "normal", "session_id": "<uuid>", "timestamp": "<ts>", "type": "stream_stopped"}
The usage object of the token_usage frame is cut. The kit dropped the reasoning frames before it wrote the file.

The docs list eight event types, from stream_started to error docs-agent API Server. The capture adds six more: team_info, toolset_info, user_message, agent_info, message_added, and token_usage. The pong agent has no tools, so no tool_call frame appears. The last request of the file reads the session back, and the stored assistant message keeps the model's reasoning_content beside the answer (capture/out/23-api.http). Figure 6.1 draws the whole exchange.

Fig. 6.1one serve api session and one streamed runsequence
One serve api session and one streamed run Four lifelines: curl on the host, docker-agent serve api on 127.0.0.1:8080 serving pong.yaml, the session database work/api-session.db, and the cassette 23-api that --fake replays in place of ai/qwen3:4b. curl lists the agents and gets pong, creates a session with an empty body, and posts the message ping to /api/sessions/<uuid>/agent/pong with Accept text/event-stream. The server streams team_info, toolset_info, user_message, and stream_started, makes one chat completion that the cassette answers with pong, then streams agent_choice, message_added, token_usage, and stream_stopped. A last GET reads the stored session with both messages. curl serve api :8080 api-session.db cassette 23-api 1 GET /api/agents 2 name: pong, multi: false 3 POST /api/sessions {} 4 session <uuid> 5 200, id <uuid> tools_approved: false 6 POST /api/sessions/<uuid>/agent/pong messages: user ping 7 team_info, toolset_info, user_message 8 stream_started 9 chat completion, dmr/ai/qwen3:4b --fake replays the recorded answer 10 pong, finish_reason stop 11 agent_choice: pong 12 message_added, token_usage 13 stream_stopped, reason normal 14 GET /api/sessions/<uuid> 15 messages: ping, pong with reasoning_content
A session is created first and stored, and one POST to the agent path returns the whole turn as SSE frames from team_info to stream_stopped. Read top to bottom. Solid ink arrows are requests and dashed ink arrows are replies. Dotted indigo arrows are SSE frames, the teal arrow stores the session, and the plum arrow is the model call that the cassette answers. From capture/out/23-api.http and 23-api-run.sse.

A run is one request, and the session outlives it. /steer injects messages into a running turn, /followup queues them with an optional Idempotency-Key, and /fork copies a session up to a user message docs-agent API Server. GET /api/sessions/:id/events is a session-wide stream that resumes from Last-Event-ID or ?since= docs-agent API Server. An interactive docker-agent run --listen ADDR serves the same control plane, with a fixed 1 MiB body limit and no --auth-token. The help does not list that flag, and each such run writes <data-dir>/runs/<pid>.json for discovery docs-agent API Server.

serve chat: the OpenAI-compatible subset

serve chat: the command that "exposes the agent through an OpenAI-compatible API at /v1/chat/completions and /v1/models" help-agent docker-agent serve chat. The model id is the agent name, so the capture lists root, the name inside the file, where serve api used pong, the file name.

the model list, one completion, and a request without the tokencapture/out/23-chat.httphttp
GET /v1/models HTTP/1.1
…
Authorization: Bearer m101-chat-key
…
      "id": "root",
…
      "owned_by": "docker-agent",
…
POST /v1/chat/completions HTTP/1.1
…
        "role": "assistant",
        "content": "pong"
…
HTTP/1.1 401 Unauthorized
…
    "message": "missing or invalid bearer token",
Headers, ids, timestamps, and the usage object are cut.

By default the server keeps no conversation, so each request carries the full history. --conversations-max caches up to N conversations keyed by X-Conversation-Id, and --conversation-ttl evicts them after 30 minutes help-agent docker-agent serve chat. A failed turn leaves a cached conversation unchanged, so a client can retry with the same id docs-agent Chat Server. With stream: true in the body, the reply is an SSE stream of chat.completion.chunk deltas docs-agent Chat Server. --max-idle-runtimes keeps four idle runtimes per agent, and --request-timeout bounds each request at five minutes, model and tool calls included help-agent docker-agent serve chat. A listener that is not on loopback needs --api-key, --api-key-env, or --insecure-no-auth docs-agent Chat Server.

serve apiserve chat
Default address127.0.0.1:8080127.0.0.1:8083
Token flag--auth-token--api-key or --api-key-env
Agent id in the capturepong, the file nameroot, the agent name
Statesessions in -s, default session.dbnone, or a cache keyed by X-Conversation-Id
Tool approvaltool_call_confirmation, then resume--safety, default restricted docs-agent Chat Server
Cassettes--fake and --recordnone

Sources:help-agent docker-agent serve, serve api, serve chat (research/sources/help-docker-agent.md); docs-agent API Server, Chat Server (research/sources/docs-docker-agent.md); rel-agent v1.23.4, v1.53.0 (research/sources/docker-agent-CHANGELOG.md); research/conflicts-register.md row C63; research/plan.md, Part 6; capture/README.md; capture/fixtures/agents/pong.yaml; capture/out/23-serve-api.log, 23-api.http, 23-api-run.sse, 23-chat.http

6.2

serve mcp and serve acp

serve mcp exposes each agent as one MCP tool over stdio or streaming HTTP on port 8081, and serve acp speaks the Agent Client Protocol to an editor over stdio.

Your MCP client already calls tools, and your editor already talks to coding agents. Neither of them reads an agent file. serve mcp turns the agent into a tool for the first, and serve acp turns it into an editor agent for the second.

When you finish this section, you can serve an agent as an MCP tool, read its schema, and open an ACP session from an editor.

serve mcp: one agent, one tool

serve mcp: "Start an MCP server that exposes the agent via the Model Context Protocol. By default, uses stdio transport" help-agent docker-agent serve mcp. --http switches to streaming HTTP on 127.0.0.1:8081. Without -a, every agent of a team file becomes its own tool, and --tool-name renames the tool when one agent is exposed help-agent docker-agent serve mcp.

--attach exposes the session of a running TUI, found by pid, address, or session id. --mcp-keepalive works over stdio only, while --auth-token, --insecure-no-auth, and --safety work with --http only help-agent docker-agent serve mcp. Over HTTP the safety policy defaults to restricted docs-agent MCP Mode.

The capture ran serve mcp fixtures/agents/pong.yaml --http --listen 127.0.0.1:8081 --tool-name pong, with the file of serve api and serve chat:

initialize, tools/list, and tools/call against /mcpcapture/out/23-mcp.httphttp
POST /mcp HTTP/1.1
…
  "method": "initialize",
  "params": {
    "protocolVersion": "2026-07-28",
…
Content-Type: text/event-stream
…
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"logging":{},"tools":{"listChanged":true}},"protocolVersion":"2025-11-25","serverInfo":{"name":"docker agent","version":"v1.149.0"}}}
…
data: {"jsonrpc":"2.0","id":2,"result":{"ttlMs":0,"cacheScope":"public","tools":[{"annotations":{"destructiveHint":false,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":true,…"inputSchema":{…"properties":{"message":{"description":"the message to send to the agent","type":"string"}},"required":["message"],"type":"object"},"name":"pong","outputSchema":{…
…
data: {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"{\"response\":\"pong\"}"}],"structuredContent":{"response":"pong"}}}
Headers, the client info, and the notifications/initialized exchange are cut. The tools/list line is cut inside the tool definition.

The client asked for revision 2026-07-28, which v1.129.0 added with "stateless Streamable HTTP transport" rel-agent v1.129.0. The server answered 2025-11-25, so read the version from the result, never from your request. No response carried an Mcp-Session-Id header. Each answer came as one SSE message event, and the notification got 202 Accepted (capture/out/23-mcp.http).

The tool takes one string, message, and returns structuredContent.response, with the same text as a JSON string in content. Its annotations mark it read-only and idempotent, and its title is the agent's description. The tools/call answer came as one event after the whole turn, with no progress notification before it. The kit README adds that the server answered on / as well as /mcp (capture/README.md). The docs register the stdio form in Claude Code with claude mcp add --transport stdio, followed by -- docker agent serve mcp and the agent reference docs-agent MCP Mode.

serve acp: JSON-RPC lines to an editor

serve acp: a server that "communicates over stdio (standard input/output)" docs-agent ACP. The editor spawns docker-agent serve acp FILE, writes JSON-RPC requests to its stdin, and reads responses and notifications from its stdout. Sessions persist in <data-dir>/session.db unless -s names another file help-agent docker-agent serve acp. A team file works as well, and the docs say its sub-agents need no change for ACP docs-agent ACP.

initialize and session/new over stdiocapture/out/23-acp.jsonljsonl
>> {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": 1, …}}
<< {"id": 1, "jsonrpc": "2.0", "result": {"agentCapabilities": {"auth": {"logout": {}}, "loadSession": true, …, "agentInfo": {"name": "docker agent", "title": "docker agent", "version": "v1.149.0"}, …, "protocolVersion": 1}}
>> {"jsonrpc": "2.0", "id": 2, "method": "session/new", "params": {"cwd": "$CAPTURE", "mcpServers": []}}
<< {"jsonrpc": "2.0", "method": "session/update", "params": {"sessionId": "<uuid>", "update": {"availableCommands": [{"description": "Summarize and compact session history", …, "name": "compact"}, {"description": "Display current context token usage and session cost", "name": "usage"}], "sessionUpdate": "available_commands_update"}}}
<< {"id": 2, "jsonrpc": "2.0", "result": {"configOptions": [{"category": "mode", "currentValue": "default", …}], "modes": {…, "currentModeId": "default"}, "sessionId": "<uuid>"}}
Lines marked >> were written to stdin, lines marked << were read from stdout. Each object is cut inside, at each mark.

initialize returns agentCapabilities: loadSession, MCP servers over HTTP and SSE, audio, image, and embedded-context prompts, and the session operations close, delete, list, and resume. It offers one auth method, host-credentials, which v1.144.0 added together with session deletion rel-agent v1.144.0. Before the session/new result, a session/update notification lists the slash commands. Release v1.143.0 announced /compact, /usage, and /new rel-agent v1.143.0, and the captured list has no new.

The session starts in mode default, which auto-approves read-only tools and asks for the rest. The mode list adds default to the four values of --safety. A client changes the mode with session/set_config_option or the older session/set_mode rel-agent v1.144.0. The capture stops at session/new, so it shows no prompt turn over ACP. The docs sketch the host side with a method agent/run and call that code pseudocode docs-agent ACP. Use the method names of the capture.

Fig. 6.2one agent file served over ACP and over MCPflow
One agent file served over ACP and over MCP Top band: an editor spawns docker-agent serve acp pong.yaml and writes JSON-RPC lines to its stdin. initialize returns agentCapabilities and agentInfo docker agent v1.149.0 at protocolVersion 1. session/new first sends a session/update notification with the slash commands compact and usage, then the result with configOptions and the mode default. Bottom band: curl posts to serve mcp --http at 127.0.0.1:8081/mcp. initialize asks for 2026-07-28 and gets 2025-11-25, notifications/initialized gets 202 Accepted, tools/list returns one tool named pong with one message argument, and tools/call returns structuredContent.response pong. Both servers run the agent root of pong.yaml on dmr/ai/qwen3:4b. ACP OVER STDIO editor ACP client serve acp stdin and stdout stdio >> initialize, protocolVersion 1 << agentCapabilities, agentInfo docker agent >> session/new, cwd, mcpServers [] << session/update: commands compact, usage << result: configOptions, currentModeId default MCP OVER STREAMING HTTP curl MCP client serve mcp --http 127.0.0.1:8081/mcp HTTP POST >> initialize, asks 2026-07-28 << protocolVersion 2025-11-25 >> notifications/initialized, 202 Accepted >> tools/list << one tool pong(message) >> tools/call << structuredContent.response pong.yaml agent root dmr/ai/qwen3:4b temperature 0
The same pong.yaml answers an editor through stdin and stdout and an MCP client through HTTP POSTs to port 8081, with different method names on each path. Read each band left to right. The boxes under each band list the captured messages in order, with >> for a request and << for a response or notification. From capture/out/23-acp.jsonl and 23-mcp.http.

For the protocol under serve mcp, read the MCP transports lesson.

Sources:help-agent docker-agent serve mcp, serve acp (research/sources/help-docker-agent.md); docs-agent MCP Mode, ACP (research/sources/docs-docker-agent.md); rel-agent v1.129.0, v1.143.0, v1.144.0 (research/sources/docker-agent-CHANGELOG.md); research/plan.md, Part 6; capture/README.md; capture/fixtures/agents/pong.yaml; capture/out/23-mcp.http, 23-acp.jsonl

6.3

serve a2a

serve a2a publishes an A2A 1.0 agent card on port 8082 and answers SendMessage with a completed task, and its card breaks three REQUIRED rules of A2A 1.0.1.

A planner on another team speaks A2A and wants to give your agent work. It will fetch a card, send one message, and read a task, as the A2A 101 manual teaches in SendMessage.

When you finish this section, you can serve an agent over A2A, call it with 1.0 names, and predict where a strict client objects.

The card at the well-known path

serve a2a: "Start an A2A server that exposes the agent via the Agent-to-Agent protocol" help-agent docker-agent serve a2a. It listens on 127.0.0.1:8082, and -a "defaults to the team's first agent" help-agent docker-agent serve a2a, where the legacy plugin named root (conflict C64). --auth-token covers the card and every invocation, and a listener off loopback needs it or --insecure-no-auth docs-agent A2A Protocol. Sessions default to the restricted safety policy docs-agent A2A Protocol.

The Docker Agent docs name neither the card path nor the protocol version (conflict C71). The capture found the card at /.well-known/agent-card.json. /.well-known/agent.json answered 404, and so did a POST to / (capture/README.md).

the agent card that serve a2a published for pong.yamlcapture/out/23-agent-card.jsonjson
{
  "supportedInterfaces": [
    {
      "url": "http://127.0.0.1:8082/invoke",
      "protocolBinding": "JSONRPC",
      "protocolVersion": "1.0"
    }
  ],
  "capabilities": {
    "streaming": true
  },
  "defaultInputModes": [],
  "defaultOutputModes": [],
  "description": "Answers every message with the word pong.",
  "name": "pong",
  "skills": [
    {
      "description": "Answers every message with the word pong.",
      "id": "pong_",
      "name": "",
      "tags": [
        "llm",
        "docker agent"
      ]
    }
  ],
  "version": "v1.149.0"
}

The card names the file, pong, where serve chat used the agent name root. Its one skill has the id pong_ and the description of the agent. The response also carried Access-Control-Allow-Origin: *, although the run set no --cors-origin and the help says "empty disables CORS" help-agent docker-agent serve a2a.

SendMessage and GetTask

The card points to one JSON-RPC interface, http://127.0.0.1:8082/invoke, at protocol version 1.0. The capture sent SendMessage there with A2A-Version: 1.0 and no configuration:

SendMessage with the 1.0 names, then GetTaskcapture/out/23-a2a.httphttp
POST /invoke HTTP/1.1
…
A2A-Version: 1.0
…
  "method": "SendMessage",
  "params": {
    "message": {
      "messageId": "m101-0001",
      "role": "ROLE_USER",
      "parts": [
        {
          "text": "ping"
        }
…
  "result": {
    "task": {
…
      "artifacts": [
        {
          "artifactId": "<uuid>",
          "metadata": {
            "adk_partial": true
          },
          "parts": [
            {
              "data": {},
…
        {
          "artifactId": "<uuid>",
          "parts": [
            {
              "text": "pong"
…
      "status": {
        "state": "TASK_STATE_COMPLETED",
…
  "method": "GetTask",
  "params": {
    "id": "<uuid>"
  }
Headers, ids, the history, the metadata, and the GetTask reply are cut.

The send returned a task already in TASK_STATE_COMPLETED, and GetTask returned the same task with its history (capture/out/23-a2a.http). The answer pong sits in the second artifact, and history keeps only the user message. The server made one live call to ai/qwen3:4b, because serve a2a has no --fake. The card sets streaming: true, so SendStreamingMessage is on offer, but the capture did not call it. Figure 6.3 draws the exchange.

Sessions persist in the session database since v1.59.0, and a client resumes one through /invoke with its A2A context rel-agent v1.59.0. A context id that collides with another session is rejected, and that session stays unchanged docs-agent A2A Protocol. Sessions that older binaries stored are labelled run and cannot be resumed over A2A docs-agent A2A Protocol.

Fig. 6.3one A2A card, one SendMessage, one GetTasksequence
One A2A card, one SendMessage, one GetTask Three lifelines: curl on the host, docker-agent serve a2a on 127.0.0.1:8082 serving pong.yaml, and Docker Model Runner on localhost:12434. curl reads the agent card at /.well-known/agent-card.json, which names one JSON-RPC interface at /invoke with protocol version 1.0. curl posts SendMessage with A2A-Version 1.0 and a ROLE_USER message ping. The server makes one live chat completion on ai/qwen3:4b, which answers pong, moves the task to TASK_STATE_COMPLETED, and returns result.task with the answer in an artifact. GetTask with the task id returns the same task with its history. curl serve a2a :8082 DMR :12434 1 GET /.well-known/agent-card.json 2 card: /invoke, JSONRPC, 1.0 3 POST /invoke SendMessage A2A-Version: 1.0, ROLE_USER, ping 4 chat completion, ai/qwen3:4b 5 pong 6 TASK_STATE_COMPLETED 7 result.task, artifact text pong 8 POST /invoke GetTask, id <uuid> 9 the same task, with history
The client reads the card, sends one blocking SendMessage that makes one model call, and gets back a completed task that GetTask returns again. Read top to bottom. Solid ink arrows are requests and dashed ink arrows are replies. The plum arrow is the live model call, and the amber box is the task state the reply carries. From capture/out/23-a2a.http.

Where it differs from A2A 1.0.1

The A2A 101 manual of this repository states the 1.0.1 rules: AgentCard fields for the card and SendMessage for the call. The table holds each captured value against those rules.

What serve a2a returnedA2A 1.0.1, as A2A 101 states itResult
the card at /.well-known/agent-card.jsonthe well-known path of spec §8.2match
one interface: /invoke, JSONRPC, protocolVersion: "1.0"url, protocolBinding, and protocolVersion are REQUIRED, and the version is Major.Minormatch
SendMessage, GetTask, ROLE_USER, TASK_STATE_COMPLETEDPascalCase method names and the 1.0 enum namesmatch
a send with no configuration returned a terminal taska send blocks until a terminal or interrupted statematch
skills[0].name is an empty stringeach skill needs an id, a name, a description, and one tagdiffers: no skill name
defaultInputModes and defaultOutputModes are empty listsa REQUIRED list holds at least one elementdiffers: two empty lists
version is v1.149.0version is the agent's own releasediffers: the docker-agent release
task metadata keyed by https://google.github.io/adk-docs/a2a/a2a-extension/an extension is declared in capabilities.extensions, and its data is keyed by its URIdiffers: the card declares no extension
an artifact whose one part is an empty data object, marked adk_partialan artifact needs a unique artifactId and at least one partallowed, but empty

The adk_* keys come from the A2A library the server is built on, which v1.129.0 moved to adka2a/v2 rel-agent v1.129.0. The docs list four limitations, among them "A2A artifact support not yet integrated" docs-agent A2A Protocol (conflict C70). The capture contradicts that one, because the answer arrives as an artifact. The other three cover tool events, memory, and sub-agents, and the pong agent has none of them, so the capture cannot test them.

The a2a toolset is the client side

An agent file calls a remote A2A agent with a type: a2a toolset. It takes a url, an optional name, and headers, such as a bearer token for --auth-token docs-agent A2A Tool. The schema adds allow_private_ips, to "Opt in to dialling non-public IP addresses (valid for type 'fetch', 'api', 'openapi', 'a2a', and remote MCP toolsets)" schema allow_private_ips. Without it, the client refuses loopback on the direct path, so a call to 127.0.0.1:8082 needs allow_private_ips: true. The capture did not run that toolset. For the protocol itself, read the A2A lesson.

Sources:help-agent docker-agent serve a2a (research/sources/help-docker-agent.md); docs-agent A2A Protocol, A2A Tool (research/sources/docs-docker-agent.md); schema allow_private_ips (research/sources/agent-schema.json); rel-agent v1.59.0, v1.129.0 (research/sources/docker-agent-CHANGELOG.md); research/conflicts-register.md rows C64, C70, C71; research/plan.md, Part 6; manuals/a2a-101/sections/2-1-agentcard-fields.md, 4-1-send-message.md, 2-2-discovery-and-supported-interfaces.md, 3-3-artifact-and-chunks.md, 5-1-json-rpc.md, 6-2-extensions.md; capture/README.md; capture/fixtures/agents/pong.yaml; capture/out/23-agent-card.json, 23-a2a.http

6.4

share push and share pull

share push stores an agent file as an OCI artifact with docker-agent annotations, and share pull or run with the same reference reads it back from any registry.

A teammate asks for the agent you used yesterday. A YAML file in a chat message has no version and no address to pull it from. A registry reference has both, but it does not prove who published the file. Docker Agent pushes agent files to OCI registries the way Docker pushes images.

When you finish this section, you can push an agent file, read its manifest, pull it back, and run it by reference.

Push to a registry

share push: "Push an agent configuration file to an OCI registry" help-agent docker-agent share push. The capture pushed files.yaml, an agent with the filesystem and shell toolsets, to a registry:2 container on localhost:15000:

one push to a local registrycapture/out/24-share-push.txttext
$ docker-agent share push fixtures/agents/files.yaml localhost:15000/m101/agent:v1
Pushing agent $CAPTURE/fixtures/agents/files.yaml to localhost:15000/m101/agent:v1
Successfully pushed artifact to localhost:15000/m101/agent:v1
[exit 0]

The registry speaks plain HTTP, and the push needed no insecure-registry setting (capture/README.md). Its catalog then listed m101/agent with the tag v1, served as application/vnd.oci.image.manifest.v1+json (capture/out/24-registry.txt).

The manifest

the manifest of m101/agent:v1capture/out/24-manifest.jsonjson
{
  "annotations": {
    "io.docker.agent.version": "v1.149.0",
    "io.docker.cagent.version": "v1.149.0",
    "org.opencontainers.image.created": "<ts>",
    "org.opencontainers.image.description": "OCI artifact containing files.yaml"
  },
  "artifactType": "application/vnd.docker.agent.config.v1+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": "<n>",
    "digest": "sha256:<digest>"
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": "<n>",
      "digest": "sha256:<digest>"
    }
  ],
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "schemaVersion": 2
}

artifactType: the field of an OCI manifest that names what the artifact is. Here it marks a Docker Agent config, while the config and the one layer keep Docker image media types. io.docker.agent.version records the release that pushed the file. io.docker.cagent.version repeats it, because v1.23.3 renamed the annotation "while maintaining backward compatibility with the old annotation" rel-agent v1.23.3.

A file that the agent reads through instruction_file travels inside the artifact. On push, "the file contents are inlined into the pushed artifact, so the published agent stays self-contained" docs-agent Agent Configuration. Figure 6.4 shows the push, the manifest, and the two ways back.

Fig. 6.4the agent file as an OCI artifactstructure
The agent file as an OCI artifact Top: docker-agent share push sends fixtures/agents/files.yaml to the local registry localhost:15000 as m101/agent:v1. share pull writes an identical copy named localhost:15000_m101_agent:v1.yaml into the current directory, and docker-agent run with the reference answers README.md has 1 line. Bottom: the manifest the registry serves. Its mediaType is the OCI image manifest, its artifactType is application/vnd.docker.agent.config.v1+json, and the config and the one layer keep Docker image media types. Four annotations: io.docker.agent.version and io.docker.cagent.version, both v1.149.0, and the OCI created and description keys. With --key, the proof is recorded as more annotations, which this capture did not do. files.yaml agent root filesystem, shell localhost:15000 m101/agent:v1 plain HTTP registry:2 share pull identical YAML in the current dir run by reference README.md has 1 line. share push share pull run manifest of m101/agent:v1, as the registry serves it mediaType application/vnd.oci.image.manifest.v1+json artifactType application/vnd.docker.agent.config.v1+json config application/vnd.docker.container.image.v1+json layers[0] application/vnd.docker.image.rootfs.diff.tar.gzip annotations io.docker.agent.version v1.149.0 io.docker.cagent.version v1.149.0, the legacy key org.opencontainers.image.created <ts> org.opencontainers.image.description OCI artifact containing files.yaml with --key: a signature or MAC is added as annotations, not in this capture
One share push stores files.yaml as an OCI manifest with four annotations, and share pull and run both read it back by the same reference. Read the top row left to right, then the manifest fields from top to bottom. Violet marks the agent file and the annotations docker-agent writes, and grey marks the registry and the manifest fields. The dashed box is the --key proof, which this capture did not record. From capture/out/24-share-push.txt, 24-registry.txt, 24-manifest.json, 24-share-pull.txt, and 24-run-ref.txt.

--key and --encrypt

The capture pushed without --key, so its manifest carries no proof. The flag arrived in v1.132.0 rel-agent v1.132.0, and it takes a key inline or as a file:// path help-agent docker-agent share push. With --key, the proof goes into manifest annotations, and "the YAML itself is always pushed in clear" help-agent docker-agent share push. A PEM or OpenSSH key (Ed25519, ECDSA, or RSA) records a signature. Any other value is a symmetric secret of at least 16 bytes and records a MAC help-agent docker-agent share push.

--encrypt also embeds an encrypted copy of the whole YAML, and Ed25519 keys cannot encrypt help-agent docker-agent share push. Since v1.138.1 the signature covers "a DSSE-wrapped in-toto statement instead of raw YAML bytes" rel-agent v1.138.1. Neither the help nor this capture names the proof annotations, so this manual does not print them.

On the other side, share pull --key checks the signature or the MAC, or decrypts the copy and compares it. "The pull fails if the artifact is unprotected or the check does not pass" help-agent docker-agent share pull. Both commands read DOCKER_AGENT_ENCRYPT_KEY in place of the flag.

Pull and run by reference

pull into the current directorycapture/out/24-share-pull.txttext
$ docker-agent share pull localhost:15000/m101/agent:v1 --force  (in work/)
Pulling agent localhost:15000/m101/agent:v1
Agent saved to localhost:15000_m101_agent:v1.yaml
[exit 0]

share pull names the file after the reference, with / replaced by _, and --force overwrites an earlier copy. The pulled YAML is byte for byte the pushed files.yaml (capture/out/24-share-pull-agent.yaml). run takes the reference in place of a path, and the replayed run answered from the registry copy:

run straight from the referencecapture/out/24-run-ref.txttext
$ docker-agent run --exec --last --working-dir fixtures/repo --fake work/cassettes/18-files localhost:15000/m101/agent:v1 …
README.md has 1 line.
[exit 0]
The command line is cut after the reference.

A reference also works as a sub_agents entry. "Tag references are checked against the registry on every docker agent run" docs-agent Agent Distribution, so pin each one to a digest, myorg/agent@sha256:…, to start from cache. serve api --pull-interval N pulls a reference again every N minutes help-agent docker-agent serve api. For local work, run also takes a plain http://localhost URL to an agent file, with no registry at all docs-agent Agent Distribution.

A private repository needs docker login first. Docker Agent forwards a Docker token by itself only for HTTPS URLs under docker.com, and docker.io is not one of them docs-agent Agent Distribution. So a private Hub repository needs docker login docker.io. Older material points to the agentcatalog namespace on Docker Hub, and v1.116.0 removed the references to that "discontinued agentcatalog Docker Hub namespace" rel-agent v1.116.0.

Sources:help-agent docker-agent share push, share pull, serve api (research/sources/help-docker-agent.md); docs-agent Agent Configuration, Agent Distribution (research/sources/docs-docker-agent.md); rel-agent v1.23.3, v1.116.0, v1.132.0, v1.138.1 (research/sources/docker-agent-CHANGELOG.md); research/plan.md, Part 6; capture/README.md; capture/fixtures/agents/files.yaml; capture/out/24-share-push.txt, 24-registry.txt, 24-manifest.json, 24-share-pull.txt, 24-share-pull-agent.yaml, 24-run-ref.txt

6.5

DMR, Compose models, and the docker/mcp-gateway service

A local model is an unauthenticated OpenAI-compatible endpoint on port 12434, Compose binds it to a service as two variables, and four things named gateway stay distinct.

Every model call in Part 5 and in this part ran with no API key and no cloud account. The model was ai/qwen3:4b on Docker Model Runner, on the same Mac as the agent.

When you finish this section, you can reach the runner from the host, a container, and a sandbox, and give it to a Compose service.

Docker Model Runner on port 12434

Docker Model Runner (DMR): the Docker component that pulls models from Docker Hub, an OCI registry, or Hugging Face docs-dmr Docker Model Runner. It serves them over OpenAI, Anthropic, and Ollama compatible APIs docs-dmr DMR REST API. Since Desktop 4.71.0, "Docker Model Runner is now disabled by default and must be explicitly enabled in Settings" docs-desktop 4.71.0. docker desktop enable model-runner --tcp <port> turns on host TCP docs-dmr DMR REST API, and the capture used port 12434.

the runner as docker model status --json reports itcapture/out/25-model-status.jsonjson
{
  "running": true,
  "backends": {
…
    "llama.cpp": "Running: llama.cpp b9879-metal (sha256:<digest>) 72874f5",
…
  },
  "kind": "Docker Desktop",
  "endpoint": "http://model-runner.docker.internal/v1/",
  "endpointHost": "http://localhost/exp/vDD4.40/v1/"
}
The other backends are cut.

"The Model Runner API is not authenticated" docs-dmr Docker Model Runner. Any client that reaches the port can pull, load, and run models. The base URL depends on where the caller runs, and figure 6.5 maps the five that this manual met:

CallerBase URLEvidence
a process on the hosthttp://localhost:12434capture/out/25-models.json
a container on Docker Desktophttp://model-runner.docker.internalcapture/out/26-compose-up.txt
a container on Docker Enginehttp://172.17.0.1:12434docs-dmr DMR REST API, not captured
a process on the host, through the Docker sockethttp://localhost/exp/vDD4.40capture/out/25-model-status.json, endpointHost
docker-agent inside a sandbox, through its proxyhttp://host.docker.internal:12434capture/out/27-inside-run.txt

After the base URL come path prefixes and whole endpoints. The OpenAI paths sit under /engines/v1/, and /engines/llama.cpp/v1/ names the engine docs-dmr DMR REST API. The Anthropic table lists /anthropic/v1/messages, while its examples call /v1/messages (conflict C79). No capture tested either path. Ollama clients use /api/. The capture also met the /v1/ root, which status reports as endpoint.

Fig. 6.5five base URLs for one Docker Model Runnerstructure
Five base URLs for one Docker Model Runner Left: five callers of Docker Model Runner. docker-agent on the host uses http://localhost:12434 once host TCP is enabled. The Compose service printer gets http://model-runner.docker.internal, injected as LLM_URL with the /v1/ path. A container on Docker Engine uses http://172.17.0.1:12434, from the docs and not captured. A host process that talks to the Docker socket reaches http://localhost/exp/vDD4.40, the endpointHost of docker model status. docker-agent inside a sandbox uses http://host.docker.internal:12434 through the sandbox proxy. Right: the runner with llama.cpp b9879-metal and ai/qwen3:4b, and what follows the base URL: the path prefixes /engines/v1/ for OpenAI, /engines/llama.cpp/v1/ with the engine name, and /v1/, the whole endpoints /anthropic/v1/messages and /v1/messages for Anthropic, and the prefix /api/ for Ollama. No path checks credentials. Docker Model Runner llama.cpp b9879-metal model ai/qwen3:4b paths after the base URL: /engines/v1/ OpenAI /engines/llama.cpp/v1/ /v1/ status endpoint /anthropic/v1/messages /v1/messages, examples /api/ Ollama docker-agent host process http://localhost:12434 TCP enabled on port 12434 printer Compose service http://model-runner.docker.internal injected as LLM_URL with /v1/ container Docker Engine http://172.17.0.1:12434 from the docs, not captured socket client host, Docker socket http://localhost/exp/vDD4.40 endpointHost in model status docker-agent inside a sandbox http://host.docker.internal:12434 through the sandbox proxy no path checks credentials: any client that reaches the port can run models
Five callers reach the same runner through five base URLs, and every path after them is open to any client that reaches the port. Read each row left to right, from the caller to the runner. The dashed caller comes from the docs and was not captured. The paths on the right follow any base URL. From capture/out/25-model-status.json, 25-models.json, 26-compose-up.txt, capture/fixtures/agents/files-sandbox.yaml, and research/sources/docs-model-runner.md.

The 8B tag ai/qwen3:latest failed to pull on the recording Mac, as docker-agent run, new, and doctor explains (capture/out/25-model-pull-latest.txt). With only the 4B tag pulled, doctor resolves auto to dmr/docker.io/ai/qwen3:4b (capture/out/25-doctor.txt). "By default model-runner unloads idle models after a few minutes" docs-agent Docker Model Runner, and provider_opts.keep_alive changes that time. provider_opts.context_size sets the context window through the runner's _configure endpoint docs-agent Docker Model Runner. No capture ran docker model configure, so conflict C77 stays open.

An agent file can also name the runner in a providers: block. fixtures/agents/dmr.yaml sets base_url: http://localhost:12434/engines/llama.cpp/v1, and its run answered Hello (capture/out/25-run-dmr.txt). An explicit base_url bypasses the --record proxy, so that run is live on every capture (capture/README.md).

Compose models

the Compose file of the capturecapture/fixtures/compose/compose.yamlyaml
name: m101

models:
  llm:
    model: ai/qwen3:4b

services:
  mcp-gateway:
    image: docker/mcp-gateway
    use_api_socket: true
    command: ["--transport=streaming", "--port=8811"]

  printer:
    image: alpine:3.22
    depends_on: [mcp-gateway]
    models:
      llm:
        endpoint_var: LLM_URL
        model_var: LLM_MODEL
…
The command of printer is cut.

models: the top-level Compose element that declares a model, which a service binds by name. The short syntax derives LLM_URL and LLM_MODEL from the name llm, and endpoint_var and model_var choose the names. Compose v2.38 or later is required (research/sources/docs-compose-models.md). docker compose up pulled and configured the model before it started a container:

the model step and what printer sawcapture/out/26-compose-up.txttext
 llm Pulling
 llm Pulled
 llm Configuring
 llm Configured
…
printer-1 | LLM_MODEL=ai/qwen3:4b
printer-1 | LLM_URL=http://model-runner.docker.internal/v1/
printer-1 | {"object":"list","data":[{"id":"docker.io/ai/qwen3:4b","object":"model","created":0,"owned_by":"docker","dmr":{}}]}
printer-1 | wget: server returned error: HTTP/1.1 401 Unauthorized
The container steps, the gateway log, and the exit lines are cut.

Compose injected the /v1/ root, where the plan, read from the model-runner source, expected /engines/v1/. GET ${LLM_URL}models answered from inside the container all the same. Compose cannot start sandboxes (conflict C96), and no capture ran models: on the private engine inside a sandbox (conflict C95).

The docker/mcp-gateway service

The mcp-gateway service runs the open source MCP gateway with the host's Docker API socket, so that it can start MCP servers as containers. In the capture it found no profile and no server, listed 0 tools, and added its own management tools such as mcp-find and mcp-add. It printed Gateway URL: http://localhost:8811/mcp and a bearer token, and the request of printer without that token got 401 (capture/out/26-compose-up.txt). Figure 6.6 draws the stack.

Fig. 6.6the capture compose.yaml with a model and a gatewayflow
The capture compose.yaml with a model and a gateway The top-level models element declares llm with model ai/qwen3:4b, and Compose asks Docker Model Runner to pull and configure it. Inside the Compose project m101, the service printer on alpine:3.22 binds llm with endpoint_var LLM_URL and model_var LLM_MODEL and receives LLM_MODEL=ai/qwen3:4b and LLM_URL=http://model-runner.docker.internal/v1/. Its GET of LLM_URL followed by models answers 200 with the model list. The service mcp-gateway runs docker/mcp-gateway with --transport=streaming --port=8811 and use_api_socket true, starts no server, prints a bearer token, and answers the printer request without that token with 401 Unauthorized. models: llm model: ai/qwen3:4b Docker Model Runner model-runner.docker.internal pull, configure COMPOSE PROJECT M101 printer, alpine:3.22 LLM_MODEL=ai/qwen3:4b LLM_URL=http://model-runner.docker.internal/v1/ endpoint_var, model_var GET LLM_URL models: 200 mcp-gateway, docker/mcp-gateway --transport=streaming --port=8811 use_api_socket: true, no server prints a bearer token, 0 tools 401 Unauthorized Docker Engine API the host socket no server started API socket
Compose pulls the model, injects two variables into printer, and runs the gateway with the API socket, which refuses a request without its bearer token. Read from the models element at the top left. Solid ink arrows are calls and the dashed rose arrow is the refused request. From capture/fixtures/compose/compose.yaml and capture/out/26-compose-config.txt and 26-compose-up.txt.

Four things named gateway

Name in this manualWhat it isAddress in the captures
models gatewaythe address set by --models-gateway or DOCKER_AGENT_MODELS_GATEWAY, to "Route all provider traffic through a models gateway URL" docs-agent A2A Protocolhttp://localhost:12434/engines, which --record with dmr needs (capture/README.md)
sbx MCP gatewayone host-side gateway per sandbox, set up by sbx mcp, separate from the MCP Toolkit docs-sbx MCP gatewayhttp://mcp-gateway.docker.internal/mcp inside the VM
MCP gateway servicedocker mcp gateway run or the docker/mcp-gateway image, mcp v0.44.1 on the recording hosthttp://localhost:8811/mcp inside the service
hosted MCP gatewaythe gateway of Docker AI Governance, "an invite-only feature" docs-mcp MCP Gatewaynot captured

The Toolkit gateway runs each MCP server in its own container. "Containers for MCP tools are limited to 2 GB" docs-mcp MCP Toolkit, and each one gets 1 CPU. A docker-agent toolset with ref: docker:<name> takes that route, to "Run MCP servers as secure Docker containers via the MCP Gateway" docs-agent MCP Tool. The sbx MCP gateway is sbx mcp add, sbx mcp load, and --static-mcp, and the MCP gateways lesson covers the pattern.

Sources:docs-dmr Docker Model Runner, DMR REST API, Get started (research/sources/docs-model-runner.md); docs-desktop 4.71.0 (research/sources/docs-desktop-release-notes.md); docs-agent Docker Model Runner, A2A Protocol, MCP Tool (research/sources/docs-docker-agent.md); docs-mcp MCP Gateway, MCP Toolkit (research/sources/docs-mcp.md); docs-sbx MCP gateway (research/sources/docs-sandboxes.md); research/sources/docs-compose-models.md; research/sources/mcp-gateway-README.md; research/conflicts-register.md rows C77, C79, C82, C84, C88, C91, C94, C95, C96; research/plan.md, Part 6; capture/README.md; capture/fixtures/agents/dmr.yaml, files-sandbox.yaml; capture/fixtures/compose/compose.yaml; capture/out/25-model-status.json, 25-models.json, 25-model-ls.txt, 25-model-pull-latest.txt, 25-doctor.txt, 25-run-dmr.txt, 26-compose-config.txt, 26-compose-up.txt, 26-compose-down.txt, 27-inside-run.txt, 11-static-inside.txt

Part 7

Operating It

The same sandbox runs in the cloud, an organization narrows it with policies and reads its audit log, old commands map to new ones, and the claims are checked.

  1. 7.1sbx --cloud run, sbx move, and sbx ttl
  2. 7.2sbx policy ls --source org, --profile, and the audit JSONL
  3. 7.3From docker sandbox and cagent to sbx and docker-agent
  4. 7.4kit-tck, sbx diagnose, and the claims this manual checked
7.1

sbx --cloud run, sbx move, and sbx ttl

A cloud sandbox is the same microVM on Docker's compute, billed by the second in five shapes, with a 24 hour TTL ceiling and its own secrets and policies.

The agent in m101-demo is halfway through a long build, your laptop goes into a bag, and the sandbox stops with it. sbx move m101-demo --to cloud captures its filesystem and starts it again on Docker's compute, where a clock ends it and the lid does not. The build process does not move with it, so the agent runs the build again. When you finish this section, you can start a sandbox in the cloud, move one in either direction, and set the clock that ends it.

This edition recorded no cloud run, because a cloud sandbox costs money and that run was not approved. Every fact below comes from the help text and the documentation.

The --cloud flag and what it hides

Cloud sandbox: a sandbox that sbx creates through the Cloud Sandboxes API instead of the local sandboxd, selected with the global --cloud flag help-sbx sbx.

the verbs that answer to --cloudresearch/sources/help-sbx-cloud.mdtext
Sandbox Commands:
  attach      Attach to a cloud sandbox, starting it first if it is stopped
  cp          Copy files or directories between a sandbox and the host
  create      Create a sandbox for an agent
  exec        Execute a command inside a sandbox
  ls          List sandboxes
  move        Move a sandbox between local and cloud
  ports       Manage sandbox port publishing
  rm          Remove one or more sandboxes
  run         Run an agent in a sandbox
  stop        Stop one or more sandboxes without removing them
  ttl         Inspect or extend a cloud sandbox's TTL
…
  volume      Manage persistent volumes (cloud-only)
The output of sbx --cloud --help on 2026-10-08, cut to two groups. Absent are daemon, prune, settings, and skills. Only here are attach, ttl, and volume.

The cloud tree drops daemon, prune, settings, and skills, and rm --all is refused with --cloud help-sbx sbx rm (conflict C47). Sandboxes, templates, secrets, volumes, and network policy live in a separate cloud store docs-sbx Compare local and cloud sandboxes.

You need sbx 0.45.1 or later and a pay-as-you-go plan on a Personal or Pro account. The docs page says 0.45.0 and the launch blog says 0.45.1 (conflict C36), and Team and Business accounts are not mentioned (conflict C103). sbx --cloud diagnose checks sign-in, the cloud API, and account access without a local daemon docs-sbx Use cloud sandboxes. Docker Offload, a remote daemon for Docker Desktop, is a different product with no sandbox path (conflicts C98 and C99).

Shapes, prices, and quotas

Without --cpus and --memory a cloud sandbox gets 2 CPUs and 4 GiB help-sbx sbx create. The pair must name one of five billable shapes help-sbx sbx template load. Prices come from the launch blog of 2026-09-24, and the docs tree prints none (conflict C101).

ShapevCPUMemoryPrice per hour, blog of 2026-09-24
micro12048 MiB$0.07
small, the default24096 MiB$0.14
medium48192 MiB$0.28
large816384 MiB$0.56
xl1632768 MiB$1.12

The blog meters compute per second, and the docs confirm half of that: "Compute isn't billed while it is stopped." docs-sbx Use cloud sandboxes The $250 credit in the same blog is promotional. An account starts with 10 concurrent sandboxes, 50 stored sandboxes, 100 volumes, 100 secrets, and 3 images in preparation docs-sbx-api Account quotas.

sbx --cloud run and attach

sbx --cloud run claude --name cloud-project creates the sandbox and attaches to its agent, or restarts the named sandbox when it exists help-sbx sbx run. Ctrl-\ detaches, and sbx --cloud attach cloud-project joins the session again help-sbx sbx attach. sbx --cloud ports cloud-project --publish 8080 returns a public HTTPS URL, so the service behind it needs its own authentication docs-sbx Use cloud sandboxes. Secrets come from sbx --cloud secret set anthropic, and rules from sbx --cloud policy init, which can be run again help-sbx sbx policy init. HTTP method rules, --protocol, and governance profiles are refused in the cloud docs-sbx Manage cloud network policy.

sbx ttl and --on-timeout

TTL: the time a cloud sandbox lives before its timeout action, one hour by default and at most 24 hours from creation help-sbx sbx ttl.

sbx --cloud ttl +2h cloud-project extends the expiration under that ceiling and never shortens it help-sbx sbx ttl. --on-timeout chooses stop, restart, or delete help-sbx sbx create. Omit it, and the server stops a sandbox it can resume and deletes the rest. restart needs a --ttl of at least one hour, and a volume-backed sandbox must use delete docs-sbx Use cloud sandboxes. Since v0.47.0 a stopped sandbox reports stopped, and its clock restarts on resume rel-sbx v0.47.0. A volume is a snapshot taken when the sandbox exits, and the last sandbox to exit overwrites it help-sbx sbx volume.

sbx move

Fig. 7.1what sbx move carries to the cloudflow
What sbx move carries to the cloud Top row, left to right: the local sandbox m101-demo is stopped and captured as one template image, the image is uploaded, and a new cloud sandbox named moved-m101-demo plus a suffix starts from it. Below, the left column lists what travels inside the image: the sandbox filesystem, the kit network rules, the published TCP ports republished under cloud URLs, and the environment variables of the local sandbox. The right column lists what stays on the host: the bind mount, the secrets in the sbx secret store, local policy rules, and running processes, with active HTTP method rules prompting before the move. At the bottom, the destination starts a TTL clock with a one hour default and a 24 hour ceiling. SBX MOVE M101-DEMO --TO CLOUD m101-demo local sandbox under sandboxd stopped while it is captured template image the sandbox filesystem one OCI image, pushed moved-m101-demo plus a unique suffix new id, small shape capture upload TRAVELS INSIDE THE IMAGE STAYS ON THE HOST the sandbox container filesystem files, packages, agent state kit network rules example.org from env-mixin (12-kit-add.txt) published TCP ports republished under cloud HTTPS URLs environment variables part of the local image host bind mount $CAPTURE/fixtures/repo, not in the image secrets in the sbx secret store M101_RECV_KEY, the cloud store is separate local policy rules local:<uuid> rules, cloud policy applies running processes and memory the destination boots from the image copied into the image active HTTP method rules they prompt first, --force keeps the warning TTL on the destination default 1h from the server, hard ceiling 24h from creation --on-timeout stop keeps it, delete removes it, sbx --cloud ttl +2h extends it clock starts
sbx move copies the sandbox filesystem as one image and nothing else, so secrets, mounts, local rules, and processes stay behind while the destination starts a TTL clock. Read left to right along the top, then down. Green boxes travel inside the image, dashed grey boxes stay on the host, and the amber box is the destination's clock. From research/sources/help-sbx.md (sbx move) and docs-sandboxes.md (Move a sandbox), because no cloud run was recorded.

sbx move m101-demo --to cloud captures the filesystem as a template image, uploads it, and creates a sandbox with a new id, named moved-m101-demo plus a suffix help-sbx sbx move. Kit network rules and the local environment travel inside the image. The host bind mount, the secrets in the store, local policy rules, host port bindings, and running processes stay behind help-sbx sbx move. Credential files written by an interactive sign-in are ordinary files, so they travel unless you delete them first docs-sbx Move a sandbox.

HTTP method rules prompt before the move, because the cloud cannot apply them, and --force answers the prompt and keeps the warning. The destination expires after the server's default of one hour unless --ttl and --on-timeout say otherwise. Moving to local can stage up to 32 GiB in the host's temporary directory help-sbx sbx move. The docs also warn that docker exec inside a cloud sandbox can read the VM filesystem instead of the container's (conflict C104). This edition did not confirm it.

Sources:help-sbx sbx, sbx create, sbx run, sbx attach, sbx rm, sbx move, sbx ttl, sbx volume, sbx policy init, sbx template load (research/sources/help-sbx.md); research/sources/help-sbx-cloud.md; docs-sbx Cloud sandboxes, Authenticate cloud agents, Compare local and cloud sandboxes, Move a sandbox, Manage cloud network policy, Use cloud sandboxes (research/sources/docs-sandboxes.md); docs-sbx-api Compute sizes and limits, Account quotas (research/sources/docs-sandboxes-api.md); rel-sbx v0.45.0, v0.45.1, v0.47.0 (research/sources/sbx-releases.md); blog 2026-09-24 and research/conflicts-register.md rows C36, C47, C98, C99, C101, C103, C104; capture/out/01-help-sbx.txt

7.2

sbx policy ls --source org, --profile, and the audit JSONL

An organization writes policies in Docker Home, the daemon pulls them within five minutes, local rules can only narrow, and every decision is written to a rotating JSONL file.

Your security team asks which hosts the agents reached last week, who allowed each one, and whether a developer could have widened the list. On one laptop the answer is sbx policy log, and across a company it is an organization policy with an audit log behind it. When you finish this section, you can say what an organization policy changes on a developer machine and read one audit record.

What an organization can set

Organization policy: a named set of rules written in Docker Home, applied to every local sandbox of the organization or of chosen teams docs-sbx Organization policies. Three kinds exist: Network access, Filesystem access, and MCP access. A network rule is HTTP, one destination with methods and path patterns, or All traffic over TCP, UDP, or both, with Allow or Deny docs-sbx Organization policies. A network policy can require approval, so each destination it allows waits once for the developer's confirmation. An MCP policy is Cedar in the MCP namespace, default deny, where a forbid beats every permit docs-sbx Policy concepts. An organization holds at most 100 policies of 250 rules and 400 KB each docs-sbx Policy concepts.

A change reaches developer machines within 5 minutes, and sbx policy reset forces the pull at the price of every local rule and recorded approval docs-sbx Organization policies. Network rules apply to the next request, filesystem rules only when a sandbox is created, and MCP registration rules at the next sbx mcp add. Sign-in enforcement lists allowedOrgs in a managed com.docker.sbx profile on macOS, the key HKLM\SOFTWARE\Policies\Docker\SBX on Windows, or /etc/docker-sbx/config.json on Linux docs-sbx Sign-in enforcement. sbx login then revokes a credential from any other account. All of this is a separate paid subscription, Docker AI Governance, and local use stays free docs-sbx FAQ.

What an organization cannot do

Governance narrows and never widens. When a policy is active, only organization allow rules grant access, and deny rules from every source still apply docs-sbx Policy concepts. Local and kit allow rules are inactive. The help text for --deny-network says the same:

A local deny even beats an organization approval requirement, so the request is blocked and no prompt appears docs-sbx Policy concepts.

RuleEvaluated under organization governance
Organization allowyes
Organization denyyes
Local allowno
Local denyyes
Kit-defined allowno
Kit-defined denyyes

Governance covers local sandboxes only, and a cloud sandbox uses its own account and sandbox policy, as the cloud section describes docs-sbx Governance. Audit records hold metadata and never prompt content, agent output, or parameter values docs-sbx AI Governance Audit Logs.

This capture has no organization

the organization filters on a machine without governancecapture/out/03-policy-org.txttext
$ sbx policy ls --source org
No policies match the selected filters.
[exit 0]

$ sbx policy ls --include-inactive
POLICY         SOURCE   APPLIES TO   SUMMARY
local-policy   local    all          network: 194 allow; filesystem read: 1 allow; filesystem write: 1 allow
[exit 0]
Both commands as recorded. The second shows the one local policy and no STATUS column, because nothing is inactive.
no profiles from remote governancecapture/out/03-policy-profile-ls.txtjson
{
  "profiles": [],
  "policy_rules_unavailable": false
}
The JSON form only. The text form prints "No policy profiles found".

No Governance: Managed by <org> line appears, and the docs name that line as the test for an active policy docs-sbx Local audit logs. Every policy check in this capture reports "governance": {"active": false} (capture/out/09-check-verbose.json). Profile: a named group of organization policies that a developer assigns with --profile at creation, listed by sbx policy profile ls help-sbx sbx policy profile ls. When no rule matches and no organization governs the machine, the proxy asks for approval. Its 403 body names sbx policy approval ls (capture/out/09-blocked.txt), a command the v0.47.0 help tree does not list (conflict C16).

The audit record

Audit record: one JSON object per policy decision or daemon session event, written by the daemon and never by the CLI docs-sbx Local audit logs. Records exist since v0.32.0 rel-sbx v0.32.0. The daemon writes them only for a signed-in user with an AI Governance license under an enforced organization policy. This capture therefore produced none, and the directory listing planned as capture K32 was not recorded (conflict C37). The sample record in the docs carries the same reason string that sbx policy log printed for m101-policy:

the reason string in the policy logcapture/out/09-policy-log.jsonjson
  "blocked_hosts": [
    {
      "host": "example.com:443",
      "vm_name": "m101-policy",
      "proxy_type": "forward",
      "rule": "no applicable policies for op(action=net:connect:tcp, resource=net:domain:example.com:443)",
      …
      "reason": "No matching allow rule (default deny)"
The one blocked_hosts entry, cut after its reason.
a denied connection in the docs' sample audit recordresearch/sources/docs-sandboxes.mdjson
{
  "audit_event_id": "95e7257f-93c9-4f29-bde7-88830e2dae80",
  "timestamp": "2026-05-28T19:15:00.728933Z",
  "schema_version": "1.82.0",
  "category": "AUDIT_CATEGORY_EVALUATION",
  "decision": "AUDIT_DECISION_DENY",
  …
  "resource_id": "example.com:443",
  "os": "macos",
  "app_version": "v0.31.0",
  "client_name": "sbx",
  "hostname": "host-machine",
  "deny_reason": [
    "no applicable policies for op(action=net:connect:tcp, resource=net:domain:example.com:443)"
  ],
  "action_type": "network_egress",
  "network_egress": { "protocol": "tcp" },
  "agent": "claude"
}
The sample record from the Local audit logs page, cut to the fields the text names. The user, organization, and session fields are cut.

On macOS the files are audit-<utc-timestamp>-<process-uuid>-<seq>.jsonl under ~/Library/Logs/com.docker.sandboxes/sandboxes/auditkit/ docs-sbx Local audit logs. The daemon finalizes a .tmp file into .jsonl every 5 minutes, 1000 events, or 50 MiB, and never deletes one. category is management, evaluation, or execution, and decision is one of five values from AUDIT_DECISION_ALLOW to AUDIT_DECISION_APPROVAL_DENY docs-sbx Audit record reference. action_type names the payload, such as network_egress, http_request with method, host, port, and path, or tool_invocation.

client_name is sbx and hostname names the machine, which is how a GitHub Actions run with runtime: docker-sbx appears blog 2026-08-21. Docker Cloud delivery is on by default and keeps events searchable for 90 days, with CSV export up to 1 000 000 rows docs-sbx View and export audit events. From 0.39.0 on it also forwards to Splunk Cloud, Dynatrace, Datadog, or Sumo Logic docs-sbx SIEM forwarding.

Fig. 7.2from Docker Home to a SIEMflow
From Docker Home to a SIEM Top row: an organization policy written in Docker Home under AI Platform is pulled by sandboxd within five minutes and merged with the local policy, whose 194 allow rules become inactive while its deny rules stay active. Second row: the proxy decides one request, example.com:443 from m101-policy, with the reason no applicable policies for op(action=net:connect:tcp, resource=net:domain:example.com:443), and the decision AUDIT_DECISION_DENY. Third row: the daemon writes one record to an audit-<utc>-<uuid>-<seq>.jsonl file under the auditkit directory, and rotation finalizes the file every 5 minutes, 1000 events, or 50 MiB. Bottom row: Docker Cloud delivery keeps the record searchable for 90 days and forwards it to a SIEM destination. A note says this capture had no organization and wrote no record. WHERE THE RULES COME FROM Docker Home Network, Filesystem, MCP policies, org or team scope sandboxd pulls within 5 minutes sbx policy reset forces it local-policy 194 allow: inactive deny: still applied pull merge the proxy decides: example.com:443 from m101-policy no applicable policies for op(action=net:connect:tcp, resource=net:domain:example.com:443), decision AUDIT_DECISION_DENY effective rules audit-<utc>-<uuid>-<seq>.jsonl ~/Library/Logs/com.docker.sandboxes/sandboxes/auditkit/ written by the daemon under an enforced org policy one record per decision rotation every 5 min, 1000 events, or 50 MiB, .tmp to .jsonl Docker Cloud delivery (default on) searchable 90 days, CSV up to 1 000 000 rows app.docker.com, AI Platform, Audit logs SIEM destination Splunk Cloud, Dynatrace, Datadog, Sumo Logic needs sbx 0.39.0 and cloud delivery upload this capture: no organization, no license, no record written (03-policy-org.txt)
One policy decision becomes one JSONL record on the developer machine, and only an enforced organization policy makes the daemon write it. Read top to bottom. The daemon pulls the organization policy, merges it with the local deny rules, and decides each request at the proxy. The record goes to the auditkit directory, where rotation finalizes it and cloud delivery forwards it. From research/sources/docs-sandboxes.md (Local audit logs, Audit record reference) and capture/out/09-policy-log.json.

Sources:docs-sbx Governance, Organization policies, Policy concepts, Local policy, Monitoring policies, AI Governance Audit Logs, Local audit logs, Audit record reference, Configure audit delivery, SIEM forwarding, View and export audit events, Sign-in enforcement, FAQ (research/sources/docs-sandboxes.md); help-sbx sbx create, sbx policy ls, sbx policy profile ls (research/sources/help-sbx.md); rel-sbx v0.32.0, v0.39.0 (research/sources/sbx-releases.md); blog 2026-08-21 and research/conflicts-register.md rows C16, C28, C37, C41; capture/out/03-policy-org.txt, 03-policy-profile-ls.txt, 09-check-verbose.json, 09-policy-log.json, 09-blocked.txt

7.3

From docker sandbox and cagent to sbx and docker-agent

Two renames and one CLI restructure changed every command in the 2025 and early 2026 material, and this section maps each old line to its current form.

A blog post from 2026-02-23 tells you to run docker sandbox run claude and to point the proxy at host.docker.internal:3128. A talk from 2025-09-24 shows cagent run agent.yaml and cagent push. On a machine with Docker Desktop 4.94.0, sbx 0.47.0, and docker-agent 1.149.0, neither command exists. When you finish this section, you can map any command from that material to its current form and name the version that changed it.

Two tracks and three removals

The sandbox product had four lives. The plan's timeline dates a container-based docker sandbox run to Docker Desktop 4.50.0 on 2025-11-06, and the preview blog followed on 2025-11-25 blog 2025-11-25. Desktop 4.58.0 replaced it with microVMs on 2026-01-26 docs-desktop 4.58.0, and Desktop 4.61.0 bundled plugin v0.12.0 on 2026-02-18 docs-desktop 4.61.0. That plugin is the one whose help this section quotes. The standalone sbx binary had its first public tag, v0.21.0, on 2026-03-31 rel-sbx v0.21.0. Desktop 4.80.0 then ended the plugin on 2026-06-29:

No docker sbx command exists in any help tree, and the binary is sbx (conflict C1).

The agent product had three names. It launched as cagent with a blog on 2025-09-18 blog 2025-09-18, and Desktop 4.49.0 bundled it on 2025-10-23. The Desktop notes for 4.49.0 now read "Docker Agent is now available through Docker Desktop." docs-desktop 4.49.0, under the current name. The agent docs say "In Docker Desktop versions 4.49 through 4.62, this feature was called cagent." docs-agent Installation, and the docker agent plugin arrived with v1.23.3 on 2026-02-16 rel-agent v1.23.3.

Version 1.23.4 restructured the commands three days later, and v1.30.0 finished the rename on 2026-03-09 rel-agent v1.30.0. The docs date Docker Agent in Desktop to 4.63, while the first release note that names it is 4.64.0 with v1.27.1 (conflict C49). Desktop 4.81.0 removed the deprecated cagent binary on 2026-07-06 docs-desktop 4.81.0. Desktop 4.94.0 bundles Docker Agent v1.144.0 docs-desktop 4.94.0, five releases behind the v1.149.0 binary this manual pins. Many Desktop notes state no bundled version at all (conflict C50).

Fig. 7.3two product tracks from 2025-09 to 2026-10timeline
Two product tracks from 2025-09 to 2026-10 Two tracks over one date axis from 2025-09 to 2026-10. The sandboxes track shows the docker sandbox plugin as a grey bar from 2025-11-06 to its removal in Desktop 4.80.0 on 2026-06-29, and sbx as a blue bar from v0.21.0 on 2026-03-31 to v0.47.0 on 2026-10-05. The agent track shows cagent as a grey bar from the 2025-09-18 launch blog to its removal in Desktop 4.81.0 on 2026-07-06, and docker-agent as a violet bar from the v1.23.3 plugin on 2026-02-16 to v1.149.0 on 2026-10-07. Rose lines mark three removals: the socket mount with Desktop 4.58.0, the plugin with 4.80.0, and the cagent binary with 4.81.0. A numbered key lists all twelve events. SANDBOXES docker sandbox plugin sbx AGENT cagent docker-agent 2025-09 2025-12 2026-03 2026-06 2026-09 1 2 3 4 5 6 7 8 9 10 11 12 KEY 1 2025-09-18: cagent launch blog, the public repo dates from 2025-09-01 2 2025-10-23: Desktop 4.49.0 bundles the agent, later renamed in the notes 3 2025-11-06: Desktop 4.50.0, container-based docker sandbox run (plan timeline) 4 2026-01-26: Desktop 4.58.0, microVM sandboxes, --mount-docker-socket gone 5 2026-02-16 and 02-19: v1.23.3 docker agent plugin, v1.23.4 restructure 6 2026-02-18: Desktop 4.61.0 bundles plugin v0.12.0, the legacy help quoted here 7 2026-03-09: v1.30.0 completes the rename to docker-agent, CAGENT_* still read 8 2026-03-31: v0.21.0, the first public standalone sbx tag 9 2026-06-29: Desktop 4.80.0 removes the docker sandbox plugin 10 2026-07-06: Desktop 4.81.0 removes the cagent binary 11 2026-09-21: v0.45.0 adds sbx --cloud and v3 kits 12 2026-10-05 and 10-07: v0.47.0 and v1.149.0, the pin of this manual
Each old name survived its successor for months, and Docker Desktop removed the two of them one week apart, on 2026-06-29 and 2026-07-06. Read left to right on one date axis. Grey bars are the old names, blue and violet bars the current ones, and rose lines mark the three removals. The numbered key below names each event with its version. From research/sources/docs-desktop-release-notes.md, sbx-releases.md, and docker-agent-CHANGELOG.md.

The old command trees

the docker sandbox plugin, v0.12.0research/sources/help-legacy-docker-sandbox.mdtext
Management Commands:
  create      Create a sandbox for an agent
  network     Manage sandbox networking

Commands:
  exec        Execute a command inside a sandbox
  ls          List VMs
  reset       Reset all VM sandboxes and clean up state
  rm          Remove one or more sandboxes
  run         Run an agent in a sandbox
  save        Save a snapshot of the sandbox as a template
  stop        Stop one or more sandboxes without removing them
  version     Show sandbox version information
…
      --bypass-cidr string   Bypass MITM proxy for an IP range in CIDR
                             notation (can be specified multiple times)
      --bypass-host string   Bypass MITM proxy for a domain or IP (can be
                             specified multiple times)
…
      --policy allow|deny    Set the default policy
The root help and the proxy flags, recorded on 2026-10-08 from the plugin that Desktop 4.61.0 bundled. The create, exec, and save sections are cut.
the docker agent plugin, v1.32.4research/sources/help-legacy-docker-agent.mdtext
Core Commands:
  new         Create a new agent configuration
  run         Run an agent
  share       Share agents

Advanced Commands:
  alias       Manage aliases
  eval        Run evaluations for an agent
  serve       Start an agent as a server
…
      --sandbox                   Run the agent inside a Docker sandbox (requires Docker Desktop with sandbox support)
…
      --template string           Template image for the sandbox (passed to docker sandbox create -t)
…
      --yolo                      Automatically approve all tool calls without prompting
The root help and three run flags, recorded on 2026-10-08. The global flags already name the cagent directories.

Beside them, the v0.47.0 tree in capture/out/01-help-sbx.txt groups policy, secret, template, and mcp under Management Commands, and network and save are gone. The v1.149.0 tree in capture/out/01-help-docker-agent.txt adds setup, doctor, models, toolsets, sessions, debug, sandbox, and serve chat, which Part 5 covers.

The command map

Old command or habitWhere it appearsStopped being trueDo this now
docker sandbox run <agent>Desktop docs, blogs, videosDesktop 4.80.0, 2026-06-29sbx run <agent>
docker sandbox run --mount-docker-socket kirore:Invent blog, 2025-12-12Desktop 4.58.0, 2026-01-26nothing: every sandbox has a private Docker Engine
--load-local-templateDesktop 4.58 to 4.60Desktop 4.61, 2026-02-18sbx template load FILE, then --pull never -t TAG
--pull-template missing, the defaultplugin v0.12.0v0.21.0, 2026-03-31--pull always, missing, or never, default always
docker sandbox create cagent .plugin v0.12.0, blog 2026-03-11Desktop 4.80.0sbx create docker-agent ., and cagent remains an alias
docker sandbox network proxy S --allow-host api.example.comlegacy docs, blog 2026-02-23Desktop 4.80.0sbx policy allow network api.example.com --sandbox S
--bypass-host, --bypass-cidrplugin v0.12.0Desktop 4.80.0no replacement in the v0.47.0 help, see capture/out/09-bypass-grep.txt
docker sandbox network log --jsonlegacy docsDesktop 4.80.0sbx policy log S --json
docker sandbox save S TAG into host Dockerplugin v0.12.0, blog 2026-02-23Desktop 4.80.0sbx template save S TAG -o FILE into the sandbox runtime's own store
docker sandbox exec -dplugin v0.12.0sbxsbx exec -d is "not supported"
names with _, +, or .plugin v0.12.0v0.43.0, 2026-09-152 to 63 characters, letters, digits, hyphens, periods, and no periods with --cloud
a proxy set by hand at host.docker.internal:3128blogs 2026-02-23 and 2026-05-26sbxnothing: the daemon sets gateway.docker.internal:3128, see capture/out/04-env.txt
API keys in ~/.zshrc, then restart Desktoptutorials before 2026-07v0.35.0, 2026-07-10sbx secret set SERVICE or sbx secret import
sbx run claude --branchblog 2026-05-26, videosv0.31.0, 2026-05-28sbx run claude --clone
kit v1 grammar, schemaVersion: "1", network.allowedDomainsblog 2026-08-03v2 on 2026-09-09, v3 on 2026-09-24v2 spec.yaml for sbx kit, v3 kit.yaml with # syntax=docker/sandbox-kit:3
sbx mcp catalogdocs before 0.45.0v0.45.0, 2026-09-21sbx mcp add --url URL
sbx mcp enable github-officialproduct page, 2026-10-07never existedsbx mcp add, then --static-mcp or sbx mcp load
-p 3000:8080 binds IPv4 and IPv6before v0.42.0v0.42.0, 2026-09-07tcp4 by default, write 3000:8080/tcp for both
kits from any registrybefore v0.34.0v0.34.0, 2026-06-26add the prefix to kit.allowedSources
a relative --command ./helper secret sourcedocs before v0.46.0v0.46.0, 2026-09-28an absolute path outside writable sandbox mounts
Windows 10early 2026v0.35.0, 2026-07-10Windows 11 with Windows Hypervisor Platform
Toolkit gateway at host.docker.internal:8811, five manual stepsre:Invent blog, 2025-12-12v0.38.0, 2026-08-06sbx mcp add and sbx mcp load, the Toolkit gateway is separate
"one sandbox per workspace"blog 2026-03-11sbx names default to <agent>-<workdir>--name for a second sandbox on one directory, see capture/out/04-second-sandbox.txt
cagent run agent.yaml, cagent new, cagent exec2025 blogs and talksv1.23.4, 2026-02-19, and Desktop 4.81.0docker agent run, docker agent new, docker agent run --exec
cagent push, cagent pull, cagent acp, api, mcp, a2a2025 blogsv1.23.4docker agent share push and pull, docker agent serve acp, api, mcp, a2a
cagent config, feedback, build, catalog2025 to early 2026v1.23.4removed, and the catalog is the Hub namespace agentcatalog/*
cagent version, brew install cagentblog 2025-11-13v1.30.0 and Desktop 4.81.0docker agent version, brew install docker-agent, winget install Docker.Agent
"Docker Agent v2.x renamed the CLI"a third-party tutorialnever truethe latest tag is v1.149.0
docs.docker.com/ai/cagent/blog 2026-03-11, old linksv1.30.0docs.docker.com/ai/docker-agent/
CAGENT_MODELS_GATEWAY, CAGENT_CONFIG_DIR, CAGENT_PPROF_ADDRolder docs and issuesv1.30.0, still acceptedDOCKER_AGENT_MODELS_GATEWAY, DOCKER_AGENT_CONFIG_DIR, DOCKER_AGENT_PPROF_ADDR
docker/sandbox-templates:cagentlegacy agent pagethe renamedocker/sandbox-templates:docker-agent
docker cp the agent binary into a sandbox, then agent run dev-team.yaml insideblog 2026-03-11the docker-agent templatesbx run docker-agent . or docker agent run --sandbox agent.yaml
--yoloeverywherestill accepted--safety autonomous, of which --yolo is the alias
the eval judge is a paid Anthropic modelv1.32.4 helpv1.147.0, 2026-10-05default openai/gpt-5.6-terra, or any provider/model
--sandbox "requires Docker Desktop with sandbox support"v1.32.4 helpsbx--sbx defaults to true, and --sbx=false has no target after Desktop 4.80.0
mcp-gateway v0.22.0 in the GitHub Actionthe action's defaultsthe repository is at v0.44.1pin the gateway version yourself

The rows restate the register's stale-advice list S1 to S30 and the plugin rows C107 to C112, which the sources section prints in full.

What kept the old name

The rename did not touch the directories. docker-agent --help still defaults --data-dir to ~/.cagent, --config-dir to ~/.config/cagent, and --cache-dir to ~/Library/Caches/cagent help-agent docker-agent. The v1.32.4 plugin printed the same three values, and the debug log stays at ~/.cagent/cagent.debug.log (conflict C54). The CAGENT_* variables are still read next to DOCKER_AGENT_* rel-agent v1.30.0.

The OCI annotation became io.docker.agent.version with the old name kept rel-agent v1.23.3. The Hub image moved from docker/cagent to docker/docker-agent, and the image keeps cagent as a symlink rel-agent v1.30.0. In sbx, cagent remains an alias of the docker-agent create subcommand help-sbx sbx create docker-agent.

Sandbox state did not migrate. The plugin kept VM state under ~/.docker/sandboxes/vm/ and an image cache under ~/.docker/sandboxes/image-cache/, as its own reset help says. sbx keeps its socket and log under ~/Library/Application Support/com.docker.sandboxes/sandboxes/sandboxd/ (capture/out/02-daemon-status.txt), and no page describes a migration (conflict C17). No capture lists the leftover directories.

On Desktop 4.94.0, docker info still lists the plugin as sandbox v0.13.0 (29-docker-plugins.txt), and each of its commands prints the removal notice:

docker sandbox on Docker Desktop 4.94.0capture/out/29-docker-sandbox.txttext
$ docker sandbox --help
"docker sandbox" is deprecated and has been removed.

Please migrate to Docker Sandboxes: https://www.docker.com/products/docker-sandboxes
[exit 1]
docker sandbox version prints the same notice and is cut.

The notice names the product page, not the docker sbx of the 4.80.0 note. On the same host, the bundled docker agent version prints v1.144.0 and the Homebrew docker-agent version prints v1.149.0 (29-docker-agent-plugin.txt).

Sources:docs-desktop 4.49.0, 4.58.0, 4.61.0, 4.64.0, 4.80.0, 4.81.0, 4.94.0 (research/sources/docs-desktop-release-notes.md); rel-agent v1.23.3, v1.23.4, v1.30.0, v1.147.0 (research/sources/docker-agent-CHANGELOG.md); rel-sbx v0.21.0, v0.31.0, v0.35.0, v0.42.0, v0.43.0, v0.45.0 (research/sources/sbx-releases.md); docs-agent Installation (research/sources/docs-docker-agent.md); help-sbx sbx create docker-agent (research/sources/help-sbx.md); help-agent docker-agent (research/sources/help-docker-agent.md); research/sources/help-legacy-docker-sandbox.md; research/sources/help-legacy-docker-agent.md; blog 2025-09-18, 2025-11-25, and research/conflicts-register.md rows C1, C17, C49, C50, C54, C107 to C112, S1 to S30; research/plan.md (the timeline); capture/out/01-help-sbx.txt, 01-help-docker-agent.txt, 02-daemon-status.txt, 04-env.txt, 04-second-sandbox.txt, 09-bypass-grep.txt, 29-docker-sandbox.txt, 29-docker-plugins.txt, 29-docker-agent-plugin.txt

7.4

kit-tck, sbx diagnose, and the claims this manual checked

Every claim this manual repeats from Docker's pages or the community was checked against a capture or marked unverified, and this section is the ledger.

A vendor page says that credential values never enter the VM docs-sbx Security model. A Hacker News thread from 2026-08-10 says the proxy re-signs every certificate, and a podcast from 2026-09-26 says nobody audited the boundary. You want to know which of those three someone checked. When you finish this section, you can say which claims this manual verified, with which file, and which it only reports.

The ledger

Each row names the claim and its source, the verdict, and the capture file behind the verdict. A verdict of confirmed means a recorded file shows the behaviour. Contradicted means a file shows the opposite. Disputed means two sources disagree and no capture settles it, and unverified means no capture was planned or recorded.

Claim and where it is madeVerdictEvidence
credential values never enter the VM (docs, Security model)confirmed10-env-sentinel.txt has M101_RECV_KEY=sbx-cs-<rand> and ANTHROPIC_API_KEY=proxy-managed, and 10-receiver.log shows the host receiver got Bearer m101-dummy-receiver-0000
the secret is injected only for a bound host, and the whole header is replaced (HN 2026-08-10, C34)confirmed, both sides10-swap-curl.txt: 204 on host.docker.internal:18080 and 000 on gateway.docker.internal:18080, while 10-receiver.log shows the swap with and without Bearer, and in X-Demo
outbound TCP is blocked unless a rule allows it (docs, Default security posture)confirmed09-blocked.txt: 403, and 09-check-verbose.json: "deny_kind": "implicit"
each sandbox has its own Docker Engine with no path to the host daemon (docs, Security model)confirmed in part04-guest.txt: Docker Engine 29.8.1 server inside, user agent in group docker, and no probe for a host path was run
--clone commits reach the host through sandbox-<name> (help, sbx create)confirmed08-clone-host.txt: git fetch sandbox-m101-clone brought 81e11df from sandbox, and 08-clone-commit.txt: a push to /run/sandbox/source is rejected
the guest uses 16 KiB pages on Apple silicon (blog 2026-05-26, C45)confirmed04-guest.txt: getconf PAGESIZE prints 16384
the proxy is gateway.docker.internal:3128 (issue #12, C26)confirmed04-env.txt: HTTPS_PROXY=http://gateway.docker.internal:3128, and PROXY_CA_CERT_B64 set
HTTPS is intercepted and re-signed (issue #12, HN 2026-08-10)not checked09-allowed.txt shows the CONNECT tunnel and a cloudflare reply, and no certificate chain was read
one MCP gateway endpoint per sandbox (docs, Architecture)confirmed11-static-inside.txt: MCP_GATEWAY_URL=http://mcp-gateway.docker.internal/mcp, and 11-gateway-tools-static.json: ask_wiki_question
27 toolset types, and transfer_task is not one of them (schema, C56)confirmed16-toolsets.txt: 27 rows, neither implicit name
the sbx kit commands are v1 and v2 tooling (C13)confirmed13-kit-v3-validate.txt: "is a v3 source kit and this load path has no kit builder configured", and 12-validate.txt: VALID
a kit signature verifies with sbx kit sign and verify (docs, Build and distribute kits)not checkedno sign or verify step was recorded, only 13-kit-builder-status.txt
a local model runs with no key (docs, Use local and hosted models)confirmed25-doctor.txt: every provider credential not set, Docker Model Runner reachable with docker.io/ai/qwen3:4b, and 25-run-dmr.txt answers Hello live. 18-cassette-head.txt shows the recorded requests going to localhost:12434, and 27-inside-run.txt answers from inside the VM. The 8B ai/qwen3:latest failed to pull (25-model-pull-latest.txt)
local use needs a Docker sign-in (docs, FAQ, disputed in HN 2026-08-10 and issue #321, C15)confirmed in part02-diagnose.txt lists Authentication as a check, the kit's prerequisites include sbx login, and help-sbx-cloud.md notes a TLS attempt to login.docker.com on every --help
sbx policy approval is not a command (the v0.47.0 help tree, C16)unverified09-blocked.txt: the 403 body says "Review and respond with: sbx policy approval ls", and no capture ran that command
the VMM is libkrun (talk 2026-01-14, HN 2026-08-10, C4)unverified00-sbx-version.json names no runtime, and 04-guest.txt shows kernel 7.0.14 and nothing more
a sandbox starts in tens of milliseconds (talk 2026-02-10, C29)not checkedthe timing runs planned as K35 were not recorded
an allowed host can carry data out (HN 2026-08-10, talk 2026-04-07)disputedboth sides are printed in the proxy section, and no capture tested it
a microVM HTTP API, a Kubernetes runtime, Warp Oz (blog 2026-05-26, blog 2026-08-21, talk 2026-03-31)unverifiedno Docker page confirms any of them (C105, C106)
an independent audit of the boundary existsnone foundthe podcast of 2026-09-26 says none exists, and the corpus has none (T24)
sbx ls hangs on macOS (#163), Windows start fails (#350)not reproduced99-final-state.txt: sbx ls answered at the end of the run
a sandbox stops itself after the last session ends (not in the docs)observed04-auto-stop.txt: "auto-stop grace period expired, stopping runtime"
Fig. 7.4confirmed by a file, or only reportedcomparison
Confirmed by a file, or only reported Two columns. The left column lists eight claims a recorded capture file confirms: the sentinel inside and the real secret outside, default deny on example.com:443, a private Docker Engine 29.8.1 inside, commits fetched through the sandbox-m101-clone remote, the proxy at gateway.docker.internal:3128, MCP_GATEWAY_URL set inside the sandbox, docker-agent toolsets printing 27 types, and ai/qwen3:4b answering on Docker Model Runner with no provider key set. The right column lists six claims. Five are reported without a test: a MITM proxy that re-signs HTTPS, libkrun as the VMM, a start in tens of milliseconds, a verified kit signature, and sbx policy approval missing from the help although a 403 body names it. One rose box marks the disputed exfiltration path. CONFIRMED BY A CAPTURE FILE REPORTED, NOT TESTED HERE sentinel inside, real secret outside 10-env-sentinel.txt, 10-receiver.log default deny on example.com:443 09-blocked.txt, 09-check-verbose.json private Docker Engine 29.8.1 inside 04-guest.txt fetched via sandbox-m101-clone 08-clone-host.txt proxy gateway.docker.internal:3128 04-env.txt MCP_GATEWAY_URL set inside 11-static-inside.txt toolsets prints 27 types 16-toolsets.txt ai/qwen3:4b on DMR, no key set 25-doctor.txt, 27-inside-run.txt HTTPS re-signed by a MITM proxy issue #12, not checked the VMM is libkrun talk 2026-01-14, unverified a start in tens of milliseconds talk 2026-02-10, K35 not run a kit signature verifies sbx kit sign, not recorded sbx policy approval is not a command 403 body names it, never run allowed host as exfiltration path HN 2026-08-10, both sides printed
Eight claims were confirmed by a recorded file, five are reported without a test, and the community disputes one more. Left, each confirmed claim with the capture file that shows it, coloured by the layer it belongs to. Right, dashed boxes are claims this manual reports without a test, and the rose box is the claim the community disputes. From the files named in each box.

sbx diagnose, the only self-check

sbx diagnose: the one command that checks an installation, in the four groups that capture/out/02-diagnose.txt shows: Installation, Platform, Storage, and Connection. It takes --json, --output github-issue for a report to paste into an issue, and --upload to send diagnostics to Docker support help-sbx sbx diagnose.

the platform and connection checks on the recording hostcapture/out/02-diagnose.txttext
  Platform
  ✓ Virtualization — supported
      kern.hv_support is 1
  ✓ mkfs.erofs — found
      /opt/homebrew/Caskroom/sbx/0.47.0/Sbx.app/Contents/libexec/mkfs.erofs, default block size <n> bytes, guest kernel page size <n> bytes
…
  Connection
  ✓ Version match — v0.47.0
  ✓ Socket — responsive
  ✓ SSH client config — not configured
  ✓ Authentication — authenticated

─────────────────────────────────────────
  13 passed
The Installation and Storage groups are cut. The byte counts and sizes are masked by the kit.

All 13 checks passed, and the JSON form in 02-diagnose.json carries the same rows with a summary of pass, warn, fail, and skip counts. The Platform group is where the microVM boundary section reads the hypervisor and the page size. sbx --cloud diagnose runs a different set, sign-in, the cloud API, and account access, and needs no daemon docs-sbx Use cloud sandboxes.

kit-tck, not run in this edition

kit-tck: the conformance suite of the Sandbox Kit Specification v3, with two independent halves, one for a published kit artifact and one for a runtime. The conformance page of the specification repository describes both (research/sources/kit-spec-extras.md). kit-tck validate <reference> judges a published artifact against the publishing and OCI layout rules, and the BuildKit frontend runs the same checks before it exports. kit-tck runtime --adapter <path> drives an adapter, an executable with verbs such as capabilities, create, exec, stop, start, recreate, and rm.

The suite judges what the runtime does through those verbs. An adapter exits 2 when it refuses a request by policy, and any other non-zero code is a failure. A runtime that cannot provide a required capability must therefore refuse the kit.

the two suites and the artifact commandsresearch/sources/kit-spec-extras.mdtext
task tck:kit REF=docker.io/me/sbx-kit-gh:1.0.0   # is this artifact a conforming Kit?
task tck:runtime ADAPTER=./my-adapter            # does this runtime behave as the pages require?
…
kit-tck validate docker.io/me/sbx-kit-gh:1.0.0 --verbose
kit-tck validate docker.io/me/sbx-kit-gh:1.0.0 --format json
Four lines from the README of docker/sandbox-kit-spec. The task lines run from a checkout, and the kit-tck lines run a released binary.

Released kit-tck binaries are attached to each GitHub release of the specification for Linux, macOS, and Windows. kit-tck inspect reads a kit's descriptor and recipe without judging it. The suite places three known values in the adapter's environment, KIT_TCK_HOST_SENTINEL, KIT_TCK_BOUND_SECRET, and KIT_TCK_SKILL_NAME. A leak of host environment or of a bound secret is then visible.

This edition did not run it. The capture kit built a v3 artifact and pushed it to a local registry (28-buildx.txt, 28-manifest.json), but no kit-tck step ran against it.

The agent regression check

For agent runs, the planned check is docker-agent sessions diff --fail-on-divergence, which compares two recorded sessions "over the sequence of tool calls, not over the assistant's prose" and stops at the first divergence help-agent docker-agent sessions diff. The capture kit ran it on recorded sessions (21-sessions-diff.txt). Two replays of one run are identical across five turns, and a run that called shell first diverges at turn 0 and exits 1. A relative reference such as -1 needs -- before it, or the parser reads it as a flag. The sessions section explains the session store.

Sources:docs-sbx Security model, Default security posture, Architecture, FAQ, Use cloud sandboxes, Build and distribute kits, Use local and hosted models (research/sources/docs-sandboxes.md); help-sbx sbx diagnose, sbx create (research/sources/help-sbx.md); help-agent docker-agent sessions diff (research/sources/help-docker-agent.md); research/sources/kit-spec-extras.md (README Conformance, docs/spec/conformance.md); research/sources/help-sbx-cloud.md; research/conflicts-register.md rows C4, C13, C15, C16, C26, C29, C34, C45, C56, C105, C106 and the community rows of research/plan.md; capture/out/00-sbx-version.json, 02-diagnose.txt, 02-diagnose.json, 04-auto-stop.txt, 04-env.txt, 04-guest.txt, 08-clone-commit.txt, 08-clone-host.txt, 09-allowed.txt, 09-blocked.txt, 09-check-verbose.json, 10-env-sentinel.txt, 10-receiver.log, 10-swap-curl.txt, 11-gateway-tools-static.json, 11-static-inside.txt, 12-validate.txt, 13-kit-builder-status.txt, 13-kit-v3-validate.txt, 16-toolsets.txt, 18-cassette-head.txt, 21-sessions-diff.txt, 25-doctor.txt, 25-model-pull-latest.txt, 25-run-dmr.txt, 27-inside-run.txt, 28-buildx.txt, 28-manifest.json, 99-final-state.txt

Reference

Reference

Lookup tables for every name in the manual.

  1. R.1sbx commands
  2. R.2docker-agent commands
  3. R.3Settings keys, environment variables, and paths
  4. R.4Agent file keys
  5. R.5Toolsets
  6. R.6Providers and models
  7. R.7Sources and the conflicts register
  8. R.8Glossary and index of figures
R.1

sbx commands

Every verb of sbx 0.47.0 in the four groups of sbx --help, with its purpose, the flags that matter, its scope, and the section that explains it.

The root help of sbx 0.47.0 lists 31 commands in four groups: Sandbox, Management, Experimental, and Other help-sbx sbx. The vendored tree holds 115 help pages, one for each command and subcommand that answered --help. The tables below keep the order of the root help. Scope says where a verb runs: local sandboxes through sandboxd, cloud sandboxes through sbx --cloud, or both. sbx --cloud --help hides daemon, prune, settings, and skills, and attach, ttl, and volume run only with --cloud (conflict C47).

Three flags apply to every command help-sbx sbx.

Global flagMeaning
--cloudDispatch to the Docker Cloud Sandboxes API instead of the local sandboxd
-D, --debugEnable debug logging
-h, --helpPrint the help of the command

Sandbox commands

CommandPurposeFlags that matterScopeSection
attach SANDBOXAttach a terminal to a cloud sandbox, and start it first when it is stopped help-sbx sbx attach--detach-keys (default Ctrl-\)cloud only§7.1
cp SRC DSTCopy a file or a directory between a sandbox and the host, with one side written as SANDBOX:PATH help-sbx sbx cp-L, --follow-linkboth§2.2
create AGENT|SANDBOX_KIT [PATH...]Create a sandbox for one of the 11 agents or from a kit reference, without attaching help-sbx sbx create--name (default <agent>-<workdir>), --clone, --cpus (0 is auto), -m, --memory (default 50% of host memory, 512 MiB to 32 GiB), -e, --env, --env-file, --deny-network, -p, --publish, --pull always|missing|never (default always), --skills off|readonly|readwrite (default readonly), --static-mcp, -t, --template, --profile, --kit (experimental), --kit-arg, --kit-args-file, -qboth§2.1
create <agent> [PATH...]The eight subcommands claude, codex, cursor, devin, docker-agent, gemini, opencode, shell, with the same flags, and cagent as an alias of docker-agent help-sbx sbx create docker-agentthe flags of createboth§2.1
create with --cloudCreate a cloud sandbox with no host workspace, 2 CPUs and 4 GiB unless sized help-sbx sbx create--allow-network, --image-ref, --on-timeout stop|restart|delete, --platform, --ttl, -v, --volume (experimental)cloud only§7.1
exec SANDBOX COMMANDRun a command inside a sandbox, and start a stopped one first help-sbx sbx exec-i, -t, -u, --user, -w, --workdir, -e, --env-file, --privileged, --detach-keys. -d is not supported. -d, --user, and --privileged are rejected with --cloudboth§2.2
lsList sandboxes with agent, status, published ports, and workspace help-sbx sbx ls--json, -qboth§2.1
move SANDBOXMove a sandbox between the host and the cloud as a filesystem image help-sbx sbx move--to local|cloud, --name (default moved- plus the source name), --ttl (15s to 24h), --on-timeout stop|delete, -fboth§7.1
ports SANDBOXList, publish, or unpublish sandbox ports help-sbx sbx ports--publish, --unpublish, --json. A port without a protocol binds tcp4both§2.2
pruneRemove all stopped sandboxes and their sandbox-scoped secrets help-sbx sbx prune--dry-run, --filter until=TIMESTAMP, -f, --jsonlocal only§2.1
rm [SANDBOX...]Remove sandboxes, their containers, git worktrees, state, and scoped secrets help-sbx sbx rm--all (disabled with --cloud), -fboth§2.1
run [AGENT|SANDBOX_KIT] [PATH...] [-- AGENT_ARGS...]Run an agent and create the sandbox when it does not exist help-sbx sbx runthe flags of create, plus -d, --detached, --rm, --name to re-attach, --new (cloud), --detach-keys (cloud). --clone works only at creationboth§2.1
stop SANDBOX...Stop sandboxes and keep their state help-sbx sbx stopnoneboth§2.1
ttl [+DURATION] SANDBOXPrint or extend the TTL of a cloud sandbox under its 24 hour ceiling help-sbx sbx ttl--jsoncloud only§7.1

The 11 agent names that run and create accept are claude, codex, copilot, cursor, devin, docker-agent, droid, gemini, kiro, opencode, and shell help-sbx sbx run. Only eight of them have a create subcommand, because copilot, droid, and kiro are public kits (conflict C9).

Management commands

CommandPurposeFlags that matterScopeSection
daemon startStart sandboxd help-sbx sbx daemon start-d, --detach, --policy allow-all|balanced|deny-alllocal only§2.4
daemon stop, daemon restartStop or restart sandboxd help-sbx sbx daemonnonelocal only§2.4
daemon statusPrint the daemon state, its socket, and its log path help-sbx sbx daemon status--jsonlocal only§2.4
daemon log-level set <target> <level>Set the log level of proxy, general, or all help-sbx sbx daemon log-level setnonelocal only§2.4
diagnoseRun the 13 installation checks help-sbx sbx diagnose--json, -o json|github-issue, --uploadboth§2.4 and §7.4
mcp add <name> (--url | --command)Register an MCP server from a remote endpoint, a registry URL, a manifest URL, a dhi.io image, or a host command help-sbx sbx mcp add--url, --command, --args, --dir, --local, --header, --client-id, --oauth-authorization-server, --scope, --no-scope, --resource, --callback-port, --skip-auth, --skip-ssrf-check, --disable-http2both§3.6
mcp auth [server-name]Authorize or reauthorize remote servers through the hosted control plane help-sbx sbx mcp auth--all, --scope, --no-scope, --verbose, --format text|json, --jsonboth§3.6
mcp auth rm, mcp auth statusRemove hosted OAuth credentials, or show their status without starting OAuth help-sbx sbx mcp auth status--all, -f (rm only), --format, --jsonboth§3.6
mcp inspect <name>Show one registration, its headers, and the effective resource value help-sbx sbx mcp inspect--jsonboth§3.6
mcp load <name> --sandbox SAttach a registered server to a running sandbox, with a tools/list_changed notification to the agent help-sbx sbx mcp load--sandbox (required)both§3.6
mcp lsList registered servers under the gateway that serves them help-sbx sbx mcp ls--json, -qboth§3.6
mcp rm <name>Remove a registration help-sbx sbx mcp rm-fboth§3.6
policy init <allow-all|balanced|deny-all>Set the global network policy once, before the first sandbox help-sbx sbx policy init--sandbox (cloud only)both§3.3
policy allow network RESOURCESAdd an allow rule for hosts, domains, IP addresses, or CIDR prefixes, TCP by default help-sbx sbx policy allow network--sandbox, --protocol tcp|udpboth§3.3
policy deny network RESOURCESAdd a deny rule, TCP and UDP by default, which wins over allow help-sbx sbx policy deny network--sandbox, --protocol tcp|udpboth§3.3
policy check network TARGETAsk the daemon authorizer whether a host and port is allowed, without sending anything help-sbx sbx policy check network--sandbox, --protocol, --verbose, --jsonboth§3.3
policy inspect <policy-or-rule>Show one policy or one rule with its RULE_ID and removal command help-sbx sbx policy inspect--jsonboth§3.3
policy log [SANDBOX]Show which hosts the proxy allowed or blocked, with rule, proxy type, and count help-sbx sbx policy log--json, --limit, -q, --type all|network|filesystem (filesystem logs are not supported yet)both§3.4
policy ls [SANDBOX]List policies, one overview row per policy, or the rules of one sandbox help-sbx sbx policy ls--wide, --json, --source local|org|kit, --decision allow|deny, --type, --created-via default|added|provisioned|approval, --profile, --protocol, --include-inactiveboth§3.3 and §7.2
policy profile lsList the profiles that remote governance policies provide help-sbx sbx policy profile ls--json, -qboth§7.2
policy resetDelete the local policy store and stop the daemon, or delete the cloud account policy help-sbx sbx policy reset-fboth§3.3
policy rm networkRemove a rule by RULE_ID or by resource help-sbx sbx policy rm network--id, --resource, --sandbox, -fboth§3.3
resetReturn sbx to a freshly installed state, including secrets and the sign-in help-sbx sbx reset-f, --preserve-secretslocal only§2.4
secret set [SERVICE]Store a service secret for one of 13 services, a dynamic source, or a registry credential help-sbx sbx secret set-t, stdin, --ref, --command, --refresh (default 55m), --no-verify, --show-error, --oauth (openai only locally), --sandbox, -f, --registry, --username, --password-stdin, --registry-auth-endpoint, --all-sandboxesboth§3.5
secret import [SERVICE]Import secrets found in host environment variables, with a last four character preview help-sbx sbx secret import--all, --force, --dry-runboth§3.5
secret lsList stored secrets across global and sandbox scopes help-sbx sbx secret ls-g, --sandbox, --service, --json, -qboth§3.5
secret rm [SERVICE]Remove a secret, a registry credential, or every secret help-sbx sbx secret rm--sandbox, --all, --all-sandboxes, --registry, -f. The examples also show --placeholder, which the flag list omitsboth§3.5
secret set-customStore a secret for a service sbx does not know, behind a placeholder the proxy swaps help-sbx sbx secret set-custom--host (repeatable), --env, --value, -t, --command, --ref, --placeholder ({rand} suffix), --refresh, --sandbox. Cloud only: --header, --format, --nameboth, experimental§3.5
settings get <key>Print one evaluated value help-sbx sbx settings get--jsonlocal only§2.5
settings listList every setting with value, type, source, restart flag, and description help-sbx sbx settings list--json, --no-trunclocal only§2.5 and R.3
settings set <key> <value>, settings unset <key>Write or remove a user override help-sbx sbx settings setnonelocal only§2.5
template save SANDBOX TAGSave a snapshot into the sandbox runtime image store help-sbx sbx template save-o, --output (also export a tar), --capture-mode disk|all (cloud), -d, --description (cloud)both§2.3
template load FILE [NAME]Load a tar into the image store, or upload it as a cloud template help-sbx sbx template load--cpus and --memory-mib (required with --cloud, must name a billable shape), --capture-mode, --descriptionboth§2.3
template ls, template rm TAG|ID|NAMEList or remove template images help-sbx sbx template ls--json, -q, -fboth§2.3
template inspect NAME|IDShow the full metadata of one cloud template help-sbx sbx template inspect--jsoncloud only in v1§2.3
tuiOpen the interactive dashboard help-sbx sbx tuinoneboth§1.1
volume create|inspect|ls|rmManage persistent cloud volumes, saved as a snapshot when a sandbox exits help-sbx sbx volume--json, -q, -fcloud only§7.1

sbx with no command opens interactive mode, and sbx tui opens the dashboard by name (conflict C44) help-sbx sbx.

Experimental commands

Each of these prints the line EXPERIMENTAL: this command may change or be removed in future releases. at the top of its help help-sbx sbx kit.

CommandPurposeFlags that matterScopeSection
env create [PATH...]Provision the secrets and bindings of an sbxenv.yaml and create the sandbox help-sbx sbx env create-y, --auto-approve, --clone, --env-arg, --env-args-file, --kit-arg, --kit-args-file, --name, --skip-host-commandsboth, experimental§4.4
env run [PATH...]Create when needed, then attach to the environment sandbox help-sbx sbx env runthe flags of env create, plus -d, --detachedboth, experimental§4.4
env plan [PATH...]Print what applying the file would change, and change nothing help-sbx sbx env plan--clone, --env-arg, --kit-arg, --name, --skip-host-commandsboth, experimental§4.4
env exec [PATH...] -- COMMANDRun a command in the environment sandbox help-sbx sbx env execthe flags of exec, plus --env-arg, --env-args-file, --nameboth, experimental§4.4
env rm [PATH...]Remove the environment sandbox and the secrets it provisioned help-sbx sbx env rm-f, --prune-bindings, --env-arg, --name, --skip-host-commandsboth, experimental§4.4
kit add SANDBOX REFERENCERecreate a sandbox with a mixin appended to its kit list help-sbx sbx kit add--kit-arg, --kit-args-fileboth, experimental§4.3
kit builder status|rmShow or remove the sbx-kit-builder sandbox and its build cache help-sbx sbx kit builder-f (rm)local, experimental§4.3
kit builder history export|inspect|logs|ls|rm|traceRun the matching docker buildx history command inside the builder sandbox help-sbx sbx kit builder historypass-throughlocal, experimental§4.3
kit inspect REFERENCELoad and display a kit from a directory, ZIP, OCI reference, or git repository help-sbx sbx kit inspect--json, --kit-arg, --kit-args-fileboth, experimental§4.2
kit pack DIRECTORYValidate a spec.yaml directory and package it as a ZIP help-sbx sbx kit pack-o, --output (default <name>.zip)both, experimental§4.3
kit provenance REFERENCEPrint the SLSA provenance attached to an OCI kit, UNSIGNED or VERIFIED help-sbx sbx kit provenance--key, --certificate-identity, --certificate-identity-regexp, --certificate-oidc-issuer, --certificate-oidc-issuer-regexp, --insecure-ignore-tlog, --jsonboth, experimental§4.3
kit pull REFERENCEPull a v1 ZIP or v2 tar.gz kit artifact from an HTTPS registry help-sbx sbx kit pull-o, --outputboth, experimental§4.3
kit push DIRECTORY REFERENCEPackage and push a spec.yaml kit, and attach SLSA provenance as an OCI referrer help-sbx sbx kit push--sign, --key, --identity-token, --identity-token-file, --tlog-upload (default true)both, experimental§4.3
kit sign REFERENCESign a kit with a cosign-compatible Sigstore signature, keyless by default help-sbx sbx kit sign--key, --identity-token, --identity-token-file, --tlog-uploadboth, experimental§4.3
kit validate REFERENCECheck that a directory, ZIP, or git reference is a valid kit help-sbx sbx kit validate--json, --kit-arg, --kit-args-fileboth, experimental§4.3
kit verify REFERENCEVerify a signature on a directory, a git reference, or an OCI kit help-sbx sbx kit verify--key, the four --certificate-* flags, --insecure-ignore-tlog, --jsonboth, experimental§4.3
setupDetect agent secrets in the host environment and import the accepted ones help-sbx sbx setupnonelocal, experimental§3.5
setup ssh, setup ssh removeWrite or remove the SSH client config that makes ssh <name>.sbx work help-sbx sbx setup ssh--alias (default *.sbx)local, experimental§2.4
skills add <repository>Install skills from a git repository or owner/repo shorthand into the shared store help-sbx sbx skills add-s, --skill, -flocal, experimental§4.5
skills importImport skills from six agent directories on the host help-sbx sbx skills import--dry-run, -flocal, experimental§4.5
skills ls, skills rm <skill>..., skills update [skill]...List, remove, or refresh installed skills help-sbx sbx skills ls--json, -q, -flocal, experimental§4.5

sbx kit ls is not a command, and the probe answered unknown command (conflict C46). The six kit builder history pages in the vendored tree hold an error instead of help text. The daemon could not bind its socket under the capture sandbox help-sbx sbx kit builder history ls.

Other commands

CommandPurposeFlags that matterScopeSection
completionGenerate the autocompletion script for a shell help-sbx sbxits help page is not in the vendored treebothnone
helpHelp about any command help-sbx sbxits help page is not in the vendored treebothnone
loginSign in to Docker help-sbx sbx login--username, --password-stdinboth§1.1
logoutStop running local sandboxes and sign out help-sbx sbx logout-y, --yeslocal only§1.1
versionPrint the CLI version, and with --json the server and runtime component versions help-sbx sbx version--jsonboth§2.4

Names that appear in text but not in the tree

The help of secret set names sbx mount, and the docs name sbx ssh proxy, sbx policy approval, --model, --provider, and --usb (conflict C16). None of them has a page in the vendored tree. The manual documents only what --help prints, and §2.4 names the hidden commands once. Every sbx ... --help call also tried to open a TLS connection to login.docker.com:443 during the capture. The capture sandbox refused the connection, and the printed text did not change (research/sources/help-sbx-cloud.md).

Sources:research/sources/help-sbx.md (every sbx help page, 115 pages, sbx v0.47.0 0411f50ee4700fe7bd37e6e7e3aced563e850ca9); research/sources/help-sbx-cloud.md (sbx --cloud --help and the network note); research/sources/probes/sbx-kit-ls.txt, sbx-version.txt; research/conflicts-register.md rows C9, C16, C44, C46, C47; manual.json (section files and ids)

R.2

docker-agent commands

Every command of docker-agent 1.149.0 in the four groups of its root help, every global flag, and every serve subcommand with its default listen address.

The root help of docker-agent 1.149.0 lists 19 commands in four groups: Core, Diagnose, Advanced, and Additional help-agent docker-agent. With subcommands, the vendored tree holds 47 pages. The binary is the Homebrew build. The Desktop plugin docker agent runs the same commands with a space in the name (conflict C48). The docs page features/cli is the only other reference, because /reference/cli/docker/agent/ does not exist (conflict C68).

Global flags

Every command accepts these flags help-agent docker-agent.

FlagDefaultMeaning
--cache-dir~/Library/Caches/cagent on macOSOverride the cache directory
--config-dir~/.config/cagentOverride the config directory. The docs add that DOCKER_AGENT_CONFIG_DIR and the legacy CAGENT_CONFIG_DIR set the same path docs-agent features/cli
--data-dir~/.cagent, or DOCKER_AGENT_DATA_DIROverride the data directory, which holds session.db, worktrees, and plans docs-agent features/cli
-d, --debugoffEnable debug logging
--log-file~/.cagent/cagent.debug.logPath of the debug log, used only with --debug
-o, --oteloffEnable OpenTelemetry tracing
-h, --helpnonePrint the help of the command

Core commands

CommandPurposeFlags that matterNeedsSection
getting-startedLearn docker agent with a hands-on interactive tour help-agent docker-agentnone. The tree has no page for it (conflict C75). The docs describe a two minute skippable tour, also reachable as docker agent tour docs-agent features/clian interactive terminal§5.1
run [<agent-file>|<registry-ref>] [message]...Run an agent from a file, a registry reference, an alias, or the built-in default help-agent docker-agent runsee the table belowa model with a credential, Docker Model Runner, or --fake§5.1
setupSet up a model through one of four paths: a built-in provider, Docker Model Runner, a custom OpenAI-compatible endpoint, or the Claude Code harness help-agent docker-agent setupnonea terminal. The provider path writes ~/.config/cagent/.env§5.1
share push <agent-file> <registry-ref>Push an agent file to an OCI registry, in clear, with an optional signature or MAC in the annotations help-agent docker-agent share push--key (PEM, OpenSSH, or a 16 byte secret, or DOCKER_AGENT_ENCRYPT_KEY), --encryptregistry access§6.4
share pull <registry-ref>Pull an agent file and, with a key, verify or decrypt it help-agent docker-agent share pull--key, --forceregistry access§6.4

The flags of run

The run page lists 54 flags help-agent docker-agent run. This table groups the ones the manual uses, with their defaults.

GroupFlags
Agent and input-a, --agent (the team's first agent), --agent-picker [refs], --prompt-file (repeatable), --attach <image>, - reads the message from stdin
Output--exec (no TUI), --last (only the final answer, needs --exec), --json (NDJSON events), --hide-tool-calls, --hide-tool-results, --on-event <type>=<cmd>
Approval--safety strict|balanced|restricted|autonomous, --yolo (same as --safety autonomous, conflict C59)
Model--model [agent=]provider/model (repeatable), --models-gateway, --dry-run
Session--session <id or -1>, -s, --session-db (default <data-dir>/session.db), --session-read-only, --working-dir
Record and replay--record [path] (a cassette plus a TUI e2e test), --fake <path>, --fake-stream [15] ms between chunks
Sandbox--sandbox, --sbx (default true, and --sbx=false forces the removed docker sandbox plugin, conflict C60), --template (default docker/docker-agent-sbx-templates:latest), --sandbox-kit, --kit (repeatable), --kit-arg, --no-kit, --cloud (implies --sandbox), --sandbox-ttl (default 1h0m0s)
Hooks--hook-pre-tool-use, --hook-post-tool-use, --hook-session-start, --hook-session-end, --hook-on-user-input, --hook-stop, all repeatable
Config--flavor (repeatable, applied in order), --env-from-file, --code-mode-tools, --mcp-oauth-redirect-uri, --remote <addr>
Worktree-w, --worktree [name] (default auto), --worktree-base <ref>, --worktree-pr <number or URL> (needs the GitHub CLI)
TUI--lean, --sidebar (default true), --theme, --app-name, --disable-commands

Diagnose commands

CommandPurposeFlags that matterNeedsSection
doctor [agent-file]Report which providers have credentials, whether Docker Model Runner answers, and which model auto picks, and exit non-zero on an issue help-agent docker-agent doctor--json, --env-from-file, --models-gatewaynothing. It runs docker model status --json to check the runner§5.1
models [list|ls]List the models --model accepts, from the gateway /v1/models first, then from the providers with credentials help-agent docker-agent models-a, --all, -p, --provider, --format table|json, --models-gatewaynothing§5.1
toolsetsList the 27 built-in toolset types help-agent docker-agent toolsets--format table|jsonnothing§5.3 and R.5

Advanced commands

CommandPurposeFlags that matterNeedsSection
alias add <alias-name> <agent-path>Save a name for an agent file or registry reference with run options help-agent docker-agent alias add--yolo, --safety (wins over --yolo), --model, --hide-tool-results, --sandboxnothing§5.1
alias list|ls, alias remove|rm <alias-name>List or remove aliases help-agent docker-agent alias list--jsonnothing§5.1
boardOpen a Kanban TUI where each card runs an agent in a tmux session on its own git worktree help-agent docker-agent boardnonetmux, git, and projects in ~/.config/cagent/config.yamlnone
debug authPrint the Docker token in use and where it came from help-agent docker-agent debug auth--jsonnothingR.6
debug config <agent-file> [flavor...]Print the canonical form of an agent file with the flavors applied help-agent docker-agent debug configthe debug group flagsnothing§5.2
debug oauth list|login|removeList stored OAuth tokens, log in to a remote MCP server, or remove a token help-agent docker-agent debug oauth--json (list)a browser for login§5.3
debug skills <agent-file>Show the skills an agent discovers help-agent docker-agent debug skills--jsonnothing§4.5
debug title <agent-file> <question>Generate a session title from a question help-agent docker-agent debug title--modela model§5.6
debug tool <agent-file> <tool-name> [parameters-json]Call one tool directly, with real side effects and no model turn help-agent docker-agent debug tool-a, --agent, --json, --no-hookwhatever the tool needs§5.3
debug toolsets <agent-file>List the tools and parameter schemas of an agent help-agent docker-agent debug toolsets--jsonnothing§5.3
eval <agent-file> [<eval-dir>]Replay saved sessions in containers and score them help-agent docker-agent eval-c, --concurrency (default 10), --judge-model (default openai/gpt-5.6-terra), --judge-type llm|evaluator (default llm), --agent-image (default the pinned docker/docker-agent:<version>), --base-image, --container-runtime (default docker), -e, --env, --keep-containers, --only, --output (default <eval-dir>/results), --repeat (default 1), --baseline, --regression-tolerance (conflict C65)a container runtime and a judge model§5.6
new [description]Write a new agent file from a description help-agent docker-agent new--model (anthropic, openai, google, dmr, or a custom provider, conflict C66), --max-iterations (default 20 for DMR, unlimited elsewhere)a model§5.1
plans create|delete|export|get|list|status|updateEdit the shared plans of the plan toolset from the host, with an optimistic lock help-agent docker-agent plans--file (- is stdin), --title, --author, --status, --expected-version (exit code 3 on conflict), --force, --output, --jsonnothingsub_agents, transfer_task, and background_agents
sandbox allow <host>...Add hosts to the persistent allowlist of every later --sandbox run help-agent docker-agent sandbox allownonenothing§1.2
sandbox deny|remove|rm <host>, sandbox list|lsRemove a host, or list the allowlist help-agent docker-agent sandboxnonenothing§1.2
serve a2a|acp|api|chat|mcpStart an agent as a server help-agent docker-agent servesee the table belowa model§6.1 to §6.3
sessions diff <session-a> <session-b>Report the first tool call where two recorded sessions differ help-agent docker-agent sessions diff--json, --fail-on-divergence, -s, --session-dbnothing§5.6

The debug group shares one flag set: --code-mode-tools, --env-from-file, --flavor, the six --hook-* flags, --mcp-oauth-redirect-uri, --models-gateway, and --working-dir help-agent docker-agent debug. The same flags appear on eval, new, and every serve subcommand.

The serve subcommands

SubcommandTransportDefault listen addressSession databaseAuth and safety flagsSection
serve a2a <agent-file>HTTP, the Agent-to-Agent protocol help-agent docker-agent serve a2a127.0.0.1:8082<data-dir>/session.db--auth-token, --insecure-no-auth, --cors-origin, --safety, -a (the team's first agent, conflict C64)§6.3
serve acp <agent-file>stdio, the Agent Client Protocol help-agent docker-agent serve acpnone<data-dir>/session.dbnone§6.2
serve api <agent-file>|<agents-dir>HTTP, sessions and SSE help-agent docker-agent serve api127.0.0.1:8080session.db in the current directory (conflict C63)--auth-token (empty disables auth), --max-request-size (default 1048576), --session-workingdir-root, --pull-interval (0 disables), --fake, --record§6.1
serve chat <agent-file>HTTP, /v1/chat/completions and /v1/models help-agent docker-agent serve chat127.0.0.1:8083no flag--api-key, --api-key-env, --insecure-no-auth, --cors-origin, --safety, --conversations-max, --conversation-ttl (default 30m0s), --request-timeout (default 5m0s), --max-idle-runtimes (default 4), --max-request-size, -a (all agents if not set)§6.1
serve mcp <agent-file>stdio by default, streaming HTTP with --http help-agent docker-agent serve mcp127.0.0.1:8081 with --httpno flag--auth-token, --insecure-no-auth, and --safety only with --http, --attach [latest] to expose a running TUI, --tool-name, --mcp-keepalive (stdio only), -a (all agents if not set)§6.2

The help text names the four safety modes on serve a2a, serve chat, and serve mcp --http, and states no default for them. The default inside each server is therefore not documented in the help.

Additional commands

CommandPurposeFlags that matterNeedsSection
completionGenerate the autocompletion script for a shell help-agent docker-agentits page is not in the vendored treenothingnone
helpHelp about any command help-agent docker-agentits page is not in the vendored treenothingnone
versionPrint the version and the commit hash help-agent docker-agent versionnonenothing§1.1

On the capture machine docker-agent version printed v1.149.0 and Commit: Homebrew (research/sources/probes/docker-agent-version.txt).

Sources:research/sources/help-docker-agent.md (every docker-agent help page, docker-agent v1.149.0, Commit: Homebrew); research/sources/docs-docker-agent.md (page features/cli for getting-started, --config-dir, and --data-dir); research/sources/probes/docker-agent-version.txt, docker-agent-toolsets.txt; research/conflicts-register.md rows C48, C59, C60, C63, C64, C65, C66, C68, C75; manual.json (section files and ids)

R.3

Settings keys, environment variables, and paths

Every key that sbx settings list printed on 2026-10-08, every environment variable both tools read, and every path they use on macOS, each with its source.

sbx settings list printed 31 keys on the capture machine, every one with SOURCE default (research/sources/probes/sbx-settings-list.txt). A value comes, in order, from the environment variable of the key, then a user override written with sbx settings set, then the built-in default docs-sbx Settings. RESTART yes means that daemon-side consumers that already exist need sbx daemon restart, while new sandboxes and supported CLI clients use the new value at once help-sbx sbx settings list. The CLI table cuts long descriptions. The Meaning column completes them from the docs settings page where the page has an entry. Where the page has none, the column says so and gives the full text of sbx settings list --json (capture/out/02-settings.json).

The settings keys

KeyTypeDefaultRestartMeaning
claude.remoteControlboolfalsenoLet the /remote-control channel of Claude Code authenticate with its own session token instead of the proxy swapping in the host credential (docs)
clipboard.imagePasteboolfalsenoLet sandboxed agents read host clipboard images, so a screenshot pastes with Ctrl+V (docs)
diagnostics.autoUploadstringemptynoConsent for automatic diagnostics uploads after daemon errors: yes, no, or empty for no decision yet (docs)
env.rememberHostCommandsboolfalsenoAsk about the host commands of an environment file only when they change, after a first approval (docs)
kit.allowExtractedAgentsbooltruenoAdmit the pinned kit references of agents that moved out of sbx into kits, even outside kit.allowedSources, and exempt them from kit.requireSignature (docs)
kit.allowLocalKitsbooltruenoAllow kits from local directories and ZIP files (docs)
kit.allowedSourcesjson["docker.io/"]noJSON array of allowed remote kit source prefixes, matched on a path segment boundary. ["*"] allows any source (docs)
kit.ignoreTransparencyLogboolfalsenoVerify keyless kit signatures without a Rekor entry, for kits signed with --tlog-upload=false (docs)
kit.requireSignatureboolfalsenoReject unsigned kits and kits signed by an untrusted signer. ZIP kits cannot carry a signature and are rejected (docs)
kit.trustedSignersjsonidentities ending in @docker.com through the Google issuernoJSON array of signer policies: a keyless identity with its issuer, or a public key file (docs)
mcp.forceLocalGatewayboolfalseyesUse the local MCP gateway when the account would otherwise use the hosted one (docs)
model.providersjson{}noJSON object of inference endpoints for sbx run --model, each with url, wire, and apiKeyEnv (docs)
no_proxystringemptyyesShared proxy exception list for sandbox, daemon, and supported CLI traffic (docs)
no_proxy.daemonstringemptyyesException list for daemon and CLI requests only. A non-empty value replaces the shared list for that scope (docs)
no_proxy.sandboxstringemptyyesException list for sandbox egress only. A non-empty value replaces the shared list for sandboxes (docs)
platform.allowExperimentalFeaturesbooltruenoAllow experimental features. The CLI text is complete. The docs say the default is false (conflict C18)
platform.images.registryMirrorstringemptynoMirror host for template and kit references that resolve to Docker Hub, without a URL scheme (docs)
platform.images.useDHIboolfalsenoUse the Docker Hardened Image variant dhi/sbx-templates:<tag> for the default agent templates (docs)
proxystringemptyyesUpstream proxy for sandbox, daemon, and CLI traffic: an HTTP, HTTPS, or SOCKS5 URL, a PAC source, system, or direct (docs)
proxy.daemonstringemptyyesUpstream proxy for daemon requests and supported CLI requests only (docs)
proxy.integratedAuthboolfalseyesNTLM or Kerberos authentication with the Windows sign-in identity. No effect on macOS or Linux (docs)
proxy.sandboxstringemptyyesUpstream proxy for sandbox egress only. It overrides proxy for that scope (docs)
sandbox.disk.dockerVolumestring10gnoSize of the /var/lib/docker volume of a new sandbox, at least 512 MiB. Existing volumes keep their size (docs)
skills.defaultModestringreadonlynoMode of the shared skills store when --skills is omitted: readonly, readwrite, or off (docs)
ssh.agentForwardingEnabledbooltrueyesLet clients forward an SSH agent into sandboxes. Private keys stay on the host (docs)
ssh.agentSocketPathstringemptyyesFixed host SSH agent socket path. Empty uses the socket each client supplies (docs)
ssh.autoCreateboolfalseyesCreate a sandbox on SSH connect when it does not exist. The docs have no entry (conflict C20)
ssh.defaultAgentstringshellyesBuilt-in agent used for SSH auto-created sandboxes. The docs have no entry
ssh.defaultTemplatestringemptyyesTemplate image override for SSH auto-created sandboxes (agent default if empty). The docs have no entry
ssh.workspaceRootstringemptyyesHost directory holding SSH auto-created sandbox workspaces (empty = mount-less, container-internal). The docs have no entry
tls.allowNegativeSerialboolfalseyesAccept server certificates with a negative serial number, as some TLS-inspecting proxies issue (docs)

Value types are bool, int, float, string, and json, and an override that conflicts with an administrator constraint is rejected help-sbx sbx settings set. Most changes apply within about five seconds help-sbx sbx settings.

Keys the docs name that the CLI did not print

The docs settings page, three guides, and one release note name keys that sbx settings list did not return (conflict C19). The manual prints them here and nowhere else.

KeyWhere the docs use itWhat the docs say
feature.modelthe local model guidesbx settings set feature.model true after platform.allowExperimentalFeatures true turns on sbx run --model docs-sbx Run a local model
feature.sandbox-gputhe GPU guidethe same pair of commands reveals the hidden --gpu flag docs-sbx Turn on the feature
feature.udp-egressthe network policy pagethe same pair of commands allows sbx policy allow network --protocol udp docs-sbx Allow outbound UDP
diagnostics.autoUploadErrorCooldownInDaysthe settings pageinteger, default 1, the number of days between automatic uploads, applied only when diagnostics.autoUpload is yes docs-sbx Settings
feature.sshthe v0.34.0 release notesbx settings set feature.ssh true enables the experimental native SSH endpoint rel-sbx v0.34.0

Environment variables that sbx reads

These variables configure the host side. They never set a variable inside a sandbox, and the daemon reads them only when it starts docs-sbx Settings.

VariableSetsSource
DOCKER_SANDBOXES_CLIPBOARD_IMAGE_PASTEclipboard.imagePastedocs-sbx Settings
DOCKER_SANDBOXES_CLAUDE_REMOTE_CONTROLclaude.remoteControldocs-sbx Settings
DOCKER_SANDBOXES_USE_DHIplatform.images.useDHIdocs-sbx Settings
DOCKER_SANDBOXES_KIT_ALLOWED_SOURCESkit.allowedSourcesdocs-sbx Settings
DOCKER_SANDBOXES_KIT_ALLOW_LOCALkit.allowLocalKitsdocs-sbx Settings
DOCKER_SANDBOXES_KIT_ALLOW_EXTRACTED_AGENTSkit.allowExtractedAgentsdocs-sbx Settings
DOCKER_SANDBOXES_KIT_REQUIRE_SIGNATUREkit.requireSignaturedocs-sbx Settings
DOCKER_SANDBOXES_KIT_TRUSTED_SIGNERSkit.trustedSignersdocs-sbx Settings
DOCKER_SANDBOXES_KIT_IGNORE_TLOGkit.ignoreTransparencyLogdocs-sbx Settings
DOCKER_SANDBOXES_PROXYproxy.sandbox, sandbox traffic onlydocs-sbx Settings
DOCKER_SANDBOXES_NO_PROXYno_proxy.sandbox, sandbox traffic onlydocs-sbx Settings
DOCKER_SANDBOXES_SSH_AUTO_CREATEssh.autoCreatecapture/out/02-settings.json, no docs entry
DOCKER_SANDBOXES_SSH_DEFAULT_AGENTssh.defaultAgentcapture/out/02-settings.json, no docs entry
DOCKER_SANDBOXES_SSH_DEFAULT_TEMPLATEssh.defaultTemplatecapture/out/02-settings.json, no docs entry
DOCKER_SANDBOXES_SSH_WORKSPACE_ROOTssh.workspaceRootcapture/out/02-settings.json, no docs entry
DOCKER_SANDBOXES_MODEL_PROVIDERSmodel.providerscapture/out/02-settings.json, no docs entry
DOCKER_SANDBOXES_ALLOW_EXPERIMENTAL_FEATURESplatform.allowExperimentalFeaturescapture/out/02-settings.json, no docs entry
DOCKER_SANDBOXES_TLS_ALLOW_NEGATIVE_SERIALtls.allowNegativeSerialdocs-sbx Settings
HTTP_PROXY, HTTPS_PROXY, NO_PROXY, and their lowercase formsthe upstream proxy when no proxy or no_proxy setting is setdocs-sbx Upstream proxies
DOCKER_SANDBOXES_DOCKER_SIZEthe Docker data disk size of one creation, such as 30gdocs-sbx Settings
DOCKER_SANDBOXES_ROOT_SIZEthe root filesystem size of one creation, such as 40gdocs-sbx Troubleshooting
DOCKER_SANDBOXES_CLONED_WORKSPACE_SIZEthe size of the private clone of a --clone sandboxdocs-sbx Troubleshooting
DOCKER_SANDBOXES_ENABLE_VIRTIOFS_CACHE0 turns off the virtiofs cache of the workspace mountdocs-sbx Troubleshooting
SBX_NO_TELEMETRY1 turns off CLI usage analyticsdocs-sbx Troubleshooting
DOCKER_ACCESS_TOKENthe Docker identity of sbx env commands, with a state scope per tokenhelp-sbx sbx env
SBX_MCP_URLnone selects the local MCP data plane instead of the hosted gatewayhelp-sbx sbx mcp add
SANDBOXES_STORAGE_ROOTnot documented in the vendored sources. The research notes name it from the v0.29.0 release note, whose body the vendored release list does not keepnone

Host lifecycle commands of an environment file receive SBX_LIFECYCLE_PHASE, SBX_ENV_FILE, SBX_ENV_FILES, SBX_ENV_DIR, SBX_SANDBOX_NAME, SBX_AGENT, and SBX_WORKSPACE docs-sbx Environment files. After a cloud creation they also receive SBX_SANDBOX_ID help-sbx sbx env.

Environment variables that docker-agent reads

The rename of v1.30.0 changed the prefix from CAGENT_ to DOCKER_AGENT_, and the old names stay accepted (conflict C53) rel-agent v1.30.0.

VariableLegacy nameMeaningSource
DOCKER_AGENT_DATA_DIRnoneThe data directory, the same as --data-dirhelp-agent docker-agent, added in v1.147.0 rel-agent v1.147.0
DOCKER_AGENT_CONFIG_DIRCAGENT_CONFIG_DIRThe config directory, the same as --config-dirdocs-agent Hooks, added in v1.100.0 rel-agent v1.100.0
DOCKER_AGENT_MODELS_GATEWAYCAGENT_MODELS_GATEWAYRoute model traffic through a gateway, the same as --models-gatewaydocs-agent User settings
DOCKER_AGENT_DEFAULT_MODELCAGENT_DEFAULT_MODELThe model used when none is given, as provider/modeldocs-agent User settings
DOCKER_AGENT_HIDE_TELEMETRY_BANNERCAGENT_HIDE_TELEMETRY_BANNER1 hides the first-run telemetry notice onlydocs-agent User settings
TELEMETRY_ENABLEDnone, no prefixfalse turns telemetry off (conflict C67)docs-agent Telemetry
DOCKER_AGENT_AUTO_UPDATEnone1, true, yes, or on lets a standalone release binary update itselfdocs-agent User settings, added in v1.74.0 rel-agent v1.74.0
DOCKER_AGENT_NO_TOKEN_EXCHANGEnone1 stops the exchange of the docker login access token for a Docker tokendocs-agent Secrets
DOCKER_AGENT_HUB_LOGIN_URLnonePoint the token exchange at a Docker staging environment, HTTPS docker.com URLs onlydocs-agent User settings
DOCKER_AGENT_AUTO_INSTALLnonefalse turns off automatic tool installation from the aqua registrydocs-agent Tools
DOCKER_AGENT_TOOLS_DIRnoneThe directory of installed tools, default ~/.cagent/tools/docs-agent Tools
DOCKER_AGENT_NO_SETUPnone1 stops the setup wizard from being offered when no model is usabledocs-agent features/cli
DOCKER_AGENT_BOARD_EDITORBOARD_EDITOR, kept for one releaseThe editor the board opens a worktree in, default codedocs-agent Board, renamed in v1.102.0 rel-agent v1.102.0
DOCKER_AGENT_PPROF_ADDRCAGENT_PPROF_ADDRA loopback address for a Go pprof server. The docs still print the legacy namerel-agent v1.139.0, docs-agent features/cli
DOCKER_AGENT_ENCRYPT_KEYnoneThe key of share push --key and share pull --keyhelp-agent docker-agent share push
GITHUB_TOKENnoneRaises the GitHub API rate limit of the auto-installerdocs-agent Tools
CAGENT_ASKPASS_SOCKET, CAGENT_ASKPASS_TOKENnone, still prefixed CAGENT_The sudo bridge of the shell toolset, set only on commands that call sudodocs-agent Shell
CAGENT_EXP_DEBUG_LAYOUT, CAGENT_HIDE_TELEMETRYrenamed in v1.30.0The new names are not documented in the vendored sourcesrel-agent v1.30.0

Provider credential variables such as ANTHROPIC_API_KEY are listed in R.6.

Paths on macOS

The capture machine runs macOS, so the table gives macOS paths first and the Linux and Windows forms where the docs state them. Replace rohitghumare with your user name.

PathHoldsToolSource
~/Library/Application Support/com.docker.sandboxes/the sbx state directory, removed as a last resort after sbx resetsbxdocs-sbx Removing all state
.../com.docker.sandboxes/sandboxes/sandboxd/sandboxd.sockthe Unix socket of sandboxdsbxresearch/sources/probes/sbx-daemon-status.txt
.../com.docker.sandboxes/sandboxes/sandboxd/daemon.logthe daemon logsbxresearch/sources/probes/sbx-daemon-status.txt
.../com.docker.sandboxes/sandboxes/agent-skillsthe shared skills store. Linux ~/.local/state/sandboxes/sandboxes/agent-skills, Windows %LOCALAPPDATA%\DockerSandboxes\sandboxes\state\agent-skillssbxdocs-sbx Share agent skills
~/Library/Logs/com.docker.sandboxes/sandboxes/auditkit/audit records written by the daemon. Linux ${XDG_STATE_HOME:-~/.local/state}/sandboxes/sandboxes/auditkit/, Windows %LOCALAPPDATA%\DockerSandboxes\sandboxes\logs\auditkit\sbxdocs-sbx Where records are stored
~/.config/sbx/credentials.yamlthe credential bindings file. Windows %APPDATA%\sbx\credentials.yamlsbxdocs-sbx Credential bindings
the macOS Keychainthe secret store behind sbx secret set. Windows uses the Credential Manager, Linux the Secret Service, or the file ~/.config/com.docker.sandboxes without onesbxdocs-sbx Credentials
~/.sbx/run/d/containerd/containerd.sock.ttrpcthe containerd ttrpc socket the daemon binds at start, named in the error the capture recordedsbxhelp-sbx sbx kit builder history ls
~/.sbxenv.yamla base environment file merged under every project filesbxhelp-sbx sbx env create
/opt/homebrew/Caskroom/sbx/0.47.0/Sbx.app/Contents/MacOS/sbxthe CLI binary of the Homebrew cask, with mkfs.erofs under Contents/libexecsbxresearch/sources/probes/sbx-diagnose.txt
~/.local/state/sandboxes/, ~/.cache/sandboxes/, ~/.config/sandboxes/the three Linux state directories, under XDG_* when set. Windows uses %LOCALAPPDATA%\DockerSandboxessbxdocs-sbx Removing all state
~/.config/cagent/config.yamluser settings, aliases, global permissions and hooks, board projectsdocker-agenthelp-agent docker-agent board
~/.config/cagent/.envthe env file that docker agent setup writes provider keys intodocker-agenthelp-agent docker-agent setup
~/.config/cagent/hooks.d/hook drop-in files, loaded in lexicographic orderdocker-agentdocs-agent Hooks
~/.cagent/the data directory, which holds session.db, worktrees, and plansdocker-agenthelp-agent docker-agent, docs-agent features/cli
~/.cagent/session.dbevery session, as SQLitedocker-agentdocs-agent Sessions
~/.cagent/cagent.debug.logthe debug log, with --debugdocker-agenthelp-agent docker-agent
~/.cagent/tools/bin/binaries the aqua auto-installer downloadsdocker-agentdocs-agent Tools
~/.cagent/plans/, ~/.cagent/session_plans/shared plans and per-session plansdocker-agentdocs-agent features/cli
~/.cagent/memory/<config-name>/memory.dbthe default database of the memory toolsetdocker-agentdocs-agent Memory
~/.cagent/themes/<name>.yamlcustom TUI themesdocker-agentdocs-agent User settings
<data-dir>/runs/<pid>.jsonthe discovery record of a run started with --listendocker-agentdocs-agent features/cli
~/Library/Caches/cagent/the cache directory on macOSdocker-agenthelp-agent docker-agent
~/Library/Caches/cagent/sandbox-kits/<hash>the kit that run --sandbox stages, keyed by the agent referencedocker-agentdocs-agent Sandbox
a private file under the cache directorythe cached Docker bearer token. Its name is not documenteddocker-agentdocs-agent Secrets
~/.codex/skills/, ~/.claude/skills/, ~/.agents/skills/, and the project .claude/skills/, .github/skills/, .agents/skills/the SKILL.md directories docker-agent discoversdocker-agentdocs-agent Sandbox

The directories of docker-agent keep the name cagent after the rename (conflict C54). Nothing migrates state from the plugin-era directories ~/.docker/sandboxes/ and ~/.sandboxd/ to the sbx state directory (conflict C17).

Sources:research/sources/probes/sbx-settings-list.txt, sbx-daemon-status.txt, sbx-diagnose.txt; capture/out/02-settings.json (the full descriptions of the four ssh.* keys, and the environment variables of the ssh.*, model.providers, and platform.allowExperimentalFeatures keys); research/sources/help-sbx.md (sbx settings, sbx settings list, sbx settings set, sbx env, sbx env create, sbx mcp add, sbx kit builder history ls); research/sources/docs-sandboxes.md (pages configuration/settings, configuration/environment-files, troubleshooting, governance/audit, workflows/agent-skills, the credentials and local model and GPU and network policy guides); research/sources/sbx-releases.md (v0.34.0); research/sources/help-docker-agent.md (root, board, setup, share push); research/sources/docs-docker-agent.md (pages features/cli, configuration/user-settings, configuration/hooks, configuration/tools, configuration/sandbox, features/sessions, guides/secrets, tools/memory, tools/shell); research/sources/docker-agent-CHANGELOG.md (v1.30.0, v1.74.0, v1.100.0, v1.102.0, v1.139.0, v1.147.0); research/conflicts-register.md rows C17, C18, C19, C20, C53, C54, C67

R.4

Agent file keys

Every key of the agent file at schema version 16, grouped by block, with its type, default, requirement, and meaning from agent-schema.json at v1.149.0.

The schema is titled Docker Agent Configuration, and the pinned copy is the file at tag v1.149.0, commit bf4169cdd31229d52385410c52c3dcc59b497858 schema Docker Agent Configuration. Its version enum runs from "0" to "16", so "16" is the current form schema version. The top level and every block below set additionalProperties to false, so an unknown key fails the load. The only required top-level key is agents schema agents. Required says whether the schema lists the key under required. Default is the schema default value, and none means the schema states none.

Top-level keys

All 16 keys come from the root properties object schema properties. The docs page configuration/overview explains the reusable blocks mcps, rag, commands, skills, and toolsets.

KeyTypeDefaultRequiredMeaning
versionstring, "0" to "16"nonenoConfiguration version
providersmap of ProviderConfignonenoReusable provider defaults: base_url, token_key, api_type
agentsmap of AgentConfignoneyesThe agents. At least one is required
modelsmap of ModelConfignonenoNamed model configurations
mcpsmap of MCPToolsetnonenoReusable MCP server definitions, referenced by name from a toolset
ragmap of RAGToolsetnonenoReusable RAG source definitions
commandsmap of CommandsnonenoNamed command groups, merged with use_commands
skillsmap of SkillsConfignonenoNamed skill groups, merged with use_skills
toolsetsmap of ToolsetnonenoNamed toolset definitions, appended with use_toolsets
metadataMetadatanonenoAuthor, license, readme, description, version, tags
permissionsPermissionsConfignonenoTool approval patterns for the whole file
runtimeRuntimeDefaultsnonenoExecution defaults the author wants. CLI flags and user settings win
budgetBudgetConfignonenoCeilings for one run, shared by every sub-session in it
budgetsmap of BudgetConfignonenoNamed budgets. Agents that share a name share one ceiling
flavorsmap of object or nullnonenoNamed YAML patches applied with --flavor, with JSON Merge Patch rules, key+ appends, key- removes
evaluatorsmap of EvaluatorConfignonenoNamed provider-backed assessments for tool_guard and routing hooks

Keys under agents

Every key of one agent comes from AgentConfig schema AgentConfig. The docs page configuration/agents explains them, configuration/structured-output covers structured_output, and features/harnesses covers harness.

KeyTypeDefaultRequiredMeaning
modelstringnonenoA model name from models or provider/model
fallbackFallbackConfig: models (array), retries (default 2), cooldown (default 1m)nonenoModels tried in order when the primary fails, with retry and cool-down rules
descriptionstringnonenoDescription of the agent
welcome_messagestringnonenoMessage shown when the agent starts
toolsetsarray of ToolsetnonenoThe toolsets of the agent
instructionstring or array of stringsnonenoThe system prompt. A list is joined with blank lines
instruction_filestring or array of stringsnonenoFiles, relative to the config file, whose content is the instruction. Exclusive with instruction
harnessHarnessConfig: type (required, claude-code, codex, pi, opencode), model, effort, agent, thinkingnonenoAn external coding CLI that runs the agent instead of a model provider
code_mode_toolsbooleannonenoExpose one tool that calls the others through JavaScript
sub_agentsarray of stringsnonenoAgents the parent delegates to with transfer_task: local names, OCI references, or name:reference
handoffsarray of stringsnonenoAgents that can receive the whole conversation
force_handoffstringnonenoThe agent that always receives the conversation after a final response
routingAgentRouting: allowed_agents (required), default_agentnonenoThe agents a routing hook can select, and the one used when an evaluator is uncertain
add_datebooleannonenoAdd the date to the context
add_environment_infobooleannonenoAdd cwd, git, OS, and arch to the context
readonlybooleannonenoKeep only tools with a read-only annotation in every toolset
safetystrict, balanced, restricted, autonomousnonenoDefault safety mode of new sessions on this agent, below any user choice
redact_secretsbooleantruenoInstall the redact_secrets builtin on tool input, model input, and tool output
max_iterationsintegernonenoMaximum loop iterations
budgetsarray of stringsnonenoNames of top-level budgets this agent spends against
max_consecutive_tool_callsinteger0, which means 5noIdentical tool calls in a row before the agent stops
max_old_tool_call_tokensintegernone, truncation offnoTokens kept from old tool arguments and results. -1 turns truncation off
max_tool_result_tokensintegernonenoTokens kept from each tool result, cut middle-out
num_history_itemsintegernonenoHistory items to keep
session_compactionbooleantruenoCompact the session at the threshold and after a context overflow
compaction_thresholdnumber0.9noFraction of the context window that starts compaction. The model value wins
compaction_modelstringnonenoModel that writes the summary. Highest priority of the three levels
add_prompt_filesarray of stringsnonenoPrompt files, such as AGENTS.md, added to the context
add_prompt_files_depthinteger0noLevels below the working directory in which the same file names are listed by path
commandsobject or arraynonenoNamed prompts for slash commands
structured_outputobject: name, description, schema, strictnonenoA JSON schema that constrains the response, native on OpenAI and Gemini
add_description_parameterbooleannonenoAdd a description parameter to every tool call
hooksHooksConfignonenoLifecycle hooks of this agent
cacheCacheConfig: enabled (default false), case_sensitive (false), trim_spaces (false), pathnonenoReplay the previous answer to the same question
skillsboolean or arraynonenotrue loads every discovered skill. A list mixes sources, names, and inline skills
use_commandsarray of stringsnonenoTop-level command groups to merge in
use_skillsarray of stringsnonenoTop-level skill groups to merge in
use_toolsetsarray of stringsnonenoTop-level toolsets to append after the inline ones

An inline skill under skills has name, description, and instructions as required keys, plus context: fork, model, allowed_tools, and toolsets schema InlineSkill. A command under commands is a string, or an object with description, instruction, agent, and url schema CommandConfig.

Keys under models

Every key comes from ModelConfig schema ModelConfig. The docs page configuration/models explains them, and configuration/routing covers routing.

KeyTypeDefaultRequiredMeaning
providerstringnoneno in the schema, yes in the docs unless first_available is setThe provider id, such as openai, anthropic, dmr
modelstringnoneno in the schema, yes in the docsThe model name
descriptionstringnonenoA human summary, not sent to the model
temperaturenumbernonenoSampling temperature
max_tokensintegernonenoMaximum output tokens per response, not the context window
top_pnumbernonenoTop-p sampling
frequency_penaltynumbernonenoFrequency penalty
presence_penaltynumbernonenoPresence penalty
base_urlstringnonenoThe API base URL, with ${env.VAR} substitution
parallel_tool_callsbooleannonenoAllow parallel tool calls
token_keystringnonenoEnvironment variable that holds the token
bypass_models_gatewaybooleannonenoConnect to the provider directly even when a models gateway is set
provider_optsobjectnonenoProvider options. For dmr: runtime_flags, context_size, keep_alive, and more in R.6
track_usagebooleannonenoTrack usage
thinking_budgetstring or integernonenoReasoning effort or token budget, in the forms R.6 lists
task_budgetinteger or objectnonenoTotal token budget of a task, sent to Anthropic as output_config.task_budget
routingarray of RoutingRule: model, examples (both required)nonenoRules that pick a model from example phrases. This model becomes the router
authAuthConfig: type (required, workload_identity_federation), workload_identity_federationnonenoA non-API-key scheme that wins over the provider path
first_availablearray of stringsnonenoCandidates in priority order. The first with credentials is used. Exclusive with the other keys
title_modelstringnonenoModel that writes session titles
compaction_modelstringnonenoModel that writes compaction summaries
compaction_thresholdnumber0.9noCompaction threshold for agents on this model
capabilitiesCapabilitiesConfig: image, pdf, audio, videononenoAttachment capabilities, when the models.dev catalogue is wrong or silent
output_capabilitiesOutputCapabilitiesConfig: imagenonenoWhether the model can generate images
costCostConfig: input, output, cache_read, cache_write, USD per million tokensnonenoPrices that override the catalogue

Keys under providers

Every key comes from ProviderConfig schema ProviderConfig. The docs page providers/custom explains them.

KeyTypeDefaultRequiredMeaning
providerstringopenai when unsetnoThe underlying type: openai, anthropic, google, amazon-bedrock, dmr, or a built-in alias
api_typeopenai_chatcompletions, openai_responsesopenai_chatcompletions in the schema, model-dependent in the docsnoThe API schema of an OpenAI-compatible provider
base_urlstringnonenoThe endpoint, required for OpenAI-compatible providers, with ${env.VAR} substitution
token_keystringnonenoEnvironment variable that holds the token
unload_apistringnonenoPath or URL of the model unload endpoint, used by the unload builtin
temperaturenumbernonenoDefault temperature
max_tokensintegernonenoDefault output tokens
top_pnumbernonenoDefault top-p
frequency_penaltynumbernonenoDefault frequency penalty
presence_penaltynumbernonenoDefault presence penalty
parallel_tool_callsbooleannonenoDefault for parallel tool calls
provider_optsobjectnonenoProvider options passed to the client
track_usagebooleannonenoDefault usage tracking
thinking_budgetinteger or stringnonenoDefault reasoning budget
task_budgetinteger or objectnonenoDefault task budget
authAuthConfignonenoA non-API-key scheme
compaction_modelstringnonenoDefault compaction model, lowest of the three levels

The federation block under auth requires federation_rule_id (prefix fdrl_), organization_id, and identity_token, and accepts service_account_id schema FederationAuthConfig. The token source has file, env, command, url, headers, and response_field schema IdentityTokenSourceConfig.

Keys under toolsets

Every key of one toolset entry comes from Toolset schema Toolset. The docs page configuration/tools explains the shared keys, and R.5 says which type uses which.

KeyTypeDefaultRequiredMeaning
typeone of 27 namesnoneno in the schemaThe toolset type
instructionstringnonenoReplaces the built-in instructions, or extends them with {ORIGINAL_INSTRUCTIONS}
toonstringnonenoComma-separated regular expressions of tools whose JSON output is re-encoded as TOON
readonlybooleannonenoKeep only tools with a read-only annotation
modelstringnonenoModel for the turn that processes results from this toolset
refstringnonenodocker:<name> or a name from mcps
configanynonenoTool-specific configuration
commandstringnonenoCommand of a stdio MCP or LSP server
remoteRemote: url (required), transport_type, headers, oauthnonenoA remote MCP server
argsarray of stringsnonenoArguments of the command
toolsarray of stringsnonenoAllow-list of tool names
envmap of stringsnonenoEnvironment variables
sharedbooleannonenoShare the tool state across agents, for think and todo
pathstringnonenoStorage path of memory or tasks
shellobjectnonenoScript definitions of script: cmd, description, args, required, env, working_dir
post_editarray of PostEditConfig: path, cmd (both required)nonenoCommands after an edit of filesystem or file
api_configApiConfig: name, endpoint, method (required), instruction, headers, args, required, output_schemanonenoThe HTTP tool of api
webhook_configWebhookConfig: url (required), provider, headers, chat_idnonenoThe destination of webhook
rag_configRAGConfig: strategies (required), tool, docs, respect_vcs (default true), indexing_timeout, resultsnonenoThe sources and strategies of rag
ignore_vcsbooleantruenoExclude .git and .gitignore patterns from filesystem operations
allow_listarray of stringsnonenoDirectories the file tools can reach
deny_listarray of stringsnonenoDirectories the file tools cannot reach. Wins over allow_list
deferboolean or arraynonenoLoad tools on demand through search_tool and add_tool
timeoutinteger30 when omittednoHTTP timeout in seconds for fetch, api, openapi
max_output_bytesinteger30000noText cutoff of openapi output. 0 turns it off
escape_htmlbooleanfalsenoLegacy HTML escaping in multi-URL fetch results
allowed_domainsarray of stringsnonenoHosts fetch can reach
blocked_domainsarray of stringsnonenoHosts fetch cannot reach. Exclusive with allowed_domains
allow_private_ipsbooleannonenoPermit non-public addresses for fetch, api, openapi, a2a, and remote mcp
sudo_askpassbooleannonenoPrompt for a sudo password through the host UI, for shell
recallbooleannonenoExpose a recall parameter on run_background_job
urlstringnonenoURL of a2a, openapi, or open_url
headersmap of stringsnonenoHTTP headers for openapi, a2a, and fetch
namestringnonenoTool name of a2a
file_typesarray of stringsnonenoExtensions an lsp server handles
allowed_serversarray of stringsnonenoCatalog server ids mcp_catalog offers
blocked_serversarray of stringsnonenoCatalog server ids removed from the offer
modelsarray of stringsnonenoModels model_picker can choose
versionstringnonenoowner/repo@version for auto-install, or false to turn it off
working_dirstringnonenoWorking directory of an mcp or lsp subprocess
lifecycleLifecycle: profile (resilient, strict, best-effort), required, startup_timeout, call_timeout, restart, max_restarts, backoffnonenoThe supervisor rules of an mcp or lsp toolset

A reusable entry under mcps has command, args, ref, remote, config, version, env, tools, instruction, name, defer, working_dir, and lifecycle schema MCPToolset. An OAuth block under remote has clientId, clientSecret, callbackPort, scopes, and callbackRedirectURL schema RemoteOAuthConfig.

Keys under permissions

The three keys come from PermissionsConfig schema PermissionsConfig, and the docs page configuration/permissions gives the pattern grammar.

KeyTypeDefaultRequiredMeaning
allowarray of stringsnonenoPatterns approved without confirmation, such as read_* or shell:cmd=ls*
askarray of stringsnonenoPatterns that always ask, even for read-only tools
denyarray of stringsnonenoPatterns always rejected. Wins over allow

Keys under hooks

The events come from HooksConfig schema HooksConfig, and the docs page configuration/hooks explains each one. Every event holds an array. Matcher events hold HookMatcherConfig entries with matcher, hooks (required), and preempt_yolo schema HookMatcherConfig. The other events hold hook definitions directly.

EventEntriesWhen it runs
prompt_file_guarddefinitionsBefore a loaded prompt file is stored or used. Every hook must approve
skill_content_guarddefinitionsBefore raw skill text is expanded. Every hook must approve
pre_tool_usematchersBefore a tool runs. Can allow, deny, or modify
post_tool_usematchersAfter a tool completes, with its response
permission_requestmatchersBefore the user is asked to approve a call
session_startdefinitionsWhen a session begins
user_prompt_submitdefinitionsOnce per user message, before the first model call
user_steering_messages_submitdefinitionsWhen queued mid-turn messages are appended
user_followup_submitdefinitionsWhen a follow-up message starts a fresh turn
turn_startdefinitionsAt the start of every model call, with transient context
turn_enddefinitionsWhen a turn ends, for any reason
before_llm_calldefinitionsJust before each model call
after_llm_calldefinitionsAfter each successful model call
session_enddefinitionsWhen a session ends
pre_compactdefinitionsBefore the transcript is compacted
subagent_stopdefinitionsWhen a sub-agent finishes
on_user_inputdefinitionsWhen the agent needs user input
stopdefinitionsWhen the model finishes responding
notificationdefinitionsWhen the agent sends an error or warning
on_errordefinitionsWhen a turn hits an error
on_max_iterationsdefinitionsWhen max_iterations is reached
on_agent_switchdefinitionsWhen the active agent changes
on_session_resumedefinitionsWhen the user lets the run continue past max_iterations
on_tool_approval_decisiondefinitionsAfter the approval chain decides, before the call runs or the denial is recorded
before_compactiondefinitionsImmediately before a compaction. Can veto it
after_compactiondefinitionsAfter a successful compaction
tool_response_transformmatchersBetween a tool run and the record of its response. Can rewrite the output
tool_input_transformmatchersBefore every tool call, ahead of approval. Can patch the arguments
tool_guardmatchersAfter the input transform and before approval. No safety mode bypasses it
before_agent_runrouting definitionsOnce per agent activation. Can route to another agent
after_agent_completerouting definitionsAfter an agent completes. Can route to another agent
worktree_createdefinitionsOnce, after --worktree creates a worktree

A hook definition comes from HookDefinition schema HookDefinition.

KeyTypeDefaultRequiredMeaning
typecommand, builtin, model, evaluatornoneyesWhat runs: a shell command, a named in-process function, a model, or an evaluator
commandstringnonenoThe shell command or the builtin name
argsarray of stringsnonenoArguments for the handler
namestringnonenoA name for logs and events
timeoutinteger60noSeconds before the hook is cut off
envmap of stringsnonenoEnvironment for this hook only
working_dirstringnonenoWorking directory of this hook
on_errorwarn, ignore, blockwarnnoWhat an error, timeout, or bad output does. pre_tool_use and tool_guard always fail closed
strict_outputbooleanfalsenoRequire one JSON object and reject unknown fields
modelstringnonenoThe provider/model of a model hook
promptstringnonenoThe Go template a model hook renders
schemastringnonenopre_tool_use_decision turns the model reply into a verdict
system_promptstringnonenoA literal system message for a model hook
evaluatorstringnonenoThe top-level evaluator of an evaluator hook
evaluator_policyEvaluatorPolicy: decisions, min_probability, fallback (all required)nonenoHow an evaluator verdict maps to a guard decision
routing_policyRoutingPolicy: routes, min_probability (both required)nonenoHow an evaluator choice maps to an agent

The type description names 17 builtins schema HookDefinition. They are add_context, add_date, add_environment_info, add_prompt_files, add_git_status, add_git_diff, add_directory_listing, add_user_info, add_recent_commits, max_iterations, redact_secrets, transform_json, limit_large_tool_results, safer_shell, http_post, snapshot, and unload.

Keys under runtime, budget, metadata, and evaluators

BlockKeyTypeDefaultRequiredMeaning
runtimesandboxbooleannonenoRun in a sandbox by default, as --sandbox does schema RuntimeDefaults
runtimenetwork_allowlistarray of stringsnonenoHosts added to the sandbox allowlist, host or host:port
runtimesafetyone of four modesnonenoDefault safety mode of the file, below a per-agent safety
budgetmax_costnumbernonenoMaximum USD per run, counting only priced responses schema BudgetConfig
budgetmax_tokensintegernonenoMaximum input plus output tokens over the run
budgetmax_timestringnonenoMaximum sum of turn durations, as a Go duration
metadataauthor, license, readme, description, version, tagsstrings, tags an arraynonenoDescriptive fields. version is used for OCI publishing schema Metadata
evaluatorsprovider, model, type, instructionsstrings, type one of boolean, choice, scorenoneyesA named assessment on the typesafe or openai Decisions backend schema EvaluatorConfig
evaluatorsbase_url, endpoint, token_key, bypass_models_gateway, choices, levels, timeout, costmixedtimeout 10s in the descriptionnoEndpoint, credential, and output shape of the evaluator

The docs pages configuration/budget and configuration/flavors explain budget and flavors with examples.

Sources:research/sources/agent-schema.json at tag v1.149.0 (root properties, and the definitions AgentConfig, ModelConfig, ProviderConfig, AuthConfig, FederationAuthConfig, IdentityTokenSourceConfig, Toolset, MCPToolset, Remote, RemoteOAuthConfig, PostEditConfig, ApiConfig, WebhookConfig, RAGConfig, Lifecycle, PermissionsConfig, HooksConfig, HookMatcherConfig, HookDefinition, RuntimeDefaults, BudgetConfig, Metadata, EvaluatorConfig, EvaluatorPolicy, AgentRouting, RoutingPolicy, RoutingRule, FallbackConfig, CacheConfig, CapabilitiesConfig, OutputCapabilitiesConfig, CostConfig, HarnessConfig, InlineSkill, CommandConfig); research/sources/docs-docker-agent.md (pages configuration/overview, configuration/agents, configuration/models, providers/custom, configuration/tools, configuration/permissions, configuration/hooks, configuration/budget, configuration/flavors, configuration/routing); research/sources/README.md (the pin of the schema copy)

R.5

Toolsets

The 27 toolset types of docker-agent 1.149.0, each with the tools it exposes, its options, and what it needs outside the agent file.

docker-agent toolsets printed 27 types on 2026-10-08 (research/sources/probes/docker-agent-toolsets.txt), and the type enum of the schema holds the same 27 names schema Toolset. The Tools column comes from the docs page of each type under tools/, and not documented marks a type with no page. Options are the keys of R.4 that the type reads, with the defaults the docs state. Needs says what the toolset reaches beyond the agent file: nothing, the host shell, the network, a credential, a binary, or Docker. The shared keys instruction, tools, readonly, model, defer, and toon apply to every type and are not repeated docs-agent Tool Configuration.

The 27 types

TypeToolsOptionsNeeds
a2aone tool per remote agent, named by name or from the agent card docs-agent A2A Toolurl (required), name, headers, allow_private_ipsthe network and the remote A2A server. A token in headers when the server was started with --auth-token
apione tool per api_config, named by api_config.name docs-agent API Toolapi_config with name, endpoint, method (GET or POST in the docs, five verbs in the schema), instruction, args, required, headers, output_schema. timeout (30), allow_private_ipsthe network. Tokens in headers through ${env.VAR}
background_agentsrun_background_agent, list_background_agents, view_background_agent, stop_background_agent docs-agent Background Agents Toolnoneagents listed under sub_agents
background_jobsrun_background_job, list_background_jobs, view_background_job, stop_background_job, wait_background_job (timeout default 60) docs-agent Background Jobs Toolenv, recall (false)the host shell
environmentnot documented. The probe summary says it reports the OS and the resolved shell, read-only, with no argumentsnone documentednothing
fetchfetch with urls, format (text, markdown, html), timeout (1 to 300) docs-agent Fetch Tooltimeout (30), allowed_domains, blocked_domains, allow_private_ips (false), headers, escape_html (false)the network. GET only. Non-public addresses are refused by default
filenot documented. The probe summary says it reads, writes, and edits individual filespost_edit, allow_list, deny_list, which the schema names for filesystem and filenothing
filesystemread_file, read_multiple_files, write_file, edit_file, list_directory, directory_tree, create_directory, remove_directory, search_files_content docs-agent Filesystem Toolignore_vcs (true), post_edit (path, cmd), allow_list, deny_listnothing
gitgit_status, git_log (limit default 20, path), git_branches, git_show (ref), git_blame (path required, rev) docs-agent Git Toolnonenothing. It uses go-git and needs no git binary
lsplsp_workspace, lsp_hover, lsp_definition, lsp_references, lsp_document_symbols, lsp_workspace_symbols, lsp_diagnostics, lsp_code_actions, lsp_rename, lsp_format, lsp_call_hierarchy, lsp_type_hierarchy, lsp_implementations, lsp_signature_help, lsp_inlay_hints docs-agent LSP Toolcommand (required), args, env, file_types, working_dir, version, lifecyclea language server binary, installed from the aqua registry when absent
mcpthe tools the server lists, filtered by tools docs-agent MCP Toolref (docker:<name> or an mcps name), or command, args, env, version, working_dir, or remote with url, transport_type (streamable or sse), headers, oauth. config, lifecycle, allow_private_ipsref: docker: needs the MCP Gateway and Docker. command needs the binary. remote needs the network and an OAuth login when the server asks for one
mcp_catalogsearch_remote_mcp_servers, enable_remote_mcp_server, list_remote_mcp_servers, disable_remote_mcp_server, reset_remote_mcp_server_auth docs-agent MCP Catalog Toolallowed_servers, blocked_serversthe network. OAuth for servers that require it. No gateway, because the catalog subset is streamable HTTP only
memoryadd_memory, get_memories, delete_memory, search_memories, update_memory docs-agent Memory Toolpath (~/.cagent/memory/<config-name>/memory.db)a SQLite file on disk
model_pickerchange_model (model required), revert_model docs-agent Model Picker Toolmodels (required)credentials for the listed models
open_urlone tool, open_url by default docs-agent Open URL Toolurl (required), namea browser on the host, through open, xdg-open, or rundll32
openapione tool per operation of the document docs-agent OpenAPI Toolurl (required), headers, timeout (30), max_output_bytes (30000), allow_private_ipsthe network, for the document and every call
planwrite_plan, read_plan, list_plans, delete_plan, update_plan_from_file, export_plan_to_file, set_plan_status, get_plan_status docs-agent Plan Toolnonethe store under ~/.cagent/plans/, shared by every agent with the type
ragone search tool named in rag_config.tool docs-agent RAG Toolrag_config with docs, strategies (chunked-embeddings, semantic-embeddings, bm25), results, respect_vcs (true), indexing_timeoutan embedding model, so a provider credential or Docker Model Runner, and a SQLite database per strategy
schedulercreate_schedule (prompt, when required, name), list_schedules, cancel_schedule (id required) docs-agent Scheduler Toolnonea running session that supports recall. Schedules are not persisted
scriptone tool per entry under shell, named by the key docs-agent Script Toolshell.<name>.cmd, description, args, required, env, working_dirthe host shell
session_contextlist_sessions, read_session docs-agent Session Context Toolnonethe session database
shellshell with cmd (required), cwd (.), timeout (30) docs-agent Shell Toolenv, sudo_askpass (false), safer (deprecated and ignored)the host shell. Every command is classified safe, destructive, or unknown before approval
taskscreate_task, get_task, update_task, delete_task, list_tasks, next_task, add_dependency, remove_dependency docs-agent Tasks Toolpath (tasks.json)a JSON file on disk
thinkone reasoning tool. The page names no tool docs-agent Think Toolshared in the schemanothing. No side effects
todocreate_todo, create_todos, update_todos, list_todos docs-agent Todo Toolshared (false)nothing
user_promptuser_prompt with message (required), title, schema docs-agent User Prompt Toolnonean elicitation handler, which the TUI and CLI provide and some MCP clients do not
webhooksend_webhook with message docs-agent Webhook Toolwebhook_config with url (required), provider (generic by default, or slack, discord, ifttt, telegram, mattermost, rocketchat, googlechat, teams), headers, chat_idthe network. The URL is itself the credential on Slack and Mattermost

Names that are not types

Two tools arrive without a toolsets entry. sub_agents injects transfer_task, and handoffs injects the tool of the same name (conflict C56). The docs built-in table lists both as types, and the schema enum and the probe exclude both.

NameToolInjected byNeeds
transfer_tasktransfer_task with agent, task, expected_output (all required), always auto-approved docs-agent Transfer Task Toolsub_agents on the callerthe named sub-agent
the tool of handoffsone tool with agent (required) that moves the conversation to a local agent and opens no network connection docs-agent Handoff Toolhandoffs on the callerthe named agent in the same file
session_planwrite_session_plan, read_session_plan, exit_plan_mode docs-agent Session Plan Toolthe docs list it as a built-in type. The v1.149.0 enum and the probe do not name it, so the pinned binary does not accept it as a typea per-session file under ~/.cagent/session_plans/

The docs name session_plan as a type, and the schema names 27 types without it. The schema ranks above the docs in this manual, so the manual treats session_plan as not available at the pin.

Sources:research/sources/probes/docker-agent-toolsets.txt; research/sources/agent-schema.json (Toolset, MCPToolset, Remote, ApiConfig, WebhookConfig, RAGConfig, ScriptShellToolConfig, PostEditConfig, Lifecycle); research/sources/docs-docker-agent.md (page configuration/tools and the 28 pages under tools/: a2a, api, background-agents, background-jobs, fetch, filesystem, git, handoff, lsp, mcp-catalog, mcp, memory, model-picker, open-url, openapi, plan, rag, scheduler, script, session_context, session_plan, shell, tasks, think, todo, transfer-task, user-prompt, webhook); research/conflicts-register.md row C56

R.6

Providers and models

Every provider id docker-agent 1.149.0 knows, the credential it reads, the provider/model reference form, the DMR endpoint order, the auto rule, and the keys that tune a model.

docker-agent doctor printed 20 providers with their credential variables on the capture machine, every one not set (research/sources/probes/docker-agent-doctor.txt). The docs name 31 ids, and the schema description of provider lists the built-in aliases schema ProviderConfig. The table below is the union, in the order of the doctor output and then of the docs. The doctor prints canonical ids such as fireworks-ai, and the legacy ids stay accepted as aliases (conflict C58).

The provider ids

Provider idLegacy idCredentialSource
anthropicnoneANTHROPIC_API_KEY, or auth.type: workload_identity_federationdoctor, docs-agent Models
openainoneOPENAI_API_KEYdoctor
chatgptnonenone. A browser sign-in through docker agent setup, shown as CHATGPT_OAUTH_TOKENdoctor, docs-agent Model Providers
github-copilotnoneGITHUB_TOKEN or GH_TOKEN, a PAT with the copilot scopedoctor, docs-agent Model Providers
googlenoneGOOGLE_API_KEY or GEMINI_API_KEY. Vertex AI uses GOOGLE_GENAI_USE_VERTEXAI, GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATIONdoctor, docs-agent Models
mistralnoneMISTRAL_API_KEYdoctor
openrouternoneOPENROUTER_API_KEYdoctor
basetennoneBASETEN_API_KEYdoctor
ovhcloudnoneOVH_AI_ENDPOINTS_ACCESS_TOKENdoctor
groqnoneGROQ_API_KEYdoctor
fireworks-aifireworksFIREWORKS_API_KEYdoctor, docs-agent Models
deepseeknoneDEEPSEEK_API_KEYdoctor
cerebrasnoneCEREBRAS_API_KEYdoctor
togetheraitogetherTOGETHER_API_KEYdoctor, docs-agent Models
huggingfacenoneHF_TOKENdoctor
moonshotaimoonshotMOONSHOT_API_KEYdoctor, docs-agent Models
vercelnoneAI_GATEWAY_API_KEYdoctor
amazon-bedrocknoneAWS_BEARER_TOKEN_BEDROCK, or the AWS credential chain, which the doctor counts as three more variablesdoctor, docs-agent Models
opencodeopencode-zenOPENCODE_API_KEYdoctor, docs-agent Model Providers
opencode-gononeOPENCODE_API_KEYdoctor
dmrnonenone. A local Docker Model Runnerdocs-agent Models
ollamanonenone. An optional base_urldocs-agent Models
xainoneXAI_API_KEYdocs-agent Models
nebiusnoneNEBIUS_API_KEYdocs-agent Models
nvidianoneNVIDIA_API_KEYdocs-agent Models
minimaxnoneMINIMAX_API_KEYdocs-agent Models
cloudflare-workers-ainoneCLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_IDdocs-agent Models
cloudflare-ai-gatewaynoneCLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, and CLOUDFLARE_GATEWAY_IDdocs-agent Models
requestynoneREQUESTY_API_KEYdocs-agent Models
azurenoneAZURE_API_KEY and a base_urldocs-agent Models
a name under providersnonethe variable in token_keyschema ProviderConfig

The doctor lists eleven ids that the docs tables do not count as providers, and the docs list ten that the doctor did not print. A credential can also come from ~/.config/cagent/.env, from a credential_helper command in the user config, from Docker Desktop, or from a 1Password op:// reference docs-agent Secrets.

The model reference

FormExampleMeaningSource
provider/modeldmr/ai/qwen3, anthropic/claude-sonnet-4-5An inline model on a known providerdocs-agent Models
a name from modelsmodel: localA named model with its own keysdocs-agent Models
name/modelmy_gateway/gpt-4oA model on a provider defined under providersdocs-agent Provider Definitions
a,banthropic/claude-sonnet-4-5,openai/gpt-5An alloy. The runtime alternates between the models in one conversationdocs-agent Models
automodel: autoThe first cloud provider with a credential, else a pulled DMR modeldocs-agent Set up a model
first_available: [...]a list of referencesThe first candidate whose credentials are configured, resolved at load timedocs-agent Models
--model [agent=]provider/model--model root=dmr/ai/qwen3A CLI override, repeatable per agenthelp-agent docker-agent run

The auto rule

auto picks the first cloud provider with a configured credential and then a locally pulled Docker Model Runner model docs-agent Set up a model. On the DMR side it prefers the model named in model: when that model is already pulled. Otherwise it takes the first available non-embedding model, instead of asking to pull ai/qwen3:latest docs-agent Docker Model Runner. It never picks a provider defined under providers docs-agent Provider Definitions. DOCKER_AGENT_DEFAULT_MODEL sets the model used when none is given docs-agent User settings. On the capture machine, with no credential and the runner unreachable, the doctor still printed auto -> dmr/ai/qwen3:latest and reported one issue (research/sources/probes/docker-agent-doctor.txt).

The DMR endpoint

StepEndpointSource
1base_url on the model or the provider, when setdocs-agent Docker Model Runner
2the endpoint the docker model plugin reports. The doctor runs docker model status --json through the Desktop contextresearch/sources/probes/docker-agent-doctor.txt
3the default http://127.0.0.1:12434/engines/llama.cpp/v1 when the plugin is not founddocs-agent Docker Model Runner, 03-stack.md note 14
in a containerhttp://model-runner.docker.internal/engines/v1, with unload_api: /engines/_unload on the providerdocs-agent Docker Model Runner
unloadthe base_url with the trailing /v1 replaced by _unload, called by the unload builtin on on_agent_switchdocs-agent Docker Model Runner

The runner needs no API key, and the API is not authenticated (conflict C84). The docs state only that the default URL is used when the plugin is not found. The order above puts that default third, after base_url and the plugin, as the research file reads it.

The models gateway

--models-gateway and DOCKER_AGENT_MODELS_GATEWAY route model traffic through one address, and bypass_models_gateway: true or a custom base_url sends a model directly to its provider docs-agent Model Configuration. docker agent models asks the gateway /v1/models first and uses the providers with credentials when the gateway answers nothing usable docs-agent features/cli. The Docker gateway needs a Docker token. Desktop hands out one that lasts 15 minutes. When Desktop has none, docker-agent exchanges the docker login access token for a fresh one over HTTPS docs-agent Secrets. The exchange is cached under the cache directory, DOCKER_AGENT_NO_TOKEN_EXCHANGE=1 turns it off, and docker agent debug auth shows the token in use.

Keys that tune a model

KeyApplies toValuesSource
temperature, top_p, frequency_penalty, presence_penaltyevery providersampling parameters, sent per requestschema ModelConfig
max_tokensevery provideroutput tokens per response, not the context windowschema ModelConfig
thinking_budgetOpenAInone, minimal, low, medium, high, xhigh, max. xhigh needs gpt-5.2 or later, none and max need gpt-5.6 or laterschema ModelConfig
thinking_budgetAnthropican integer from 1024 to 32768, adaptive, adaptive/<effort>, or an effort levelschema ModelConfig
thinking_budgetAmazon Bedrock with Claudean integer, or low, medium, highschema ModelConfig
thinking_budgetGemini 2.5an integer, -1 dynamic, 0 off, 24576 at mostschema ModelConfig
thinking_budgetGemini 3minimal on Flash only, low, medium, highschema ModelConfig
thinking_budgetDMR on llama.cppsent as llamacpp.reasoning-budget through _configure. On vLLM as thinking_token_budget per request. Ignored on MLX and SGLangdocs-agent Docker Model Runner
task_budgetAnthropican integer or {type: tokens, total: N}, sent as output_config.task_budgetschema ModelConfig
provider_opts.context_sizeDMRthe context window, sent through _configure. max_tokens is never the windowdocs-agent Docker Model Runner
provider_opts.runtime_flags, raw_runtime_flagsDMRflags for the inference runtime, as a list or one string. Exclusive with each otherdocs-agent Docker Model Runner
provider_opts.keep_aliveDMRa Go duration, 0 unloads at once, -1 never unloadsdocs-agent Docker Model Runner
provider_opts.modeDMRcompletion, embedding, reranking, image-generationdocs-agent Docker Model Runner
provider_opts.speculative_draft_model, speculative_num_tokens, speculative_acceptance_rateDMRspeculative decoding with a draft modeldocs-agent Docker Model Runner
provider_opts.gpu_memory_utilization, hf_overridesDMR on vLLMengine settings sent through _configuredocs-agent Docker Model Runner
provider_opts.supports_images, supports_pdfDMRdeclare attachment types, because DMR models are not in the models.dev catalogueschema ModelConfig
provider_opts.http_headersOpenAI-compatible providersheaders on every request, such as the Copilot integration idschema ModelConfig
fallback.models, retries (2), cooldown (1m)every providermodels tried after a failure, with backoffschema FallbackConfig
capabilities, output_capabilities, costevery providerattachment flags, image output, and USD prices that override the catalogueschema ModelConfig
title_model, compaction_model, compaction_threshold (0.9)every providercheaper models for titles and summaries, and the compaction pointschema ModelConfig
providers.<name> defaultsevery providertemperature, max_tokens, thinking_budget, task_budget, and the other defaults a model inheritsdocs-agent Provider Definitions

The docs give a default reasoning effort per provider docs-agent Models. It is medium on OpenAI always-reasoning models, off on Anthropic, -1 on Gemini 2.5, and model-dependent on Gemini 3.

Sources:research/sources/probes/docker-agent-doctor.txt, docker-agent-models-list.txt; research/sources/agent-schema.json (ModelConfig, ProviderConfig, FallbackConfig); research/sources/docs-docker-agent.md (pages concepts/models, configuration/models, providers/overview, providers/custom, providers/dmr, getting-started/set-up-a-model, guides/secrets, configuration/user-settings, features/cli); research/sources/help-docker-agent.md (run, models, doctor); /Users/rohitghumare/.cache/aiefs-manuals-wip/docker-research/03-stack.md note 14; research/conflicts-register.md rows C58, C84

R.7

Sources and the conflicts register

Two help trees, one schema, one kit specification, the vendored docs, the release notes, and one capture kit, with a ruling for every conflict a section touches.

The pin is sbx 0.47.0 and docker-agent 1.149.0 (manual.json). The agent file schema is at version 16, and the Sandbox Kit Spec at milestone v3.0.0-m.8. The pin date is 2026-10-07, and the facts were verified on 2026-10-08. Every file named below lives under research/sources/, and research/sources/README.md records its origin, commit, line count, and license.

The pin

ItemValue
sbxv0.47.0, commit 0411f50ee4700fe7bd37e6e7e3aced563e850ca9, Homebrew cask, released 2026-10-05
docker-agentv1.149.0, Homebrew build, tag commit bf4169cdd31229d52385410c52c3dcc59b497858, released 2026-10-07
agent file schemaversion 16, the agent-schema.json at the v1.149.0 tag
Sandbox Kit Specv3.0.0-m.8, tag commit 129be2ff45e8f9463450eb3cf04ddcb52c2b76e5, 2026-10-02, a pre-release
docs.docker.com252 pages served 2026-10-08, source repository docker/docs at 2c8a358489b56cd24069cc9e3a3d9a7b376dcfe7
capture machineone Mac, macOS, with the probes recorded between 06:55Z and 06:58Z on 2026-10-08

The ranked sources

When two sources disagree, the higher one wins and the section says so. The table repeats the ranking of manual.json with the citation keys of quoteSources.

RankSourceCited asUsed for
1the help text that sbx 0.47.0 and docker-agent 1.149.0 printhelp-sbx, help-agent with the commandevery command, flag, default, and path
2the agent file schema at v1.149.0schema with the definitionevery key, type, and default of the agent file
3Sandbox Kit Spec v3 at tag v3.0.0-m.8kitspec with the section, kitspec-main for unreleased changes, kitcap for a capability pagethe kit descriptor and each capability
4the Docker documentation, fetched 2026-10-08docs-sbx, docs-agent, docs-dmr, docs-mcp, docs-sbx-api, docs-desktop with the pagerules and limits the help text does not state
5the release notesrel-sbx, rel-agent with the versionwhen a behaviour appeared, changed, or was removed
6Docker blog posts and talks, through the research filesblog, talk with a date, declared nullhistory and positioning only, never a rule
7the capture kitthe file under capture/out/every command output, file, and record shown

The vendored files

FileOriginCommit, tag, or version
help-sbx.md, help-sbx-cloud.mdthe full sbx --help tree and sbx --cloud --help, program outputsbx v0.47.0 0411f50ee4700fe7bd37e6e7e3aced563e850ca9
help-docker-agent.mdthe full docker-agent --help treedocker-agent v1.149.0, Commit: Homebrew
help-legacy-docker-sandbox.md, help-legacy-docker-agent.mdthe docker sandbox and docker agent plugin trees, kept for comparisonplugin v0.12.0 f13b3c1a96a8be40b06473bb3db0c26dbfe1878c, plugin v1.32.4 bd55840ec12b55874dd9fccf88912f9b6bb3e3f3
agent-schema.jsondocker/docker-agent agent-schema.jsontag v1.149.0 bf4169cdd31229d52385410c52c3dcc59b497858
docker-agent-CHANGELOG.mddocker/docker-agent CHANGELOG.md, newest entry v1.149.0, no v1.146.0 entrytag v1.149.0 bf4169cdd31229d52385410c52c3dcc59b497858
docker-agent-README.mddocker/docker-agent README.mdmain 7a69c316f03c635d8d1951bc4677c59b47beedc7
SPEC-v3-at-v3.0.0-m.8.mddocker/sandbox-kit-spec docs/spec/SPEC-v3.md at the tagtag v3.0.0-m.8 129be2ff45e8f9463450eb3cf04ddcb52c2b76e5
SPEC-v3.md, kit-capabilities.md, kit-spec-extras.md, kit.schema.jsonthe same specification on main, 20 capability pages, the README and governance files, and the kit schemamain 4be7f4dff4d647f10c51dcdb392ce3fa18dc3e96, 2026-10-07
docs-sandboxes.md79 pages under /ai/sandboxes/docker/docs main 2c8a358489b56cd24069cc9e3a3d9a7b376dcfe7, served 2026-10-08
docs-docker-agent.md108 pages under /ai/docker-agent/the same
docs-model-runner.md, docs-mcp.md, docs-compose-models.md, docs-sandboxes-api.md, docs-desktop-release-notes.md8, 10, 3, 44 pages, and the Desktop release notes to 4.94.0the same
sbx-releases.mdthe GitHub releases of docker/sbx-releases, stable tags v0.21.0 to v0.47.0 only, bodies not keptAPI read 2026-10-08, repository main 2329d12106fee653c0890152947fdd827e00cfd0
mcp-gateway-README.md, model-runner-README.md, compose-for-agents-README.mdthe README of each repositorymain a34df45d4ec0e941a9853ad768c4f6cd818966b3, ed3e67a8205b8d068b9c65b30d3708132a231bba, bfd4fe952591495af757a1a737c7eacc78c75c15
probes/21 read-only command runs with timestamps and exit codessbx v0.47.0, docker-agent v1.149.0, 2026-10-08

The sbx help text and release notes are proprietary program output of Docker Inc., quoted as short quotations. The repositories docker/docker-agent, docker/sandbox-kit-spec, docker/docs, and docker/model-runner are Apache-2.0. The repository docker/mcp-gateway is MIT, and docker/compose-for-agents is Apache-2.0 or MIT (research/sources/LICENSES.md).

The conflicts register

research/conflicts-register.md holds 122 conflicts, C1 to C122, in nine groups, and 30 pieces of stale advice, S1 to S30. The table below keeps every C row that a section entry of the plan cites or whose ruling names a section. That is 94 rows, each with the ruling the manual follows. The 28 rows that no section uses are cut to keep the table short, and the next table names them by group. Their substance appears in the S rows below or in the register itself. Kind is the register's own label.

GroupRows cut
A, sbxC1, C2, C3, C5, C6, C8, C10, C11, C14, C22, C23, C24, C30
B, docker-agentC55, C57, C74
C, Docker Model RunnerC78, C80, C81, C82, C85
D, MCP gateway and ToolkitC86, C87
E, ComposeC94, C95, C97
F, OffloadC100
G, Cloud SandboxesC102
IdKindWhat disagreesRuling
C4talk vs docslibkrun or Firecracker as the VMM, against a VMM Docker wroteA VMM Docker wrote, on Hypervisor.framework, WHP, and KVM. libkrun stays unverified
C7removedAPI keys exported in the shell, against the secret store since v0.35.0Secrets come from the secret store. -e KEY is a plain variable, not a secret
C9docs vs CLIfive to nine agent names in blogs, against 11 in sbx run --helpThe 11 names of the help, the 8 create subcommands, and cagent as an alias
C12docs vs repoa 512 MB default kit volume, against 20 GiB in the spec docsBoth numbers with their sources, no single default
C13docs vs CLIv1, v2, and v3 kits, against sbx kit commands that name only v1 and v2The sbx kit artifact commands are v1 and v2 tooling. A v3 kit is an OCI image built with Docker tooling
C15community vs docsno Docker Desktop needed, against a required sign-inBoth true. No Desktop, but a Docker account sign-in
C16undocumentedsbx mount, sbx ssh proxy, sbx policy approval, --model, --provider, --usb in text, absent from the treeOnly what --help prints, and one note on the hidden names
C17undocumentedplugin-era state directories, against the sbx state directoryNo state migration exists
C18docs vs CLIplatform.allowExperimentalFeatures default false in the docs, true in the CLIThe value and SOURCE column from the capture
C19docs vs CLIfeature.* keys in the docs, absent from sbx settings listOnly keys the CLI returns, plus a note on the documented ones
C20undocumentedfour ssh.* keys in the CLI, absent from the docs: ssh.autoCreate, ssh.defaultAgent, ssh.defaultTemplate, and ssh.workspaceRootIncluded, with the full descriptions of sbx settings list --json
C21docs vs CLI11 built-in secret services in the docs, 13 in the CLI13 services
C25renamedthe plugin name rule, against the sbx ruleThe sbx rule: 2 to 63 characters, letters, digits, hyphens, periods, default reserved
C26undocumentedhost.docker.internal:3128 or gateway.docker.internal:3128, against docs with no addressThe address and variables the capture shows
C27blog staleUDP and ICMP blocked for good, against UDP behind feature.udp-egressUDP rules exist behind the experimental flag. ICMP is blocked. DNS is policy-controlled
C28experimentalfilesystem policies in ls and audit, against policy log that does not support themFilesystem rules list but do not log in v0.47.0
C29talk vs docsnear-instant start, against secondsThe manual's own timing on one machine, no vendor number
C31removeddocker sandbox exec -d, against sbx exec -d not supportedSaid so in 2.2
C32renameddocker sandbox save into the host daemon, against the runtime image storeSaid so in 2.3 and the migration table
C33undocumentedGordon runs on the host, against sbx reset clearing Gordon sessionsThe help line, nothing more
C34community vs docsheader matching rules for custom secrets, against --header and --format cloud onlyThe captured request on the receiver. --header and --format cloud only
C35docs vs CLIproxy-managed sentinels, against GHO_SBX_PROXY_MANAGED and docker-placeholder-valueThe captured values
C36docs vs docscloud needs 0.45.0, against 0.45.10.45.1
C37undocumentedaudit JSONL needs 0.39.0, against no word on subscriptionsWhat the directory holds after the runs
C38removedrelative --command helpers, against a fresh temporary directory since v0.46.0Absolute helper paths outside writable mounts
C39removed-p binds both loopbacks, against tcp4 since v0.42.0The tcp4 rule
C40removedkits from any registry, against kit.allowedSources since v0.34.0The setting is changed for a local registry and the error without it is shown
C41removedglobal rules only, against per-sandbox policies since v0.29.0The two scopes global and local
C42docs vs CLIssh <name>.sbx through a managed block, against sbx setup ssh detailsThe help text facts
C43docs vs CLItemplates and kits as different things, against a kit as the positional agentThree words defined once in 2.1
C44undocumentedthe TUI as the dashboard, against sbx with no commandBoth entry points
C45community vs docs16K guest pages, against the sbx diagnose lineThe diagnose line
C46docs vs CLIsbx kit ls, against unknown commandThe ten sbx kit subcommands the help lists
C47docs vs CLIthe same verbs with --cloud, against hidden and cloud-only verbsEach verb marked local, cloud, or both
C48docs vs CLIplugin v1.32.4, against the Homebrew v1.149.0v1.149.0 from Homebrew, both versions captured
C49docs vs docsDocker Agent in Desktop 4.63, against the first release note at 4.64.04.63 per the docs, first note 4.64.0
C50undocumenteda stated bundle per Desktop release, against releases that state noneA footnote in 7.3
C51renamedcagent commands and docs, against docker agent and /ai/docker-agent/Migration rows S5 to S9
C52removedcagent config, feedback, build, catalog, exec, against the v1.23.4 restructureMigration rows S6 and S7
C53renamedCAGENT_* variables, against DOCKER_AGENT_* since v1.30.0The new names with the legacy aliases
C54renameda complete rename, against directories still named cagentThe paths as they are
C56docs vs repohandoff and transfer_task as types, against 27 types without themNot a type. Separate rows marked implicit
C58renamedfireworks, together, moonshot, opencode-zen, against canonical idsCanonical ids with an alias column
C59renamed--yolo as the way, against --safety autonomous--safety autonomous, with --yolo as its alias
C60docs vs CLI--sandbox needs Desktop, against --sbx default true--sbx=false has no working target on Desktop 4.80.0 or later
C61docs vs docsdocker/docker-agent-sbx-templates:latest, against docker/sandbox-templates:docker-agentTwo images for two launch paths
C62undocumented--yolo inside --sandbox, against a cut defaultThe mode stays unknown in this edition. The run stopped at "attempt to write a readonly database (1032)" before it printed one (27-sandbox-run.txt)
C63docs vs CLI<data-dir>/session.db, against session.db in the current directory for serve apiBoth defaults
C64renamedserve a2a -a defaults to root, against the team's first agentThe v1.149.0 text
C65docs vs CLIeval -c as the CPU count and an Anthropic judge, against 10 and openai/gpt-5.6-terra10 and openai/gpt-5.6-terra
C66docs vs CLInew --model with 30 providers, against four named in the helpnew auto-selects among those, run --model takes any provider
C67docs vs repotelemetry off through DOCKER_AGENT_*, against TELEMETRY_ENABLED=falseBoth printed
C68undocumenteda CLI reference at /reference/cli/docker/agent/, against a 404The features page and the help tree
C69undocumentedN hook built-ins, against eight confirmed namesOnly the confirmed built-ins
C70docs vs docsserve a2a as full A2A, against listed limitationsThe captured card and each limitation the capture shows
C71undocumentedthe card at /.well-known/agent-card.json, against no stated pathThe path that answered and the protocolVersion field
C72undocumentedGordon as separate, against docker ai calling Docker AgentOne sentence in 1.1
C73undocumentedref: docker:<name> through the gateway, against no word on the ToolkitBoth outcomes
C75undocumenteddocker agent with no arguments runs run, against getting-started listed firstWhat its help says
C76docs vs docsserved agents forward budgets, against no forwardingBudgets do not apply to served agents
C77docs vs CLIdocker model configure, against a missing commandWhichever exists. Docker Agent sets context_size through _configure
C79docs vs docs/anthropic/v1/messages, against /v1/messagesThe one that answered 200
C83undocumentedcontext_size applied, against issue #4522The request body the runner received
C84docs vs docsno key needed, against an unauthenticated APIThe same fact, said in 6.5
C88docs vs repoan invite-only gateway, against an MIT gatewayTwo things share a name, separated in 6.5
C89docs vs repono default for --verify-signatures, against trueThe default from the help
C90docs vs reporegistry references as supported, against partly implementedMarked partly implemented in the Toolkit
C91docs vs docsone gateway for everything, against a separate sandbox gatewayThree gateways named
C92undocumenteda known bundled gateway version, against 0.42.2 last statedBoth outputs
C93docs stalethe Action installs v0.22.0, against v0.44.1One sentence in 7.3
C96community vs docsCompose starts sandboxes, against no such featureOne sentence in 6.5
C98blog stale300 free GPU minutes, against no public priceOffload is out of scope
C99docs vs docsOffload as the cloud path, against sbx --cloudSaid so in 7.1
C101docs vs docsprices documented, against prices in a blog and template load --helpShapes from the help, prices from the blog with its date
C103docs vs docspay-as-you-go on a Personal account, against Personal and ProPersonal or Pro, the rest unverified
C104docs vs docsdocker exec and healthchecks work in the cloud, against the VM filesystem being reached insteadA documented limitation
C105talk vs docsWarp Oz on Docker cloud sandboxes, against no confirmationOmitted
C106blog stalethe sandbox primitive inside Kubernetes, against no other mentionOmitted
C107removedthe plugin still in Desktop, against removal in 4.80.0The error text the capture shows
C108removed--mount-docker-socket, --load-local-template, --pull-template, against none in sbxMigration rows S1 to S4
C109renamednetwork proxy flags, against policy subcommands with no bypassMigration rows S10 and S11, bypass with no replacement found
C110removedthe legacy default allowed hosts, against three presetsThe legacy list only in 7.3
C111removedcagent in Desktop, against removal in 4.81.0Migration row S8
C112removedkeys from the daemon environment and state under ~/.docker/sandboxes/, against the sbx storeOne table in 7.3
C113docs vs CLIindex annotations that the frontend promotes (kitspec §9.3), against an index with an attestation manifest and no annotations4.1 prints both. The annotations sit on the platform manifest (28-index.json, 28-manifest.json), which a consumer reads when the index has none
C114docs vs CLIa floating docker/sandbox-kit:3 that never moves for a milestone, against a resolve to 3.0.0-m.84.1 prints both (28-buildx.txt). A build that needs the same frontend every time names the exact version tag
C115docs vs CLIdocker buildx build -f kit.yaml --push as the publish command, against a schema 2 manifest with no annotations from the default docker driver4.1 says to build with a docker-container builder (28-manifest-docker-driver.json, 28-manifest.json). An image without the annotation is not a kit
C116docs vs CLIa local source directory passed to sbx during development, against sbx kit inspect failing on Desktop 4.94.04.1 prints both (13-kit-v3-inspect.txt, 28-kit-inspect-source.txt). No capture ran sbx run on a directory
C117docs vs CLIsource builds in the sbx-kit-builder sandbox, against a build through the host Docker daemon and a builder "not created"4.1 and 4.3 print the help line and the status (13-kit-builder-status.txt). Where a successful source build runs stays unstated
C118docs vs repoagent file config version 15 as current, against a schema enum to "16" and captured files that load"16" from the schema (5.2, R.4). The docs page still names 15
C119docs vs CLIsession titles made from the first message, against five sessions titled Running agent5.6 prints the five rows (21-session-db.txt)
C120docs vs CLIa skill name and mode lists that A2A 1.0.1 requires, against a card with an empty skill name and two empty mode lists6.3 prints the card against the rules (23-agent-card.json). A client accepts the empty values
C121docs vs CLI"A2A artifact support not yet integrated", against a task whose artifacts hold the answer6.3 says the capture contradicts the limitation (23-a2a.http)
C122docs vs CLIa release note that points to docker sbx, against a notice that points to the product page and a plugin list that still shows sandbox v0.13.07.3 prints the notice (29-docker-sandbox.txt, 29-docker-plugins.txt). The plugin entry stays, and the command only prints the notice

Stale advice

The migration section prints every row. The last column is what the register says to do now.

IdThe advice as printedStopped being trueDo this instead
S1docker sandbox run <agent>Desktop 4.80.0, 2026-06-29sbx run <agent>
S2docker sandbox run --mount-docker-socket kiroDesktop 4.58.0, 2026-01-26Every sandbox has a private Docker Engine
S3--load-local-templateDesktop 4.61, 2026-02-18sbx template load FILE, then --pull never -t TAG
S4--pull-template missingv0.21.0, 2026-03-31--pull always, missing, or never, default always
S5docker sandbox create cagent .Desktop 4.80.0sbx create docker-agent ., with cagent kept as an alias
S6cagent run agent.yaml, cagent new, cagent execv1.23.4, 2026-02-19, and Desktop 4.81.0docker agent run, docker agent new, docker agent run --exec
S7cagent push, pull, acp, api, mcp, a2av1.23.4docker agent share push or pull, docker agent serve acp, api, mcp, a2a
S8cagent version, brew install cagentv1.30.0, 2026-03-09, and Desktop 4.81.0docker agent version, brew install docker-agent, winget install Docker.Agent
S9cagent config, feedback, build, catalogv1.23.4Removed. The catalog is the Hub namespace agentcatalog/*
S10docker sandbox network proxy S --allow-host api.example.comDesktop 4.80.0sbx policy allow network api.example.com [--sandbox S]
S11docker sandbox network log --jsonDesktop 4.80.0sbx policy log [S] --json
S12docker sandbox save S TAG into host DockerDesktop 4.80.0sbx template save S TAG [-o FILE] into the runtime store
S13a proxy set by hand at host.docker.internal:3128sbx, where the daemon configures the proxyNothing. The capture prints what the sandbox sees
S14API keys in ~/.zshrc and a Desktop restartv0.35.0, 2026-07-10sbx secret set SERVICE or sbx secret import
S15sbx run claude --branchv0.31.0, 2026-05-28sbx run claude --clone
S16the kit v1 grammar with schemaVersion: "1"v2 recommended 2026-09-09, v3 published 2026-09-24v2 spec.yaml for sbx kit artifacts, v3 kit.yaml with # syntax=docker/sandbox-kit:3 for OCI kits
S17sbx mcp catalogv0.45.0, 2026-09-21sbx mcp add --url <registry or manifest URL>
S18sbx mcp enable github-official --sandbox my-projectnever in a help treesbx mcp add, then --static-mcp or sbx mcp load
S19Docker Agent v2.x renamed the CLInever trueThe latest is v1.149.0
S20docs.docker.com/ai/cagent/the v1.30.0 renamedocs.docker.com/ai/docker-agent/
S21CAGENT_MODELS_GATEWAY, CAGENT_CONFIG_DIR, CAGENT_PPROF_ADDRv1.30.0, still acceptedDOCKER_AGENT_MODELS_GATEWAY, DOCKER_AGENT_CONFIG_DIR, DOCKER_AGENT_PPROF_ADDR
S22docker/sandbox-templates:cagentthe renamedocker/sandbox-templates:docker-agent
S23a --command helper written as ./helper or cat tokenv0.46.0, 2026-09-28An absolute path outside writable sandbox mounts
S24-p 3000:8080 binds IPv4 and IPv6v0.42.0, 2026-09-07Default tcp4. Write 3000:8080/tcp for both
S25kits from any registryv0.34.0, 2026-06-26Add the prefix to kit.allowedSources
S26docker cp of the agent binary and agent run dev-team.yaml insidesbx and the docker-agent templatesbx run docker-agent . or docker agent run --sandbox agent.yaml
S27one sandbox per workspacenames default to <agent>-<workdir>Name sandboxes with --name
S28Windows 10 is supportedv0.35.0, 2026-07-10Windows 11 with the Windows Hypervisor Platform
S29the Toolkit gateway at host.docker.internal:8811 in five stepsv0.38.0, 2026-08-06sbx mcp add and sbx mcp load. The Toolkit gateway is separate
S30a paid Anthropic judge model for evalv1.147.0, 2026-10-05Default openai/gpt-5.6-terra, any provider/model works

Sources:manual.json (pin, sources, quoteSources); research/sources/README.md (origins, commits, tags, versions, and the trim of 2026-10-08); research/sources/LICENSES.md; research/conflicts-register.md (rows C1 to C122 and S1 to S30, with the plan's citations in research/plan.md used to pick the rows); research/sources/probes/ (the probe timestamps); capture/out/13-kit-builder-status.txt, 13-kit-v3-inspect.txt, 21-session-db.txt, 23-a2a.http, 23-agent-card.json, 27-sandbox-run.txt, 28-buildx.txt, 28-index.json, 28-kit-inspect-source.txt, 28-manifest.json, 28-manifest-docker-driver.json, 29-docker-plugins.txt, 29-docker-sandbox.txt (the files that rows C62 and C113 to C122 name)

R.8

Glossary and index of figures

Each term below has one meaning across the manual and names the section that defines it, and the index lists every figure by the claim it makes.

Every term is defined once, in the section the last column names, with the sources that section cites. The meanings below come from the help text, the schema, the kit specification, and the docs named in the Sources line.

Glossary

TermMeaning in this manualDefined in
AgentOne entry under agents, with a model, an instruction, toolsets, and optional sub-agents5.2
Agent fileThe YAML or HCL file docker-agent run loads, titled Docker Agent Configuration in the schema: agents, models, providers, toolsets, and the rules between them5.2
AlloyA model reference of two models with a comma between them, a,b, that the runtime alternates between in one conversation5.2
AttestationA signed statement attached to an artifact: SLSA provenance on a kit, or the DSSE publication statement on a shared agent4.3 and 6.4
Audit recordOne JSONL line the daemon writes for each decision under the auditkit directory, rotated by time, count, and size7.2
Background agentA sub-agent started by run_background_agent that runs while the parent continuessub_agents, transfer_task, and background_agents
BindingA record in credentials.yaml of the credential mechanism and the domains approved for one service3.5
CapabilityA typed, versioned request a kit makes of the host, com.docker.sandbox/<name>@N, answered as granted, refused, or prompted4.2
CassetteThe file --record writes with the model API interactions of a run, and --fake replays5.1
Clone modeThe --clone workspace mode: the host repository is mounted read-only at /run/sandbox/source and the agent works on a private clone, whose commits return through the sandbox-<name> remote3.2
Cloud sandboxA sandbox run through sbx --cloud on Docker's compute, with no host workspace, a shape, and a TTL7.1
DelegationThe transfer_task tool: the parent sends a task to a sub-agent, which runs in a sub-session and returns a resultsub_agents, transfer_task, and background_agents
Environment filesbxenv.yaml, which declares the agent, kits, workspace, secrets, ports, MCP servers, and host commands of one sandbox4.4
Environment planThe list of everything applying an environment file would set up, printed by sbx env plan and approved before create or run acts4.4
EvalA saved session that docker-agent eval replays in a container and scores5.6
FlavorA named YAML patch under flavors, applied with --flavor before the file is parsed5.2
Forward proxyThe host proxy that HTTP and HTTPS requests from a sandbox pass through. It enforces policy and injects credentials3.4
GatewayThe one MCP endpoint a sandbox sees, served on the host, through which every registered server is reached. The Toolkit gateway and the hosted gateway are separate things with the same name3.6
Governance profileA named profile from remote governance policies, assigned to a sandbox with --profile7.2
handoffThe tool that handoffs: injects. It moves the whole conversation to another agent in the same session, and the previous agent leaves the loopsub_agents, transfer_task, and background_agents
HarnessAn external coding CLI, claude-code, codex, pi, or opencode, that runs an agent instead of a model provider5.1
HookA command, builtin, model, or evaluator that runs at one of the 33 lifecycle events of an agent5.5
KitOne OCI image whose manifest annotation vnd.docker.sandbox.kit.descriptor carries its declarations. For the sbx kit commands, a v1 or v2 artifact with a spec.yaml4.1
Kit argumentA value for an argument a kit declares, given as --kit-arg name=value4.1
Kit setA kind: set descriptor that lists kits and is never published4.1
MixinA kit of kind: mixin: an overlay on the filesystem of a workload, zero or more per composition4.1
Model referenceprovider/model, a name from models, auto, an alloy, or a first_available list5.2
Models gatewayAn address set with --models-gateway that model traffic routes through, with a Docker tokenR.6
Permission ruleAn allow, ask, or deny pattern on a tool name and its arguments, evaluated deny, then allow, then ask5.5
PolicyA set of rules that controls what sandboxes can reach. Local rules apply to all sandboxes or to one3.3
Policy scopeglobal, all sandboxes, or local, one sandbox named with --sandbox3.3
Presetallow-all, balanced, or deny-all, chosen once with sbx policy init3.3
ProviderA model API docker-agent knows by id, such as anthropic or dmr, or an entry under providers with its own base URL5.2
Proxy typeThe PROXY column of sbx policy log: forward, forward-bypass, transparent, network, or browser-open3.4
Proxy-managedA credential whose real value stays on the host while the proxy swaps its sentinel into outbound requests. Also the literal sentinel value proxy-managed3.5
RuleOne allow or deny entry of a policy, with a RULE_ID, a resource pattern, and a protocol3.3
Safety modestrict, balanced, restricted, or autonomous: what the runtime does with a tool call that no permission rule matched5.5
SandboxA microVM with its own kernel and a private Docker Engine, in which the agent runs as a container. It has a name, a workspace, a template, and a lifecycle2.1
sandboxdThe host daemon that owns every local sandbox, reached over a Unix socket2.4
SecretA value sbx secret set stores in the host keychain for one of 13 services, a custom host, or a registry. It never enters the sandbox3.5
SentinelThe placeholder value the agent sees in place of a secret, such as proxy-managed3.5
SessionThe record of one conversation in session.db: every message, tool call, sub-agent run, and cost5.6
ShapeA billable cloud size, micro, small, medium, large, or xl, from 1 vCPU and 2048 MiB to 16 vCPU and 32768 MiB7.1
Skills storeThe shared directory of SKILL.md skills that sbx links into every sandbox, read-only by default4.5
Static setThe MCP servers fixed at creation with --static-mcp, as opposed to servers attached later with sbx mcp load3.6
Sub-agentAn agent listed in sub_agents that the parent delegates to with transfer_tasksub_agents, transfer_task, and background_agents
TemplateA container image a sandbox starts from: the default docker/sandbox-templates:<agent> image, a saved snapshot, or a loaded tar2.3
ToolsetOne entry under toolsets with a type from the 27 built-in types, which gives the agent a set of tools5.3
Transparent proxyThe host proxy that intercepts TCP traffic other than HTTP and HTTPS. It enforces policy and injects nothing3.4
TTLThe time-to-live of a cloud sandbox, 1 hour by default, under a 24 hour ceiling from creation7.1
WorkloadA kit of kind: workload: the root filesystem with entrypoint, cmd, env, user, and workdir, exactly one per composition4.1
WorkspaceThe host directory a sandbox mounts at the same absolute path, read-write by default, with extra paths marked :ro2.1

Index of figures

Each number links to its figure, and the claim is the bold sentence that opens the caption.

Fig.The claim it makes
0.1Each of the eight arrow styles marks one kind of exchange, and every label is a real command, rule, status, or event name from the capture.
1.1sbx owns every layer from the daemon to the guest kernel, and docker-agent owns its binary, its files, and the loop inside the guest.
1.2Both paths end with docker-agent in a microVM, but sbx run starts from a template image and docker-agent run --sandbox starts from your agent file.
1.3docker-agent stages a kit, has sbx create the VM, and opens two hosts on the proxy, and the model call from inside then leaves through that proxy.
2.1A sandbox is absent, running, stopped, or removed, and every sbx verb in this section moves it along exactly one edge.
2.2A template moves from a stopped sandbox into the runtime image store, out as a tar, and into a new sandbox with --pull never -t.
2.3Everything sandboxd owns on macOS sits under one Application Support directory, with the audit log under Logs and a symlink at ~/.sbx/run.
3.1The agent sits on a private Docker Engine inside a guest kernel, and five doors cross the hypervisor line: mount, network, secret, MCP, and SSH agent.
3.2The host repository enters the VM read-only at /run/sandbox/source, the agent commits to a private clone, and git-daemon serves that clone back as a host remote.
3.3A matching deny ends the walk at once, an allow from any active scope admits the host, and a request that matches nothing is denied as implicit.
3.4The first CONNECT is denied inside TLS by the proxy itself, one allow rule later the same CONNECT becomes a forward-bypass tunnel, and both leave a log row.
3.5A host allow passes any request to github.com, body included, while a network-policy@2 entry denies one method on one host and leaves the rest open.
3.6The sandbox only ever holds sbx-cs-<rand>, the forward proxy swaps it for the stored value on the bound host, and a direct connection gets no swap.
3.7The host store holds m101-deepwiki once, each sandbox gets its own gateway at one URL, and a static set is fixed while load changes a dynamic one live.
4.1The frontend copies the hello-kit descriptor into one manifest annotation, derives six more annotations from its fields, and records its own release in built-by.
4.2One workload and two mixins merge into one grant set where allows union, deny wins, and a removed deny on the next version stops for approval.
4.3A v2 kit is validated and packed on the host, signed and pushed with two referrers, verified from the registry, and consumed by create or kit add.
4.4sbx env plan turns the file into margin-marked rows, approval on create records them, and a later plan prints only the rows that moved.
5.1run loads the agent file, alternates model calls and tool calls in one loop, and writes the answer, the events, a session, and with --record a cassette.
5.2files.yaml resolves local to the dmr provider and finds the endpoint itself, while dmr.yaml resolves qwen through providers.runner to a fixed URL.
5.3Each toolsets entry becomes named tools in the model request: built-in types run in process, and mcp goes through a gateway, a child process, or a URL.
5.4transfer_task sends writer only the task and returns its sentence to root, while the session move makes reviewer answer every later message.
5.5The preempting hook allows every call only as advice, the patterns decide rm and echo, and the safety mode alone decides pwd.
5.6The two replays of files.yaml match on all five turns, the guarded run differs at turn 0, and eval scores the same agent per tool call.
6.1A session is created first and stored, and one POST to the agent path returns the whole turn as SSE frames from team_info to stream_stopped.
6.2The same pong.yaml answers an editor through stdin and stdout and an MCP client through HTTP POSTs to port 8081, with different method names on each path.
6.3The client reads the card, sends one blocking SendMessage that makes one model call, and gets back a completed task that GetTask returns again.
6.4One share push stores files.yaml as an OCI manifest with four annotations, and share pull and run both read it back by the same reference.
6.5Five callers reach the same runner through five base URLs, and every path after them is open to any client that reaches the port.
6.6Compose pulls the model, injects two variables into printer, and runs the gateway with the API socket, which refuses a request without its bearer token.
7.1sbx move copies the sandbox filesystem as one image and nothing else, so secrets, mounts, local rules, and processes stay behind while the destination starts a TTL clock.
7.2One policy decision becomes one JSONL record on the developer machine, and only an enforced organization policy makes the daemon write it.
7.3Each old name survived its successor for months, and Docker Desktop removed the two of them one week apart, on 2026-06-29 and 2026-07-06.
7.4Eight claims were confirmed by a recorded file, five are reported without a test, and the community disputes one more.

Sources:the definition paragraphs of sections 2.1 to 7.2 as research/plan.md plans them; research/sources/help-sbx.md (sbx create claude, sbx template, sbx kit, sbx policy, sbx policy init, sbx policy allow network, sbx policy log, sbx secret, sbx secret set-custom, sbx mcp ls, sbx run, sbx template load, sbx ttl, sbx policy profile, sbx daemon status, sbx skills); research/sources/help-docker-agent.md (run, eval, serve); research/sources/agent-schema.json (AgentConfig, HooksConfig, HarnessConfig, Toolset); research/sources/SPEC-v3-at-v3.0.0-m.8.md §1, §3.4, §7; research/sources/docs-sandboxes.md (pages architecture, configuration/credentials, governance/audit); research/sources/docs-docker-agent.md (pages concepts/models, concepts/agents, configuration/flavors, configuration/permissions, features/sessions, features/cli); the figure blocks of front.md and every file under sections/

About this edition

Docker Sandboxes and Docker Agent 101, edition 2026.10, by Rohit Ghumare. It is part of AI Engineering from Scratch, an open source course at aiengineeringfromscratch.com. The newest edition is always at aiengineeringfromscratch.com/manual-docker-sandboxes-101.html.

Docker Sandboxes (sbx) and Docker Agent (docker-agent) sbx 0.47.0, docker-agent 1.149.0 is the subject, at commit bf4169cdd31229d52385410c52c3dcc59b497858 of 2026-10-07, checked on 2026-10-08. Every listing comes from a recorded run of the capture kit in the manual's source directory, and every quoted rule is checked word for word against the vendored sources.

© 2026 Rohit Ghumare · MIT license. You can copy and share this manual. Keep this page and the copyright line with it. Report an error at github.com/rohitg00/ai-engineering-from-scratch/issues.