Give your agents reusable skills
- Intermediate
- 20 min
- Published
- Verified
What you'll learn
- When to choose between an agent skill and agent context for a piece of team knowledge
- Package a skill directory in a mixin kit
- Add always-available guidance
- Understand how an agent finds bundled skills
- Verify that an agent in a new sandbox can see and use the skill
Your team has a way of doing things. Changelog entries go under a particular heading, link a ticket, and read a certain way. Everyone learned it in their first code review. An agent in a new sandbox didn’t, so it writes the entry wrong. Then someone pastes the convention into the prompt, and does it again in the next sandbox.
You can write that convention down once, as an agent skill, and package it in a kit. Then every sandbox that includes the kit starts with the skill in the place its agent looks for skills. The skill doesn’t depend on any one agent or project, or on anyone remembering to paste it.
In this guide you will:
- write a small skill: a
SKILL.mdand one reference file; - package it in a mixin kit, and watch the build reject the kit while the skill is missing from the image;
- add a short piece of always-on guidance next to the skill;
- run the kit with Claude Code in a sandbox, check where the skill landed, and have the agent use it.
Prerequisites
sbx0.45 or later. Kits v3 need at least 0.45, and this guide was written against 0.47.0. If you’ve never run a sandbox, start with Run your first sandbox.- Docker with Buildx, to build and validate the kit.
- An Anthropic credential for Claude Code. Store an API key with
sbx secret set anthropic, or sign in with/loginthe first time Claude Code starts. See Working with secrets for how that credential stays on your host.
You don’t need to have written a kit before. Every file is given in full below. For a slower introduction to kit descriptors, see Build your first Docker Sandbox kit.
Skill or context?
A kit asks the sandbox for things through capabilities: typed entries in
its descriptor’s capabilities list, such as
com.docker.sandbox/agent-skill@1. The name says what’s being asked for, and
the @1 is the version of that capability’s contract, which is independent of
the kit format and the sbx release.
Kits can hand an agent knowledge through two of these, and choosing between them is the first decision:
| You want the agent to… | Use | What it is |
|---|---|---|
| Follow a procedure when a task calls for it | agent-skill@1 |
A directory with a SKILL.md and any supporting files |
| Always know a few facts about this environment | agent-context@1 |
A short Markdown body, indexed in the agent’s instruction file |
The example in this guide is a team’s changelog convention, and it uses both. Writing a changelog entry is a procedure the agent needs only when it’s editing the changelog, so the convention itself becomes a skill. A short note telling the agent that the convention exists, and that it should never invent a ticket ID, becomes context.
Starting state
Make a working directory with a changelog to try the skill on:
acme-demo/
├── CHANGELOG.md
└── acme-skills/ the kit
├── acme-skills.yaml descriptor
├── acme-skills.dockerfile puts the skill in the image
├── context.md always-on guidance
└── skills/
└── changelog-entry/ the skill
├── SKILL.md
└── references/
└── format.mdStart with acme-demo/CHANGELOG.md:
# Changelog
## UnreleasedWrite the skill
A skill is a directory. Its SKILL.md has YAML front matter that tells the
agent what the skill is for, and a body that tells the agent what to do.
Supporting files go in the same directory, and the body links to them by
relative path.
Create acme-skills/skills/changelog-entry/SKILL.md:
---
name: changelog-entry
description: Add an entry to CHANGELOG.md in Acme's format. Use when a change
is ready for review, or when asked to update the changelog.
---
# Add a changelog entry
1. Find the ticket ID for the change. Acme ticket IDs look like `ACME-123`.
If you can't find one, ask. Never invent an ID.
2. Open `CHANGELOG.md` and find the `## Unreleased` section.
3. Put the entry under the right subsection, creating the subsection if it's
missing. [The format reference](references/format.md) lists the
subsections and their order.
4. Write one line in the format the reference describes.
Show the line you added when you're done.Create acme-skills/skills/changelog-entry/references/format.md:
# Changelog format
Subsections, in this order: `### Added`, `### Changed`, `### Fixed`,
`### Security`.
Each entry is one line: an imperative sentence describing what the user
sees, then the ticket as a link.
- Stop retrying API requests after five attempts. ([ACME-412](https://tracker.acme.example.com/ACME-412))The name in the front matter matches the directory name, changelog-entry.
That matters later: the directory name is the name the sandbox gives the skill
in the agent’s skills directory.
Declare the skill in a kit
A kit is an image plus a descriptor that tells Docker Sandboxes what the image is for. This kit adds things to an agent’s environment rather than running anything itself, so it’s a mixin.
Create acme-skills/acme-skills.yaml:
# syntax=docker/sandbox-kit:3
schemaVersion: "3"
kind: mixin
displayName: Acme team skills
description: Acme's engineering conventions, packaged as agent skills.
version: "1.0.0"
capabilities:
- type: com.docker.sandbox/agent-skill@1
name: Changelog entry
config:
path: /usr/share/acme-skills/changelog-entry
- type: com.docker.sandbox/agent-context@1
config:
contentFile: ./context.mdTwo entries, one for each kind of knowledge:
agent-skill@1points at the skill’s directory inside the kit’s image.pathmust be an absolute path, and that directory must contain aSKILL.md. The entry’snameis only a label for display. The skill’s name comes from the last part of the path,changelog-entry.agent-context@1withcontentFileadds a body of guidance from the build context. The build copies it into the image, so the published kit doesn’t depend on your directory.
The path is under /usr/share/acme-skills/, not under an agent’s skills
directory such as ~/.claude/skills. That’s on purpose. The kit doesn’t know
which agent it will be used with, so it keeps the skill in a location named
for the kit, and Docker Sandboxes exposes it wherever the agent looks.
Now create acme-skills/context.md:
This sandbox includes Acme's team skills. Every user-facing change needs a
changelog entry: use the `changelog-entry` skill to write it. Acme ticket IDs
look like `ACME-123`. Never invent one; ask instead.Keep context short. Every agent in the sandbox can find it, every session, whether or not the task needs it. The procedure itself stays in the skill.
Build it, and watch it fail
The descriptor promises a skill at /usr/share/acme-skills/changelog-entry,
but nothing puts it there yet. Build anyway, from acme-demo/. The failure
is expected:
Host machine (acme-demo)
docker buildx build -f acme-skills/acme-skills.yaml -t acme-skills:dev acme-skills/
=> [internal] load build definition from acme-skills.yaml => => transferring dockerfile: 461B => resolve image config for docker-image://docker.io/docker/sandbox-kit:3 => [internal] load kit descriptor acme-skills.yaml · sandbox-kit 3.0.0-m.8 (129be2ff) => => transferring dockerfile: 461B => [internal] load guidance context.md => => transferring context: 245B => [kit] stage guidance /usr/share/sandbox/kit/acme-skills/context.md => [kit] stage sources /usr/share/sandbox/kit/acme-skills -------------------- 1 | >>> # syntax=docker/sandbox-kit:3 2 | schemaVersion: "3" 3 | kind: mixin --------------------
ERROR: failed to solve: conformance: - agent-skill-content (agent-skill@1/content-present): bundled skill "changelog-entry" does not carry /usr/share/acme-skills/changelog-entry/SKILL.md
The # syntax=docker/sandbox-kit:3 line sends the build to the kit frontend.
The frontend checks the image it’s about to produce against the descriptor
before it exports anything. A mixin with no recipe is a valid, declaration-only
kit, but this one declares a skill whose SKILL.md isn’t in the image. A kit
like that is refused at build time, so it can’t be published and then fail in
someone’s sandbox later.
The descriptor says what the kit provides. Only the image carries the files.
Put the skill in the image
Create acme-skills/acme-skills.dockerfile. The frontend finds it because it
shares the descriptor’s file name:
# syntax=docker/dockerfile:1
FROM scratch
COPY skills/changelog-entry /usr/share/acme-skills/changelog-entryFROM scratch starts from an empty image, so the mixin carries only the
skill. When the kit is used, these files are layered onto the workload’s
filesystem without bringing a base distribution along.
Build again:
Host machine (acme-demo)
docker buildx build -f acme-skills/acme-skills.yaml -t acme-skills:dev acme-skills/
[+] Building 3.1s (9/9) FINISHED => [internal] load build definition from acme-skills.yaml => => transferring dockerfile: 461B => resolve image config for docker-image://docker.io/docker/sandbox-kit:3 => [internal] load kit descriptor acme-skills.yaml · sandbox-kit 3.0.0-m.8 (129be2ff) => => transferring dockerfile: 461B => [internal] load guidance context.md => => transferring context: 245B => [internal] load .dockerignore => => transferring context: 2B => [internal] load build context => => transferring context: 1.24kB => [1/1] COPY skills/changelog-entry /usr/share/acme-skills/changelog-entry => [kit] stage guidance /usr/share/sandbox/kit/acme-skills/context.md => [kit] stage sources /usr/share/sandbox/kit/acme-skills => exporting to image => => exporting layers => => naming to docker.io/library/acme-skills:dev => => unpacking to docker.io/library/acme-skills:dev
The descriptor and the image now agree, and the kit is valid. You don’t need
this local image to run the kit. sbx builds a kit directory itself, as you’ll
see next. A successful buildx build tells you the kit passed validation.
Run the skill with an agent
A skill needs an agent to read it. Use two published Docker kits as the base:
the shell workload, and the Claude Code mixin on top of it. Then add your kit
from its directory. From acme-demo/:
Host machine (acme-demo)
sbx run docker/sbx-kit-shell:1.0.0 --name acme-skills-demo --kit docker/sbx-kit-claude-mixin:2.1.281 --kit ./acme-skills
agent@acme-skills-demo:~/acme-demo$
sbx builds ./acme-skills and adds the three kits together. Because the
workload is a shell, it opens a shell rather than starting an agent. That
gives you a chance to look around before you start Claude Code.
Find the skill
The Claude Code mixin declares /home/agent/.claude/skills as its skills
directory with agent-skills@1. Your skill should be there, under its
directory name:
Sandbox shell (acme-skills-demo)
ls -R /home/agent/.claude/skills/changelog-entry
/home/agent/.claude/skills/changelog-entry: SKILL.md references /home/agent/.claude/skills/changelog-entry/references: format.md
The whole directory is there, with the reference file at the same relative
path, so the link in SKILL.md still works.
Find the context
Every kit carries its own sources under /usr/share/sandbox/kit/<stem>/, where
<stem> is the descriptor’s file name without .yaml. Your context body
was copied there:
Sandbox shell (acme-skills-demo)
ls /usr/share/sandbox/kit/acme-skills/
context.md kit.dockerfile kit.yaml
grep -F "acme-skills/context.md" /home/agent/.claude/CLAUDE.md
- Acme team skills: /usr/share/sandbox/kit/acme-skills/context.md
kit.yaml is the descriptor as it was published, and kit.dockerfile is the
recipe that built the kit’s files. Claude Code reads
/home/agent/.claude/CLAUDE.md when it starts. Docker Sandboxes keeps a “Kits”
index in that file, with one entry for each kit that contributed guidance.
Each entry points at that kit’s body instead of copying it in. Adding more kits
makes the index longer, but the instruction file stays short.
Ask the agent
Start Claude Code and check that it can see the skill:
Sandbox shell (acme-skills-demo)
claude
Claude Code v2.1.281
Which skills can you use here?
I have one skill available: changelog-entry, which adds an entry to CHANGELOG.md in Acme's format.
We fixed the API client so it stops retrying after five attempts. That was ACME-412. Add a changelog entry.
I'll use the changelog-entry skill. Added to CHANGELOG.md under ## Unreleased > ### Fixed: - Stop retrying API requests after five attempts. ([ACME-412](https://tracker.acme.example.com/ACME-412))
Exit Claude Code with /exit, then look at the file itself. Your project
directory is the sandbox’s workspace, so the change also appears on your host:
Sandbox shell (acme-skills-demo)
cat CHANGELOG.md
# Changelog ## Unreleased ### Fixed - Stop retrying API requests after five attempts. ([ACME-412](https://tracker.acme.example.com/ACME-412))
The agent created the subsection, wrote an imperative one-liner, and linked the ticket. Nobody explained the convention in the prompt. Without the kit, the agent would have written the entry in its own style.
Exit the shell when you’re done. To clean up, run sbx rm acme-skills-demo
on the host.
Understand what happened
Your kit says where the skill is in the image. The agent kits say where their agents look for skills. Docker Sandboxes connects the two when it creates the sandbox:
Most of how skills behave in a kit follows from that split between source and destination.
The skill kit doesn’t name an agent. Destinations come from the kits that supply agents. Put the same kit with the Codex workload and the skill appears in Codex’s directory instead. Use both agents in one sandbox and it appears in both:
$ sbx run docker/sbx-kit-codex:0.155.1 --name acme-codex --kit ./acme-skillsA skill needs a destination. A sandbox with only the shell workload and
your kit has no agent, so no kit declares a skills directory. Your
agent-skill@1 entry is required by default, so Docker Sandboxes refuses to
create that sandbox instead of quietly leaving the skill out. If a kit is still
useful without its skills, mark the entry optional: true.
Names have to be unique. If something already exists at
changelog-entry in a destination, Docker Sandboxes won’t overwrite it. That
includes a skill a teammate shared from the host. The request can’t be
satisfied, and a required skill stops the sandbox from being created. Two kits
that use the same skill name with different source paths can’t be combined.
Give your skills names a teammate is unlikely to choose for something else. To
keep the source directory’s name but change the skill’s name, set
config.name on the entry.
Keep sources out of discovery directories. If your Dockerfile copied the
skill straight into /home/agent/.claude/skills, it would work for only one
agent. Docker Sandboxes manages the discovery directories itself, so sources
belong in a location named for your kit.
A skill grants nothing. agent-skill@1 and agent-context@1 don’t add
anything to the kit’s permission surface, which is the set of grants the user
reviews and approves. They’re files for the agent to read. Docker Sandboxes
never runs a skill’s scripts to register it, and running one is up to the
agent. If a skill needs to reach a service, the network rule and credential
come from a separate grant, which is reviewed as usual. Treat skill and
context text as you would any instructions you give an agent. A kit from a
publisher you don’t trust can give your agent bad instructions, even though
it can’t grant itself any access.
Bundled skills are separate from host skills. The agent’s skills
directory can also receive skills you share from your host. That’s controlled per
environment with the skills setting (off, readonly, or readwrite) in a
sandbox environment file.
Bundled skills appear even when host sharing is off, because the image, not
your host, supplies them. If you share your host’s skills, a kit can’t change
them unless both the kit and your settings allow write access.
Where to go next
- More skills, one kit. Add another directory under
skills/, anotherCOPY, and anotheragent-skill@1entry. Each entry ships one skill. - Pull in a public skill at build time. The skills-hub example installs a skill from a catalog in a build stage. Only the skill goes into the final image, and kit arguments choose which skill. To make your own kits configurable, see Make your Sandbox kits configurable.
- Ship a tool with its instructions. A skill often goes with a CLI. See Add tools and runtimes to a sandbox.
- Share it with your team. Push the kit to Docker Hub, and teammates can
use
moby/acme-skills:1.0.0in place of./acme-skills. See Publish and share your Docker Sandbox kits. - Reference. The
agent-skill@1,agent-skills@1, andagent-context@1capability pages define every field and rule. The review-skill example is a complete skill kit that setsconfig.name.
Part of a learning path
This works on its own and is also included in the paths below. Open a path to see the complete route.
Learning path
Build and customize Docker Sandbox environments
Step 3 of 6
Table of contents