diff --git a/content/manuals/ai/sandboxes/customize/_index.md b/content/manuals/ai/sandboxes/customize/_index.md index 5d1b7a30d82..d5df8e2365a 100644 --- a/content/manuals/ai/sandboxes/customize/_index.md +++ b/content/manuals/ai/sandboxes/customize/_index.md @@ -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. diff --git a/content/manuals/ai/sandboxes/customize/build-an-agent.md b/content/manuals/ai/sandboxes/customize/build-an-agent.md index 237e25e209e..98aec4206f0 100644 --- a/content/manuals/ai/sandboxes/customize/build-an-agent.md +++ b/content/manuals/ai/sandboxes/customize/build-an-agent.md @@ -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 `) 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 diff --git a/content/manuals/ai/sandboxes/customize/kit-examples.md b/content/manuals/ai/sandboxes/customize/kit-examples.md index 9bf728e64e3..2d67dd7bf81 100644 --- a/content/manuals/ai/sandboxes/customize/kit-examples.md +++ b/content/manuals/ai/sandboxes/customize/kit-examples.md @@ -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 diff --git a/content/manuals/ai/sandboxes/customize/kit-reference.md b/content/manuals/ai/sandboxes/customize/kit-reference.md new file mode 100644 index 00000000000..fd8d6cde247 --- /dev/null +++ b/content/manuals/ai/sandboxes/customize/kit-reference.md @@ -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: +name: +displayName: +description: +``` + +| 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: + : + env: [, ...] + file: + path: + parser: + priority: +``` + +| Field | Description | +| -------------------------- | ------------------------------------------------------------- | +| `sources` | Map of service identifier to credential source. | +| `sources..env` | Environment variables to read on the host, in priority order. | +| `sources..file.path` | Path on host. `~` expands to home directory. | +| `sources..file.parser` | How to extract the credential value from the file. | +| `sources..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:` 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:` | Parses the file as JSON and returns the value at the dot-separated path. | +| any other value | Rejected — `unsupported parser: `. | + +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: [, ...] + deniedDomains: [, ...] + serviceDomains: + : + serviceAuth: + : + headerName:
+ valueFormat: +``` + +| 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: + : + proxyManaged: [, ...] +``` + +| 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: + user: + description: + startup: + - command: [, ...] + user: + background: + description: + initFiles: + - path: + content: + mode: + onlyIfMissing: + description: +``` + +### 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: | + +``` + +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 `.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 + `` and `` + markers so it can be regenerated when kits are added or removed. + +## Sandbox block + +Required for `kind: sandbox`. + +```yaml +sandbox: + image: + aiFilename: + entrypoint: + run: [, ...] + args: [, ...] +``` + +| 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. diff --git a/content/manuals/ai/sandboxes/customize/kits.md b/content/manuals/ai/sandboxes/customize/kits.md index 59b0cec6cab..c19552ed647 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -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 `/.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 `.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: -name: -displayName: -description: -``` - -| 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: - : - env: [, ...] - file: - path: - parser: - priority: -``` - -| Field | Description | -| -------------------------- | ------------------------------------------------------------- | -| `sources` | Map of service identifier to credential source. | -| `sources..env` | Environment variables to read on the host, in priority order. | -| `sources..file.path` | Path on host. `~` expands to home directory. | -| `sources..file.parser` | How to extract the credential value from the file. | -| `sources..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:` 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:` | Parses the file as JSON and returns the value at the dot-separated path. | -| any other value | Rejected — `unsupported parser: `. | - -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: [, ...] - deniedDomains: [, ...] - serviceDomains: - : - serviceAuth: - : - headerName:
- valueFormat: -``` - -| 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: - : - proxyManaged: [, ...] -``` - -| 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: - user: - description: - startup: - - command: [, ...] - user: - background: - description: - initFiles: - - path: - content: - mode: - onlyIfMissing: - description: -``` - -#### `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: | - -``` - -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 `.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 - `` and `` - markers so it can be regenerated when kits are added or removed. - -### Agent block - -Required for `kind: agent`. - -```yaml -agent: - image: - aiFilename: - persistence: - entrypoint: - run: [, ...] - args: [, ...] -``` - -| 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 diff --git a/content/manuals/ai/sandboxes/customize/templates.md b/content/manuals/ai/sandboxes/customize/templates.md index da4410bb740..5e5f1f21e53 100644 --- a/content/manuals/ai/sandboxes/customize/templates.md +++ b/content/manuals/ai/sandboxes/customize/templates.md @@ -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 diff --git a/content/manuals/ai/sandboxes/faq.md b/content/manuals/ai/sandboxes/faq.md index 79a2c94652d..4f83d2ce515 100644 --- a/content/manuals/ai/sandboxes/faq.md +++ b/content/manuals/ai/sandboxes/faq.md @@ -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?