Merge pull request #25339 from dvdksn/worktree-split-kits-reference

Split kit spec reference and update kit field renames
This commit is contained in:
David Karlsson
2026-06-15 10:07:07 +02:00
committed by GitHub
7 changed files with 406 additions and 373 deletions
@@ -40,12 +40,12 @@ dependencies — anything you'd rather not reinstall on every sandbox start.
A kit is a YAML artifact applied at sandbox creation. The kit can run
install commands, drop files into the sandbox, declare network and
credential rules, and (for agent kits) define which template image the
credential rules, and (for sandbox kits) define which template image the
agent runs in. Use kits for things that vary per agent or per team:
shared linter config, project-specific install steps, credential
injection for a service the agent talks to.
Templates and kits work together. An agent kit's `agent.image` field
Templates and kits work together. A sandbox kit's `sandbox.image` field
points at a template: the template provides the base environment, the
kit layers config, secrets, and runtime behavior on top. A team can ship
one heavy template and several thin kits without rebuilding the image
@@ -58,7 +58,7 @@ each time something changes.
| Pre-install tools and packages into a reusable base image | [Template](templates.md) |
| Capture a configured running sandbox for reuse | [Saved template](templates.md#saving-a-sandbox-as-a-template) |
| Add a tool, credential, or config to agent runs via YAML | [Kit (mixin)](kits.md) |
| Define a new agent from scratch | [Kit (agent)](kits.md#defining-an-agent) |
| Define a new agent from scratch | [Kit (sandbox)](kits.md#define-an-agent) |
Templates and kits can be used together. A template bakes heavy tools into
the image for fast sandbox startup; a kit layered on top adds per-run
@@ -67,4 +67,4 @@ credentials, config, or extra capabilities.
## Tutorials
- [Build your own agent kit](build-an-agent.md) — step-by-step walkthrough
for packaging [Amp](https://ampcode.com/) as an agent kit.
for packaging [Amp](https://ampcode.com/) as a sandbox kit.
@@ -18,8 +18,8 @@ This tutorial walks through building an agent kit for the
[Amp](https://ampcode.com/) coding agent. Each step explains the decision
behind a part of the spec, so you can apply the same reasoning to other agents.
For reference on every field, see the [Kits](kits.md) page. This tutorial
focuses on the journey.
For reference on every field, see the [Kit spec reference](kit-reference.md).
This tutorial focuses on the journey.
The finished kit is also published as a runnable sample at
[docker/sbx-kits-contrib](https://github.com/docker/sbx-kits-contrib/tree/main/amp) —
@@ -28,7 +28,7 @@ useful as a reference while you follow along.
## Choose a base image
An agent kit needs a container image that satisfies the
[base image requirements](kits.md#base-image-requirements): non-root
[base image requirements](kit-reference.md#base-image-requirements): non-root
`agent` user at UID 1000, passwordless sudo, `/home/agent/` home, and HTTP
proxy environment variable forwarding.
@@ -65,32 +65,28 @@ substitutes the real key on outbound requests to the API host, so the
secret never enters the sandbox. A later section walks through the
specific command for storing the key.
## Write the agent block
## Write the sandbox block
The `agent:` block tells the sandbox how to launch Amp when the user
The `sandbox:` block tells the sandbox how to launch Amp when the user
attaches.
```yaml {title="amp/spec.yaml"}
schemaVersion: "1"
kind: agent
kind: sandbox
name: amp
displayName: Amp
description: The frontier coding agent.
agent:
sandbox:
image: "docker/sandbox-templates:shell-docker"
aiFilename: AGENTS.md
persistence: persistent
entrypoint:
run: [amp, --dangerously-allow-all]
```
- `aiFilename: AGENTS.md` tells the sandbox to create `AGENTS.md` at launch
and append the [`memory`](#prime-amp-with-memory) block to it. Amp reads
and append the [`agentContext`](#prime-amp-with-memory) block to it. Amp reads
this file for instructions.
- `persistence: persistent` keeps Amp's state (auth tokens, history) in a
named volume across sandbox restarts. Without it, you re-authenticate
every time.
- `entrypoint.run` runs `amp` in "YOLO-mode" when the sandbox starts. Adjust if
you want to pass different args on startup.
@@ -157,12 +153,12 @@ pick any name.
## Prime Amp with memory
The `memory` field appends markdown to `AGENTS.md` at sandbox creation.
The `agentContext` field appends markdown to `AGENTS.md` at sandbox creation.
Use it to tell Amp about the sandbox environment so it knows the
conventions when it starts.
```yaml
memory: |
agentContext: |
## Sandbox environment
You are running inside a Docker sandbox. The workspace is mounted at
@@ -180,15 +176,14 @@ Putting it all together:
```yaml {title="amp/spec.yaml"}
schemaVersion: "1"
kind: agent
kind: sandbox
name: amp
displayName: Amp
description: The frontier coding agent.
agent:
sandbox:
image: "docker/sandbox-templates:shell-docker"
aiFilename: AGENTS.md
persistence: persistent
entrypoint:
run: [amp, --dangerously-allow-all]
@@ -209,7 +204,7 @@ commands:
user: "1000"
description: Install Amp
memory: |
agentContext: |
## Sandbox environment
You are running inside a Docker sandbox. The workspace is mounted at
@@ -287,7 +282,7 @@ Two loops help:
- Edit the spec and re-run `sbx run --kit ./amp/ amp` to pick up changes.
Remove the sandbox first (`sbx rm <name>`) for a clean start.
Flesh out the `memory` block as you refine how Amp should behave in the
Flesh out the `agentContext` block as you refine how Amp should behave in the
sandbox.
## Publish
@@ -316,7 +311,7 @@ the same decisions for your agent:
placeholder. If it accepts the env var as-is, declare
`environment.proxyManaged` in the kit and skip the user-side step.
The rest — memory block, network-policy iteration, packaging — is the
The rest — agent-context block, network-policy iteration, packaging — is the
same regardless of agent.
## Remove the stored secret
@@ -1,7 +1,7 @@
---
title: Kit examples
linkTitle: Examples
description: Copy-and-adapt spec.yaml snippets for common mixin and agent kit patterns — static files, install commands, background services, initFiles, Claude Code skills, and agent forks.
description: Copy-and-adapt spec.yaml snippets for common mixin and sandbox kit patterns — static files, install commands, background services, initFiles, Claude Code skills, and agent forks.
keywords: sandboxes, sbx, kits, mixins, examples, patterns, skills
weight: 25
---
@@ -17,7 +17,7 @@ weight: 25
Each section below shows one `spec.yaml` snippet that demonstrates a
single kit pattern. These aren't complete, distributable kits — they're
small, focused examples you can lift into your own kit. For the full
spec reference, see [Kits](kits.md).
spec reference, see [Kit spec reference](kit-reference.md).
## Drop a shared config file
@@ -253,7 +253,7 @@ idempotent. The heredoc pattern overwrites cleanly each time.
## Fork an existing agent
Agent kits (`kind: agent`) define a full agent from scratch. The most
Sandbox kits (`kind: sandbox`) define a full agent from scratch. The most
common variant is a fork of a built-in agent — same image and
credentials, but a different entrypoint. This example reproduces the
built-in `claude` agent but drops `--dangerously-skip-permissions` so
@@ -261,15 +261,14 @@ every tool call prompts for approval:
```yaml {title="claude-safe/spec.yaml"}
schemaVersion: "1"
kind: agent
kind: sandbox
name: claude-safe
displayName: Claude Code (with approval prompts)
description: Claude Code without --dangerously-skip-permissions
agent:
sandbox:
image: "docker/sandbox-templates:claude-code-docker"
aiFilename: CLAUDE.md
persistence: persistent
entrypoint:
run: [claude]
@@ -297,7 +296,7 @@ Launch with the kit's `name:` as the agent argument to `sbx run`:
$ sbx run claude-safe --kit ./claude-safe
```
For a step-by-step walkthrough of building a new agent kit from
For a step-by-step walkthrough of building a new sandbox kit from
scratch, see [Build an agent](build-an-agent.md).
## More examples
@@ -0,0 +1,348 @@
---
title: Kit spec reference
linkTitle: Spec reference
description: Field-by-field reference for a kit's spec.yaml — credentials, network rules, environment, commands, files, agent context, and the sandbox block.
keywords: sandboxes, sbx, kits, spec.yaml, reference, schema, fields
weight: 22
---
{{< summary-bar feature_name="Docker Sandboxes sbx" >}}
> [!NOTE]
> Kits are experimental. The kit file format, CLI commands, and experience
> for creating, loading, and managing kits are subject to change as the
> feature evolves. Share feedback and bug reports in the
> [docker/sbx-releases](https://github.com/docker/sbx-releases) repository.
This page documents every field in a kit's `spec.yaml`. For an overview of
what kits are and how to use them, see [Kits](kits.md).
A kit directory has a required `spec.yaml` and an optional `files/` tree:
```text
my-kit/
├── spec.yaml # required
└── files/ # optional — static files to inject
├── home/
└── workspace/
```
## Changelog
Renamed fields are still accepted for backward compatibility, but
`sbx kit validate` reports a deprecation warning for each, and a future
release may stop accepting them. Update kits to the current names.
### v0.32.0
The following `spec.yaml` fields were renamed:
| Previous | Current |
| -------------- | ---------------- |
| `memory` | `agentContext` |
| `kind: agent` | `kind: sandbox` |
| `agent:` block | `sandbox:` block |
The per-kit directory was also renamed from `kits-memory/` to
`kits-agent-context/`. An existing `kits-memory/` directory is migrated
automatically the next time the sandbox starts.
## Top-level fields
```yaml
schemaVersion: "1"
kind: <mixin | sandbox>
name: <name>
displayName: <name>
description: <text>
```
| Field | Required | Description |
| --------------- | -------- | -------------------------------------------------------------------------- |
| `schemaVersion` | Yes | Spec schema version. Set to `"1"`. |
| `kind` | Yes | `mixin` for kits that extend an agent; `sandbox` for kits that define one. |
| `name` | Yes | Unique identifier. Lowercase, alphanumeric, hyphens. |
| `displayName` | No | Human-readable name. |
| `description` | No | Short description. |
The sections below apply to both kinds. Sandbox kits also declare a
[`sandbox:` block](#sandbox-block).
## Credentials
```yaml
credentials:
sources:
<service-id>:
env: [<env-var>, ...]
file:
path: <path>
parser: <parser>
priority: <env-first | file-first>
```
| Field | Description |
| -------------------------- | ------------------------------------------------------------- |
| `sources` | Map of service identifier to credential source. |
| `sources.<id>.env` | Environment variables to read on the host, in priority order. |
| `sources.<id>.file.path` | Path on host. `~` expands to home directory. |
| `sources.<id>.file.parser` | How to extract the credential value from the file. |
| `sources.<id>.priority` | `env-first` (default) or `file-first`. |
Service identifiers link credentials to [network rules](#network).
### file.parser
`file.parser` tells the proxy how to extract a credential from the file at `file.path`.
Omit it for plain-text files; set it to `json:<dot.path>` to extract a field from a JSON file.
| Value | Behavior |
| ----------------- | ------------------------------------------------------------------------------------ |
| omitted or empty | Reads the entire file as the credential. Leading and trailing whitespace is trimmed. |
| `json:<dot.path>` | Parses the file as JSON and returns the value at the dot-separated path. |
| any other value | Rejected — `unsupported parser: <value>`. |
For `json:` paths, segments are separated by `.` (for example, `json:credentials.github.token`).
Only object keys can be navigated — arrays are not supported and there is no `[0]`-style indexing.
Keys that contain a literal `.` cannot be referenced. The resolved value must be a string, number,
or boolean; numbers and booleans are converted to strings. Objects, arrays, and null are rejected.
When a source has both `env` and `file` defined, `priority` controls which is tried first. The
preferred source is used when it exists — the environment variable is set, or the file is
present on disk. If it doesn't, the other source is used instead. The choice is made once at
discovery time, so parser errors (missing JSON field, wrong value type, invalid JSON) surface
as errors rather than triggering a fallback.
Plain-text token file:
```yaml
credentials:
sources:
openai:
file:
path: "~/.openai/token"
```
Nested JSON field, with an environment variable as fallback:
```yaml
credentials:
sources:
github:
env:
- GH_TOKEN
file:
path: "~/.config/myapp/creds.json"
parser: "json:credentials.github.token"
priority: file-first
```
Given `~/.config/myapp/creds.json`:
```json
{
"credentials": {
"github": { "token": "ghp_xyz", "expires": "2026-12-31" }
}
}
```
The proxy resolves the credential to `ghp_xyz`, falling back to `GH_TOKEN` if the file is
missing. If the file exists but the JSON path doesn't resolve, the request fails with the
parser error below instead of falling back.
Common errors when using `json:` parsers:
| Error message | Cause |
| --------------------------------------------- | ------------------------------------------------------------------- |
| `field 'X' not found in JSON` | The path doesn't exist in the file. |
| `cannot navigate to field 'X': not an object` | A path segment hit a string, array, or scalar instead of an object. |
| `field 'X' is not a string value` | The resolved value is an object, array, or null. |
| `failed to parse JSON: ...` | The file is not valid JSON. |
## Network
```yaml
network:
allowedDomains: [<domain>, ...]
deniedDomains: [<domain>, ...]
serviceDomains:
<domain>: <service-id>
serviceAuth:
<service-id>:
headerName: <header>
valueFormat: <format>
```
| Field | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `allowedDomains` | Domains the sandbox can reach. Wildcards supported. |
| `deniedDomains` | Domains the sandbox is blocked from reaching. Deny rules take precedence over allow rules, including those from other composed kits. |
| `serviceDomains` | Map of domain to service identifier from `credentials.sources`. |
| `serviceAuth.headerName` | HTTP header the proxy sets (for example, `Authorization`). |
| `serviceAuth.valueFormat` | Format string for the header value (for example, `"Bearer %s"`). |
## Environment
```yaml
environment:
variables:
<NAME>: <value>
proxyManaged: [<NAME>, ...]
```
| Field | Description |
| -------------- | ------------------------------------------------------------------------------------------------------------------- |
| `variables` | Key-value pairs set directly in the container. |
| `proxyManaged` | Environment variable names populated by the proxy at request time. Pair with [`credentials.sources`](#credentials). |
Variable names must be valid shell identifiers (`[A-Za-z_][A-Za-z0-9_]*`).
## Commands
```yaml
commands:
install:
- command: <shell-string>
user: <uid>
description: <text>
startup:
- command: [<argv>, ...]
user: <uid>
background: <true | false>
description: <text>
initFiles:
- path: <path>
content: <text>
mode: <octal>
onlyIfMissing: <true | false>
description: <text>
```
### install
Runs once during sandbox creation. Shell strings passed to `sh -c`.
| Field | Default | Description |
| ------------- | ------- | ----------------------------- |
| `command` | — | Shell command string. |
| `user` | `"0"` | User to run as. `"0"` = root. |
| `description` | — | Human-readable description. |
### startup
Runs at every sandbox start. String array, not interpreted by a shell.
| Field | Default | Description |
| ------------- | -------- | ----------------------------------- |
| `command` | — | Command and args as a string array. |
| `user` | `"1000"` | User to run as. `"1000"` = agent. |
| `background` | `false` | Run in background. |
| `description` | — | Human-readable description. |
Startup commands are non-interactive. They run before the agent
attaches, with no terminal connected, so they can't prompt the user
(for example, an interactive `aws login` will hang or fail). They also
don't gate the agent's entrypoint: the agent launches once startup
commands have been dispatched, regardless of `background`. Use them
for non-interactive prep — launching daemons, warming caches,
refreshing config — and use `commands.initFiles` for any value that
needs to land on disk before the agent runs.
Startup commands must be idempotent. They run on every sandbox start
and replay on container restarts, so a command that fails or
misbehaves on a second invocation breaks the restart path. Guard
work with existence checks, use upserts instead of inserts, and
prefer commands that converge to the same end state regardless of
how many times they run.
### initFiles
Files written at sandbox start, with runtime substitution.
| Field | Default | Description |
| --------------- | -------- | --------------------------------------------------------- |
| `path` | — | Absolute container path. |
| `content` | — | File content. `${WORKDIR}` expands to the workspace path. |
| `mode` | `"0644"` | File permissions in octal. |
| `onlyIfMissing` | `false` | Skip if the file already exists. |
## Static files
```text
my-kit/files/
├── home/ → /home/agent/
└── workspace/ → primary workspace path
```
| Kit path | Container destination |
| ------------------ | --------------------------------------- |
| `files/home/` | `/home/agent/` (config files, dotfiles) |
| `files/workspace/` | The primary workspace path |
Parent directories are created automatically. Existing files are
overwritten. Absolute paths and path-traversal sequences (`../../`) are
rejected.
## Agent context
```yaml
agentContext: |
<markdown>
```
Top-level field. Available in both mixin and sandbox kits. Markdown
appended to the agent's memory file at sandbox creation. The agent reads
this content at startup. Write it as instructions or notes the agent
should follow when working in the sandbox. Applied only when the active
sandbox kit sets [`sandbox.aiFilename`](#sandbox-block).
The file is written to the parent of the workspace path inside the
sandbox, not to the workspace itself. For a workspace mounted at
`/Users/you/myproject`, the memory file lands at
`/Users/you/AGENTS.md` (or whatever `aiFilename` is set to). It exists
only inside the sandbox. Nothing is written to the host.
When several loaded kits declare `agentContext:` blocks, the content is split
across files instead of being concatenated into the main one:
- Each kit's agent context is written to `<kit-name>.md` in a sibling
`kits-agent-context/` directory next to the main memory file.
- The main memory file gets a `## Kits` section listing every kit with
a pointer to its file. The section is delimited by
`<!-- sbx:kits-section start -->` and `<!-- sbx:kits-section end -->`
markers so it can be regenerated when kits are added or removed.
## Sandbox block
Required for `kind: sandbox`.
```yaml
sandbox:
image: <image-ref>
aiFilename: <filename>
entrypoint:
run: [<argv>, ...]
args: [<arg>, ...]
```
| Field | Required | Description |
| ------------------------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| `sandbox.image` | Yes | Docker image reference. See [Base image requirements](#base-image-requirements). |
| `sandbox.aiFilename` | No | Memory filename (for example, `AGENTS.md`). Appends top-level [`agentContext`](#agent-context) at creation. |
| `sandbox.entrypoint.run` | No | Command and args as a string array. Replaces the image's entrypoint. |
| `sandbox.entrypoint.args` | No | Args appended to the image's existing entrypoint. |
### Base image requirements
The agent's container image must provide:
- A non-root `agent` user at UID 1000 with passwordless sudo.
- A `/home/agent/` home directory owned by `agent`.
- HTTP proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`,
`NO_PROXY`) preserved across sudo.
- The agent binary (baked in, or installed via
[`commands.install`](#commands)).
Build on top of `docker/sandbox-templates:shell-docker` to get these for
free.
+28 -337
View File
@@ -29,11 +29,11 @@ and enforces them at runtime. Credentials stay on the host and go through
a proxy instead of entering the VM, and outbound traffic is restricted to
the domains permitted by the kit's network rules.
A kit is either a mixin or an agent:
A kit is either a mixin or a sandbox:
- Mixin kits (`kind: mixin`) extend an existing agent with extra
capabilities. Stack several on the same sandbox.
- Agent kits (`kind: agent`) define a full agent from scratch: its image,
- Sandbox kits (`kind: sandbox`) define a full agent from scratch: its image,
entrypoint, network policies, and everything else the agent needs.
## What kits can do
@@ -55,7 +55,7 @@ commands:
Startup commands cover things like launching background services,
warming caches, or refreshing config on each start. They must be
idempotent — see the [`startup`](#startup) spec reference:
idempotent — see the [`startup`](kit-reference.md#startup) spec reference:
```yaml
commands:
@@ -96,7 +96,7 @@ commands:
onlyIfMissing: true
```
See [`initFiles`](#initfiles) in the spec reference for all fields.
See [`initFiles`](kit-reference.md#initfiles) in the spec reference for all fields.
Sandboxes seed settings files for some built-in agents during setup.
For example, the sandbox writes `/home/agent/.claude/settings.json`
@@ -105,7 +105,7 @@ for the `claude` agent. This happens after the kit's static files and
Workspace files (such as `<workspace>/.claude/settings.local.json`)
aren't affected, and you can ship them under `files/workspace/` as
usual. To override a path the sandbox writes to, use a
[`commands.startup`](#startup) script instead. See
[`commands.startup`](kit-reference.md#startup) script instead. See
[Override agent settings](kit-examples.md#override-agent-settings) for
an example.
@@ -203,45 +203,45 @@ the agent project conventions, usage tips for a tool the kit installs,
or other guidance that should be in scope when the sandbox runs.
```yaml
memory: |
agentContext: |
Ruff is installed. Run `ruff check` before committing.
Shared config lives at `/workspace/ruff.toml`.
```
Both mixin and agent kits can declare `memory:`. The content is written
only when the active agent kit sets [`agent.aiFilename`](#agent-block),
Both mixin and sandbox kits can declare `agentContext:`. The content is written
only when the active sandbox kit sets [`sandbox.aiFilename`](kit-reference.md#sandbox-block),
which determines the memory file's name.
When more than one loaded kit declares a `memory:` block, each kit's
When more than one loaded kit declares an `agentContext:` block, each kit's
content is written to its own `<kit-name>.md` file under a sibling
`kits-memory/` directory. The main memory file gets a `## Kits` section
that points to each kit file:
`kits-agent-context/` directory. The main memory file gets a `## Kits`
section that points to each kit file:
```text
/Users/you/
├── myproject/ # workspace
├── AGENTS.md # main memory file with a "## Kits" index
└── kits-memory/
├── myproject/ # workspace
├── AGENTS.md # main memory file with a "## Kits" index
└── kits-agent-context/
├── ruff-lint.md
├── vale.md
└── git-ssh-sign.md
```
See [`memory`](#memory) in the spec reference for the full field schema.
See [`agentContext`](kit-reference.md#agent-context) in the spec reference for the full field schema.
### Define an agent
Agent kits declare an `agent:` block with the image the agent runs in and
Sandbox kits declare a `sandbox:` block with the image the agent runs in and
the command the user attaches to when they launch the sandbox:
```yaml
agent:
sandbox:
image: "my-registry/my-agent:latest"
entrypoint:
run: [my-agent, "--yolo"]
```
See [Agent kits](#agent-kits) for use cases and an example.
See [Sandbox kits](#sandbox-kits) for use cases and an example.
## Mixin kits
@@ -307,9 +307,9 @@ To apply the mixin to a sandbox that's already running, use
[`sbx kit add`](#local) instead. The `--kit` flag only takes effect when a
sandbox is created.
## Agent kits
## Sandbox kits
An agent kit defines a full agent from scratch — image, entrypoint, and
A sandbox kit defines a full agent from scratch — image, entrypoint, and
everything the agent needs. Common use cases:
- Package a custom agent you've built so others can run it
@@ -317,25 +317,24 @@ everything the agent needs. Common use cases:
- Run a fork of an existing agent with your own config
- Prototype a new agent integration
Agent kits declare everything a mixin kit can, plus an
[`agent:` block](#agent-block) that tells the sandbox how to launch the
Sandbox kits declare everything a mixin kit can, plus an
[`sandbox:` block](kit-reference.md#sandbox-block) that tells the sandbox how to launch the
agent. For a step-by-step walkthrough, see
[Build your own agent kit](build-an-agent.md).
### Example: the built-in `claude` agent
The `claude` agent you get from `sbx run claude` is defined as a kit. Here
is an abbreviated version of its spec, showing how the agent block combines
is an abbreviated version of its spec, showing how the sandbox block combines
with network, credentials, environment, and commands:
```yaml {title="claude/spec.yaml"}
schemaVersion: "1"
kind: agent
kind: sandbox
name: claude
agent:
sandbox:
image: "docker/sandbox-templates:claude-code-docker"
aiFilename: CLAUDE.md
persistence: persistent
entrypoint:
run: [claude, "--dangerously-skip-permissions"]
@@ -459,317 +458,9 @@ Docker credential store, so pushing to a private registry requires a prior
## Spec reference
A kit directory has a required `spec.yaml` and an optional `files/` tree:
```text
my-kit/
├── spec.yaml # required
└── files/ # optional — static files to inject
├── home/
└── workspace/
```
### Top-level fields
```yaml
schemaVersion: "1"
kind: <mixin | agent>
name: <name>
displayName: <name>
description: <text>
```
| Field | Required | Description |
| --------------- | -------- | ------------------------------------------------------------------------ |
| `schemaVersion` | Yes | Spec schema version. Set to `"1"`. |
| `kind` | Yes | `mixin` for kits that extend an agent; `agent` for kits that define one. |
| `name` | Yes | Unique identifier. Lowercase, alphanumeric, hyphens. |
| `displayName` | No | Human-readable name. |
| `description` | No | Short description. |
The sections below apply to both kinds. Agent kits also declare an
[`agent:` block](#agent-block).
### Credentials
```yaml
credentials:
sources:
<service-id>:
env: [<env-var>, ...]
file:
path: <path>
parser: <parser>
priority: <env-first | file-first>
```
| Field | Description |
| -------------------------- | ------------------------------------------------------------- |
| `sources` | Map of service identifier to credential source. |
| `sources.<id>.env` | Environment variables to read on the host, in priority order. |
| `sources.<id>.file.path` | Path on host. `~` expands to home directory. |
| `sources.<id>.file.parser` | How to extract the credential value from the file. |
| `sources.<id>.priority` | `env-first` (default) or `file-first`. |
Service identifiers link credentials to [network rules](#network).
#### file.parser
`file.parser` tells the proxy how to extract a credential from the file at `file.path`.
Omit it for plain-text files; set it to `json:<dot.path>` to extract a field from a JSON file.
| Value | Behavior |
| ----------------- | ------------------------------------------------------------------------------------ |
| omitted or empty | Reads the entire file as the credential. Leading and trailing whitespace is trimmed. |
| `json:<dot.path>` | Parses the file as JSON and returns the value at the dot-separated path. |
| any other value | Rejected — `unsupported parser: <value>`. |
For `json:` paths, segments are separated by `.` (for example, `json:credentials.github.token`).
Only object keys can be navigated — arrays are not supported and there is no `[0]`-style indexing.
Keys that contain a literal `.` cannot be referenced. The resolved value must be a string, number,
or boolean; numbers and booleans are converted to strings. Objects, arrays, and null are rejected.
When a source has both `env` and `file` defined, `priority` controls which is tried first. The
preferred source is used when it exists — the environment variable is set, or the file is
present on disk. If it doesn't, the other source is used instead. The choice is made once at
discovery time, so parser errors (missing JSON field, wrong value type, invalid JSON) surface
as errors rather than triggering a fallback.
Plain-text token file:
```yaml
credentials:
sources:
openai:
file:
path: "~/.openai/token"
```
Nested JSON field, with an environment variable as fallback:
```yaml
credentials:
sources:
github:
env:
- GH_TOKEN
file:
path: "~/.config/myapp/creds.json"
parser: "json:credentials.github.token"
priority: file-first
```
Given `~/.config/myapp/creds.json`:
```json
{
"credentials": {
"github": { "token": "ghp_xyz", "expires": "2026-12-31" }
}
}
```
The proxy resolves the credential to `ghp_xyz`, falling back to `GH_TOKEN` if the file is
missing. If the file exists but the JSON path doesn't resolve, the request fails with the
parser error below instead of falling back.
Common errors when using `json:` parsers:
| Error message | Cause |
| --------------------------------------------- | ------------------------------------------------------------------- |
| `field 'X' not found in JSON` | The path doesn't exist in the file. |
| `cannot navigate to field 'X': not an object` | A path segment hit a string, array, or scalar instead of an object. |
| `field 'X' is not a string value` | The resolved value is an object, array, or null. |
| `failed to parse JSON: ...` | The file is not valid JSON. |
### Network
```yaml
network:
allowedDomains: [<domain>, ...]
deniedDomains: [<domain>, ...]
serviceDomains:
<domain>: <service-id>
serviceAuth:
<service-id>:
headerName: <header>
valueFormat: <format>
```
| Field | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `allowedDomains` | Domains the sandbox can reach. Wildcards supported. |
| `deniedDomains` | Domains the sandbox is blocked from reaching. Deny rules take precedence over allow rules, including those from other composed kits. |
| `serviceDomains` | Map of domain to service identifier from `credentials.sources`. |
| `serviceAuth.headerName` | HTTP header the proxy sets (for example, `Authorization`). |
| `serviceAuth.valueFormat` | Format string for the header value (for example, `"Bearer %s"`). |
### Environment
```yaml
environment:
variables:
<NAME>: <value>
proxyManaged: [<NAME>, ...]
```
| Field | Description |
| -------------- | ------------------------------------------------------------------------------------------------------------------- |
| `variables` | Key-value pairs set directly in the container. |
| `proxyManaged` | Environment variable names populated by the proxy at request time. Pair with [`credentials.sources`](#credentials). |
Variable names must be valid shell identifiers (`[A-Za-z_][A-Za-z0-9_]*`).
### Commands
```yaml
commands:
install:
- command: <shell-string>
user: <uid>
description: <text>
startup:
- command: [<argv>, ...]
user: <uid>
background: <true | false>
description: <text>
initFiles:
- path: <path>
content: <text>
mode: <octal>
onlyIfMissing: <true | false>
description: <text>
```
#### `install`
Runs once during sandbox creation. Shell strings passed to `sh -c`.
| Field | Default | Description |
| ------------- | ------- | ----------------------------- |
| `command` | — | Shell command string. |
| `user` | `"0"` | User to run as. `"0"` = root. |
| `description` | — | Human-readable description. |
#### `startup`
Runs at every sandbox start. String array, not interpreted by a shell.
| Field | Default | Description |
| ------------- | -------- | ----------------------------------- |
| `command` | — | Command and args as a string array. |
| `user` | `"1000"` | User to run as. `"1000"` = agent. |
| `background` | `false` | Run in background. |
| `description` | — | Human-readable description. |
Startup commands are non-interactive. They run before the agent
attaches, with no terminal connected, so they can't prompt the user
(for example, an interactive `aws login` will hang or fail). They also
don't gate the agent's entrypoint: the agent launches once startup
commands have been dispatched, regardless of `background`. Use them
for non-interactive prep — launching daemons, warming caches,
refreshing config — and use `commands.initFiles` for any value that
needs to land on disk before the agent runs.
Startup commands must be idempotent. They run on every sandbox start
and replay on container restarts, so a command that fails or
misbehaves on a second invocation breaks the restart path. Guard
work with existence checks, use upserts instead of inserts, and
prefer commands that converge to the same end state regardless of
how many times they run.
#### `initFiles`
Files written at sandbox start, with runtime substitution.
| Field | Default | Description |
| --------------- | -------- | --------------------------------------------------------- |
| `path` | — | Absolute container path. |
| `content` | — | File content. `${WORKDIR}` expands to the workspace path. |
| `mode` | `"0644"` | File permissions in octal. |
| `onlyIfMissing` | `false` | Skip if the file already exists. |
### Static files
```text
my-kit/files/
├── home/ → /home/agent/
└── workspace/ → primary workspace path
```
| Kit path | Container destination |
| ------------------ | --------------------------------------- |
| `files/home/` | `/home/agent/` (config files, dotfiles) |
| `files/workspace/` | The primary workspace path |
Parent directories are created automatically. Existing files are
overwritten. Absolute paths and path-traversal sequences (`../../`) are
rejected.
### Memory
```yaml
memory: |
<markdown>
```
Top-level field. Available in both mixin and agent kits. Markdown
appended to the agent's memory file at sandbox creation. The agent reads
this content at startup. Write it as instructions or notes the agent
should follow when working in the sandbox. Applied only when the active
agent kit sets [`agent.aiFilename`](#agent-block).
The file is written to the parent of the workspace path inside the
sandbox, not to the workspace itself. For a workspace mounted at
`/Users/you/myproject`, the memory file lands at
`/Users/you/AGENTS.md` (or whatever `aiFilename` is set to). It exists
only inside the sandbox. Nothing is written to the host.
When several loaded kits declare `memory:` blocks, the content is split
across files instead of being concatenated into the main one:
- Each kit's memory is written to `<kit-name>.md` in a sibling
`kits-memory/` directory next to the main memory file.
- The main memory file gets a `## Kits` section listing every kit with
a pointer to its file. The section is delimited by
`<!-- sbx:kits-section start -->` and `<!-- sbx:kits-section end -->`
markers so it can be regenerated when kits are added or removed.
### Agent block
Required for `kind: agent`.
```yaml
agent:
image: <image-ref>
aiFilename: <filename>
persistence: <persistent | ephemeral>
entrypoint:
run: [<argv>, ...]
args: [<arg>, ...]
```
| Field | Required | Description |
| ----------------------- | -------- | ---------------------------------------------------------------------------------------------- |
| `agent.image` | Yes | Docker image reference. See [Base image requirements](#base-image-requirements). |
| `agent.aiFilename` | No | Memory filename (for example, `AGENTS.md`). Appends top-level [`memory`](#memory) at creation. |
| `agent.persistence` | No | `persistent` (named volume across restarts) or `ephemeral` (default). |
| `agent.entrypoint.run` | No | Command and args as a string array. Replaces the image's entrypoint. |
| `agent.entrypoint.args` | No | Args appended to the image's existing entrypoint. |
#### Base image requirements
The agent's container image must provide:
- A non-root `agent` user at UID 1000 with passwordless sudo.
- A `/home/agent/` home directory owned by `agent`.
- HTTP proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`,
`NO_PROXY`) preserved across sudo.
- The agent binary (baked in, or installed via
[`commands.install`](#commands)).
Build on top of `docker/sandbox-templates:shell-docker` to get these for
free.
For a field-by-field reference of every `spec.yaml` block — top-level
fields, credentials, network, environment, commands, static files,
agent context, and the sandbox block — see [Kit spec reference](kit-reference.md).
## Debugging
@@ -29,7 +29,7 @@ ask the agent to install what's needed.
> create new agent runtimes. The agent that launches inside the sandbox is
> determined by the base image variant you extend and the agent you specify
> in the `sbx run` command, not by binaries installed in the template. To
> define a new agent from scratch, see [Kits](kits.md#defining-an-agent).
> define a new agent from scratch, see [Kits](kits.md#define-an-agent).
### Base images
+4 -4
View File
@@ -111,22 +111,22 @@ startup. In Claude Code, use the `/permissions` command to change the mode
interactively.
To make approval prompts the default for every session, define a custom
agent kit that overrides the agent's entrypoint to drop the
sandbox kit that overrides the agent's entrypoint to drop the
permission-skipping flag. For example, a kit that launches Claude Code
without `--dangerously-skip-permissions`:
```yaml {title="claude-safe/spec.yaml"}
schemaVersion: "1"
kind: agent
kind: sandbox
name: claude-safe
agent:
sandbox:
image: "docker/sandbox-templates:claude-code-docker"
entrypoint:
run: [claude]
```
Run it with `sbx run claude-safe --kit ./claude-safe/`. See
[Agent kits](customize/kits.md#agent-kits) for the full pattern.
[Sandbox kits](customize/kits.md#sandbox-kits) for the full pattern.
## How do I know if my agent is running in a sandbox?