docs: Updated docs to mention static file inclusion in sbx-mixins

2026-08-12 13:25:28 -06:00
parent f03d49ea65
commit c83bb80354
4 changed files with 72 additions and 7 deletions
+12 -1
@@ -895,9 +895,20 @@ Layout:
<coyote-config-dir>/agents/<my-agent>/
├── config.yaml
├── tools.sh
── sbx-mixin.yaml # gets auto-applied alongside the embedded base kit
── sbx-mixin.yaml # <- gets auto-applied alongside the embedded base kit
└── files/ # <- optional sibling: dotfiles/scripts/refs auto-mounted into the sandbox
└── home/
└── .config/my-tool/settings.json
workspace/
└── .editorconfig
```
The sibling `files/` directory is optional. If present, its contents are staged into the mixin's kit and injected into
the sandbox via [sbx's static-files convention](https://docs.docker.com/ai/sandboxes/customize/kits/#inject-files):
`files/home/*` lands under `/home/agent/`, `files/workspace/*` lands under the mounted workspace. See
[Sandboxes: Bundling static files alongside a mixin](Sandboxes#bundling-static-files-alongside-a-mixin) for the
caveats (symlinks rejected, tree contents hashed into the kit identity, exec bits preserved).
## Example
A custom agent that uses `httpie` to call an internal API:
+10 -3
@@ -38,7 +38,10 @@ is meaningful as a portable unit you'd share via [Sharing Configurations](Sharin
```
<coyote-config-dir>/functions/<your-tool-dir>/
├── tools.sh
── sbx-mixin.yaml
── sbx-mixin.yaml
└── files/ # optional: static resources auto-mounted into the sandbox
└── home/
└── .config/my-tool/rules.toml
```
**Global custom-tools mixin:** Covers all your custom tools at once. Use this when several tools share the same
@@ -48,7 +51,11 @@ binary/domain needs and you don't want to duplicate:
<coyote-config-dir>/functions/sbx-mixin.yaml
```
Both are auto-discovered by Coyote on every `coyote --sandbox`. No flags or registration required.
Both are auto-discovered by Coyote on every `coyote --sandbox`. No flags or registration required. If you drop a
sibling `files/` directory next to either mixin, its contents get staged into the mixin's kit and injected into the
sandbox via sbx's [static-files convention](https://docs.docker.com/ai/sandboxes/customize/kits/#inject-files). See
[Sandboxes: Bundling static files alongside a mixin](Sandboxes#bundling-static-files-alongside-a-mixin) for the
caveats.
### Example: a custom tool that uses `httpie`
@@ -396,4 +403,4 @@ export function run(name: string): string {
```
This is useful for pinning a specific Python version, using an alternative TypeScript runtime like
[Bun](https://bun.sh/) or [Deno](https://deno.com/), or working with virtual environments.
[Bun](https://bun.sh/) or [Deno](https://deno.com/), or working with virtual environments.
+39
@@ -354,6 +354,45 @@ Inside the sandbox, `INTERNAL_API_TOKEN` holds the literal string `proxy-managed
`Authorization: Bearer <token>` header added at the proxy. Use `header:` + `format: "%s"` instead of
`scheme: bearer` for non-standard headers (exactly one `%s` required, and `format`/`scheme` are mutually exclusive).
### Bundling static files alongside a mixin
For content that doesn't need runtime substitution (dotfiles, tool configs, helper scripts, cheatsheets, reference
material, etc.), drop it into a `files/` directory alongside the `sbx-mixin.yaml`. Coyote stages the whole tree into the
cache-backed kit it hands to `sbx`, and sbx applies its normal
[static-files convention](https://docs.docker.com/ai/sandboxes/customize/kits/#inject-files): `files/home/*` lands
under `/home/agent/`, `files/workspace/*` lands under the mounted workspace, and so on.
```text
<config-dir>/agents/researcher/
├── sbx-mixin.yaml
└── files/
├── home/
│ └── .config/my-tool/settings.json # → /home/agent/.config/my-tool/settings.json
└── workspace/
└── .editorconfig # → <workspace>/.editorconfig
```
The convention applies to **every** discovery path in the table above: the workspace mixin, per-tool mixins, RAG
sidecars, and the top-level user mixin can each ship a sibling `files/`. There's no per-source special-casing.
Use `files/` for static content; use the mixin's [`setup.files`](#what-a-mixin-can-declare) entries for content that
needs `${WORKDIR}` substitution or has to be generated at startup.
**Caveats:**
- **The `files/` directory must be a sibling of the `sbx-mixin.yaml`, not somewhere else in the config tree.** Coyote
doesn't recurse into arbitrary paths. A tree at `<config-dir>/files/...` with no adjacent mixin is ignored, and a
file (rather than a directory) named `files` next to the mixin is also ignored.
- **Symlinks inside the tree are rejected with an error** at sandbox-launch time. This is deliberate: a symlink
pointing at `/etc/passwd` in a shared bundle would silently exfiltrate host content into the sandbox. Copy the file
in for real, or reference it via a `setup.files` `content:` entry.
- **The tree's contents are hashed into the kit identity.** Editing any file under `files/` invalidates the cached kit
dir on the next `coyote --sandbox`, so iteration is safe. No stale content is served from cache.
- **Executable bits are preserved on Unix.** Scripts placed at e.g. `files/home/.local/bin/my-helper` land runnable
inside the sandbox.
- **UTF-8 paths only.** A file whose path Coyote can't render as UTF-8 (rare, but possible on some filesystems) is
rejected at wrap time so the kit hash stays deterministic across platforms.
### Built-in mixins Coyote ships
Coyote already ships mixins for the most common needs. They get auto-applied whenever they're relevant — you don't need to do anything to enable them:
+11 -3
@@ -367,10 +367,12 @@ unknown sources.
### Before installing a bundle from a source you don't fully trust
1. **Grep the repo for `sbx-mixin.yaml` files** and read each one:
1. **Grep the repo for `sbx-mixin.yaml` files** and read each one, then list any sibling `files/` trees they'll drop
into your sandbox:
```sh
git clone --depth 1 https://github.com/<source>/<repo> /tmp/audit
find /tmp/audit -name 'sbx-mixin.yaml' -exec cat {} +
find /tmp/audit -type d -name files -path '*/agents/*' -o -type d -name files -path '*/functions/*'
```
2. **Look specifically at**:
- `permissions.network.allow` (v1: `network.allowedDomains`): What domains are being added to the sandbox's allowlist?
@@ -378,6 +380,11 @@ unknown sources.
(apt repo, official installer, signed release) or arbitrary URLs?
- `environment.variables`: Do any look like credential exfiltration vectors (`*_API_URL` pointing at unknown
hosts, `LD_PRELOAD`, etc.)?
- **Sibling `files/` trees next to any `sbx-mixin.yaml`**: These are auto-mounted into the sandbox via sbx's
[static-files convention](https://docs.docker.com/ai/sandboxes/customize/kits/#inject-files). Look for anything
landing at `files/home/.config/*` (agent config overrides), `files/home/.local/bin/*` (executables on `$PATH`),
or `files/workspace/*` (files dropped into your project dir). See
[Sandboxes: Bundling static files alongside a mixin](Sandboxes#bundling-static-files-alongside-a-mixin).
3. **First-run with `--no-mixins` to verify the bundle's non-sandbox features work** without granting any sandbox
elevation:
```sh
@@ -395,5 +402,6 @@ off, abort with Ctrl-C (only safe before `sbx create` starts running install com
### When sharing your own bundle
If you publish a config repo, **document any `sbx-mixin.yaml` files in your `README.md`**. Explain what they add to
the sandbox and why. Users who land on your repo will trust you more if you make the privilege grants explicit
instead of letting them discover via grep.
the sandbox and why, and if any of them ship a sibling `files/` tree, spell out what lands where (e.g. "installs a
`~/.gitconfig` and a helper at `~/.local/bin/foo`"). Users who land on your repo will trust you more if you make the
privilege grants explicit instead of letting them discover via grep.