Skip to content

Commit 0249c6d

Browse files
ZhiXiao-LinRoy Lin
andauthored
docs(skills): ship the a3s-box agent skill + cross-tool installer (#157)
One Agent Skills SKILL.md that teaches a coding agent to drive the a3s-box CLI, reused across Claude Code / Codex / Gemini CLI / Cursor / Amp / OpenCode / Zed / a3s-code (shared SKILL.md format). Adds integrations/skills/ (SKILL.md, install.sh, README) and a complete Install+Use section in the box README. Docs-only. Co-authored-by: Roy Lin <roylin@a3s.box>
1 parent 2f9a316 commit 0249c6d

4 files changed

Lines changed: 307 additions & 0 deletions

File tree

README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,65 @@ a3s-box unseal dev --context app/key
343343

344344
TEE features include SNP report parsing/verification, RA-TLS certificate extensions, AES-256-GCM sealing with HKDF-SHA256, and RA-TLS secret injection. Treat simulation as a developer workflow only; it does not prove hardware isolation. TDX is not productized.
345345

346+
## Coding-agent skill
347+
348+
`integrations/skills/a3s-box/SKILL.md` is an [Agent Skills](https://agentskills.io)
349+
`SKILL.md` that teaches an AI coding agent to drive this CLI — the `--` separator,
350+
the box lifecycle, snapshots, the warm pool, the networking footguns, and an
351+
errors→fix table for recovery. It is **one file** in the cross-tool Agent Skills
352+
format, so the same skill works in Claude Code, OpenAI Codex, Gemini CLI, Cursor,
353+
Sourcegraph Amp, OpenCode, Zed, and a3s-code — no per-agent variant.
354+
355+
### Install
356+
357+
The installer symlinks the single `SKILL.md` into each agent's skills directory
358+
(one source of truth):
359+
360+
```bash
361+
cd integrations/skills
362+
363+
./install.sh all # this project: .agents + .claude + .codex + .a3s
364+
./install.sh --home agents claude # user-wide: ~/.agents + ~/.claude
365+
./install.sh --dir ./agent/skills # any SKILL.md-format skills dir
366+
./install.sh --copy all # copy instead of symlink
367+
```
368+
369+
Two roots reach almost every skill-capable agent; install the targets you use:
370+
371+
| Target | Skills root | Reached by |
372+
|--------|-------------|------------|
373+
| `agents` | `.agents/skills/` | OpenAI Codex · Gemini CLI · Amp · Cursor · OpenCode · Zed |
374+
| `claude` | `.claude/skills/` | Claude Code · Claude Agent SDK · Cline · Cursor/OpenCode (compat) |
375+
| `codex` | `.codex/skills/` | Codex (project-specific path) |
376+
| `a3s-code` | `.a3s/skills/` | a3s-code |
377+
| `all` | all of the above | |
378+
379+
Manual equivalent (no installer):
380+
`ln -s "$(pwd)/a3s-box/SKILL.md" <root>/a3s-box/SKILL.md`.
381+
382+
### Use
383+
384+
Reload the agent so it rescans its skills directory. The skill then:
385+
386+
- **surfaces as the `/a3s-box` slash command** (the directory name is the command), and
387+
- **is auto-invoked** when you ask the agent to run, build, exec into, snapshot, or
388+
sandbox something with a3s-box — its `description` is matched against your request.
389+
390+
The agent reads the skill body and drives `a3s-box` for you — e.g. *"build this repo
391+
and run it in a sandbox"*`a3s-box build``a3s-box run -d` → verify it's up. The
392+
skill restricts itself to `Bash(a3s-box*)`, so it can only invoke this CLI.
393+
394+
### Agents without a skill mechanism
395+
396+
GitHub Copilot, Windsurf/Devin, Continue.dev, Aider, and Jules/Factory have only
397+
always-on instruction files (no on-demand skills). To make one aware of a3s-box, add
398+
a one-line pointer to `integrations/skills/a3s-box/SKILL.md` in that tool's
399+
instructions file, or in a repo-root **`AGENTS.md`** (which most of them read).
400+
401+
See [`integrations/skills/README.md`](integrations/skills/README.md) for the full
402+
agent matrix, the no-skill-agent details, and why this is a skill rather than a
403+
Claude Code plugin.
404+
346405
## Kubernetes CRI
347406

348407
The CRI server is reachable by standard gRPC clients — `crictl`, the kubelet, and `critest` — over its Unix domain socket, and runs the core pod + container lifecycle and `exec` end to end. It is Linux-only and not yet fully `critest`-conformant.

integrations/skills/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# a3s-box agent skill
2+
3+
One `SKILL.md` that teaches an AI coding agent to drive the `a3s-box` CLI. It
4+
uses the cross-tool **Agent Skills** format (`<name>/SKILL.md`), so the *same
5+
file* works in every agent that supports skills — there is no per-agent variant.
6+
7+
## Install
8+
9+
```sh
10+
./install.sh all # .agents + .claude + .codex + .a3s, this repo
11+
./install.sh --home agents claude # user-wide (~/.agents, ~/.claude)
12+
./install.sh --dir ./agent/skills # any SKILL.md-format skills dir
13+
./install.sh --copy all # copy instead of symlink
14+
```
15+
16+
The installer symlinks the one `SKILL.md` into each skills root (single source
17+
of truth). Manual equivalent: `ln -s "$(pwd)/a3s-box/SKILL.md" <root>/a3s-box/SKILL.md`.
18+
19+
## Which agents this reaches
20+
21+
Two skills roots cover almost every skill-capable coding agent (2026):
22+
23+
| Skills root | Reached by |
24+
|-------------|-----------|
25+
| `.agents/skills/` | OpenAI Codex · Gemini CLI · Sourcegraph Amp · Cursor · OpenCode · Zed |
26+
| `.claude/skills/` | Claude Code · Claude Agent SDK · Cline · Cursor (compat) · OpenCode (compat) · the a3s CLI menus |
27+
| `.codex/skills/` | Codex (project-specific path) |
28+
| `.a3s/skills/` | a3s-code |
29+
30+
`--home` writes the `~/...` equivalents (`~/.agents/skills`, `~/.claude/skills`,
31+
…). Reload the agent to pick up the skill. The skill directory name (`a3s-box`)
32+
becomes the `/a3s-box` slash command.
33+
34+
## Agents without a skill mechanism
35+
36+
Some agents have no on-demand skills, only always-on instruction files:
37+
**GitHub Copilot** (`.github/copilot-instructions.md`), **Windsurf / Devin**
38+
(`.devin/rules/`), **Continue.dev** (`.continue/rules/`), **Aider**
39+
(`CONVENTIONS.md`), **Jules / Factory** (`AGENTS.md`).
40+
41+
We deliberately do *not* ship a copy in each bespoke rules format. If you want
42+
one of these to know about a3s-box, add a one-line pointer to `a3s-box/SKILL.md`
43+
in that tool's instructions file. Most of them also read the cross-tool
44+
**`AGENTS.md`** at the repo root, so a single `AGENTS.md` line reaches the
45+
majority. (Claude Code is the exception — it reads `CLAUDE.md`, not `AGENTS.md`.)
46+
47+
`claude.ai` and the Claude API do not read the filesystem — upload the `a3s-box/`
48+
folder as a skill ZIP / via the Skills API.
49+
50+
## Why a skill, not a plugin
51+
52+
A Claude Code *plugin* would only help Claude Code. A shared `SKILL.md` is the
53+
single format the whole ecosystem discovers, so one file covers everything.
54+
55+
Note: the a3s-code loader caps skill bodies at 10 KiB and is fail-secure on
56+
`allowed-tools` (omitting it denies all tool use), so the `SKILL.md` is kept
57+
tight and declares `Bash(a3s-box*)`.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
---
2+
name: a3s-box
3+
description: Drive the a3s-box microVM sandbox CLI — a Docker-like runtime that runs OCI/container images in hardware-isolated microVMs (libkrun). Use when the user wants to run, build, exec into, snapshot, or tear down containers with a3s-box, sandbox untrusted or agent-generated code, spin up throwaway isolated environments, or mentions a3s-box / microVM / libkrun / "run this safely in a sandbox". Teaches the box lifecycle, the `--` argv separator, the networking model, and error recovery. Not for plain Docker/Podman (different CLI, same verbs).
4+
allowed-tools: Bash(a3s-box*), Bash(curl*), Read(*), Grep(*)
5+
---
6+
7+
# a3s-box — drive the microVM sandbox
8+
9+
`a3s-box` runs OCI images inside hardware-isolated microVMs with a Docker-like
10+
CLI (verbs mirror Docker). **Preflight once:** `a3s-box info` (confirms
11+
virtualization + home dir).
12+
13+
## Mental model (these cause silent failures)
14+
15+
- **Lifecycle:** `created → running → paused → stopped → dead`.
16+
- `run` = create **and** start. `create` stops at `created``start` it.
17+
`snapshot restore``created``start` it.
18+
- A box dies when its **PID 1 exits** — it needs a foreground process.
19+
`run -d alpine` runs `sh`, which exits at once → `dead`. Use a long-lived
20+
command: `run -d nginx`, or `run -d alpine -- sleep 3600`.
21+
- `exec`/`shell`/`cp`/`top`/`attach` need a **running** box. `start` accepts
22+
only `created|stopped|dead`. `rm` removes `created`/`stopped`/`dead` without
23+
`-f`; `-f` is only for a `running`/`paused` box.
24+
- **The `--` rule:** the in-box command goes **after `--`**.
25+
`a3s-box exec <box> -- <cmd>` (required); `a3s-box run <image> [-- <cmd>]`
26+
(optional override). Missing it → `error: unexpected argument '…' found`.
27+
- **`exec` has a 5-second default timeout** — long builds/installs/tests get
28+
killed mid-run (and look like a failure). Use `--timeout 300` (or `--timeout 0`
29+
to disable): `a3s-box exec --timeout 300 web -- <cmd>`.
30+
- **Detached boxes need the monitor.** `run -d` prints the id and exits; its
31+
health/restart task dies with it. For `--restart` policies and health checks to
32+
fire, run `a3s-box monitor` in the background first.
33+
- **No in-guest localhost by default.** A box can't reach its own service on
34+
`127.0.0.1` (so `--health-cmd 'curl localhost'` and in-box curl fail). To make
35+
in-guest localhost work, attach a bridge network (recipe below). To check a
36+
service from your side, curl the **published HOST port** (see Ports).
37+
- **Ports:** publish with `-p HOST:GUEST` (TCP). But `ps`'s PORTS column and
38+
`a3s-box port <box>` render `GUEST -> 0.0.0.0:HOST` — the host port to curl is
39+
the one after `0.0.0.0:`. Read it with `a3s-box port <box>`.
40+
- **Output streams:** `run -d` prints a human `Creating box <name> (<id>)...`
41+
line, the full box id, and (when uncached) image-pull progress — all to
42+
**stdout**; only tracing/WARN/ERROR go to **stderr**. Don't parse the id from
43+
stdout — reference boxes by `--name`. JSON where offered: `inspect`,
44+
`image-inspect`, `snapshot ls --json`, and `ps --format json` (2.6+ only; on
45+
2.0 use `ps --format '{{.ID}} {{.Status}} {{.Names}}'`).
46+
47+
## Run → verify → exec → teardown
48+
49+
```sh
50+
a3s-box info
51+
a3s-box run -d --name web -p 8080:80 app:dev # reference by --name, not stdout
52+
a3s-box ps -a --filter name=web # MUST use -a; expect STATUS=running
53+
a3s-box port web # host port = value after 0.0.0.0:
54+
curl -fsS http://localhost:8080/ # confirm SERVING from the host
55+
a3s-box exec --timeout 60 web -- env # in-box command after --
56+
a3s-box logs web # logs print on stderr
57+
a3s-box stop web && a3s-box rm web
58+
```
59+
60+
A box can boot then die — always verify with `ps -a` (a `dead`/gone box does
61+
**not** appear in plain `ps`; empty output means dead-or-gone, not "name typo").
62+
If STATUS=`dead`, its main process exited: read `a3s-box inspect web`
63+
(`.State.ExitCode`, summary `dead (Exit N)`) and `a3s-box logs web`.
64+
65+
## Errors → fix
66+
67+
| Error (on stderr) | Cause → fix |
68+
|---|---|
69+
| `error: unexpected argument '…' found` (Usage … `-- <CMD>`) | missing `--``exec <box> -- <cmd>` |
70+
| `Box X is not running` | stopped/created/dead → `a3s-box start X`; if just run, it died on boot → `inspect`/`logs X` |
71+
| `Box X is not running (status: dead)` | PID 1 exited → `a3s-box inspect X` (`.State.ExitCode`); run a long-lived command |
72+
| `No such box: X` | wrong ref → `a3s-box ps -a` to find name/id |
73+
| `WARN … heartbeat failed, exec will not be available` | guest booted but exec channel never came up → unhealthy; `logs X`, recreate |
74+
| `libkrun call failed status=-17 … krun_add_vsock_port2` / `VM boot failed` | started an already-running/stale box → `a3s-box ps`; if running just `exec`; if wedged `stop X` then `start X` |
75+
76+
## Core commands (non-obvious; full verb list: `a3s-box --help`)
77+
78+
| Goal | Command |
79+
|---|---|
80+
| Run one command, throwaway | `a3s-box run --rm alpine -- echo hi` |
81+
| Create then start | `a3s-box create --name w nginx``a3s-box start w` |
82+
| Exec / interactive shell | `a3s-box exec -it web -- /bin/sh` · `a3s-box shell web` |
83+
| List, custom columns | `a3s-box ps -a --format '{{.ID}} {{.Status}} {{.Names}}'` |
84+
| Build image | `a3s-box build -t app:dev .` |
85+
| Copy in / out | `a3s-box cp ./f web:/data/f` · `a3s-box cp web:/data/f ./f` |
86+
| Commit box → image | `a3s-box commit web app:snap` |
87+
88+
Resource/isolation flags on `run`/`create``--cpus` (default 2), `--memory`
89+
(default 512m), `-e K=V`, `-v host:guest`, `--read-only`,
90+
`--cap-drop ALL --cap-add NET_BIND_SERVICE`, `--pids-limit`, `--network`,
91+
`--init`, … : `a3s-box run --help`.
92+
93+
## Recipes
94+
95+
**Working in-guest localhost (bridge network)**
96+
```sh
97+
a3s-box network create mynet --subnet 10.89.0.0/24
98+
a3s-box run -d --name api --network mynet -p 8080:80 app:dev
99+
```
100+
101+
**Snapshot → restore** (filesystem snapshot; restored box lands in `created`)
102+
```sh
103+
a3s-box snapshot create web # create from a running/stopped box
104+
a3s-box snapshot ls --json
105+
a3s-box snapshot restore <snap-id> --name restored # name it → no ps scraping
106+
a3s-box ps -a --filter name=restored && a3s-box start restored
107+
```
108+
109+
## Finding exact flags & versions
110+
111+
`a3s-box <command> --help` works for every command, and nested help works too:
112+
`a3s-box snapshot create --help`, `a3s-box network create --help`, etc. Check the
113+
build with `a3s-box version`; newer builds (≥2.4) add `pool run`,
114+
`snapshot prune`, and `monitor --install/--metrics-addr`.
115+
116+
## More
117+
118+
Other areas — `network`/`volume`/`compose`/`pool` (warm pre-boot VMs), registry
119+
`login`/`push`, `events`/`audit`/`df`, and TEE (`--tee`/`--tee-simulate`,
120+
`attest`/`seal`/`unseal`/`inject-secret`, `--sidecar`): `a3s-box --help`, then
121+
`a3s-box <cmd> --help`.

integrations/skills/install.sh

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/bin/sh
2+
# Install the a3s-box agent skill into a coding agent's skills directory.
3+
# One SKILL.md, reused across every agent that speaks the Agent-Skills (SKILL.md)
4+
# format. Symlinks by default (single source of truth); --copy to detach.
5+
#
6+
# Usage:
7+
# ./install.sh [--copy] [--home] <agent>...
8+
# ./install.sh --dir <path> # install into an explicit skills dir
9+
#
10+
# agents: agents claude codex a3s-code all
11+
# agents -> .agents/skills cross-tool standard: Codex, Gemini CLI, Amp,
12+
# Cursor, OpenCode, Zed all read this root
13+
# claude -> .claude/skills Claude Code/SDK, Cline, Cursor & OpenCode compat
14+
# codex -> .codex/skills Codex-specific; the a3s CLI menu also scans it
15+
# a3s-code -> .a3s/skills a3s-code agent dir
16+
# all -> agents + claude + codex + a3s-code
17+
# --home install at user scope ($HOME) instead of the current project
18+
# --copy copy the file instead of symlinking
19+
# --dir P treat P as a skills root and drop a3s-box/SKILL.md inside it
20+
#
21+
# Examples:
22+
# ./install.sh all # wire every root in this repo
23+
# ./install.sh --home agents claude # user-wide cross-tool + Claude Code
24+
# ./install.sh --dir ./my-agent/skills # any SKILL.md-format agent dir
25+
set -eu
26+
27+
SRC="$(CDPATH= cd -- "$(dirname -- "$0")/a3s-box" && pwd)/SKILL.md"
28+
[ -f "$SRC" ] || { echo "error: SKILL.md not found at $SRC" >&2; exit 1; }
29+
30+
COPY=0; SCOPE=project; DIR=""; AGENTS=""
31+
while [ $# -gt 0 ]; do
32+
case "$1" in
33+
--copy) COPY=1 ;;
34+
--home) SCOPE=home ;;
35+
--dir) shift; DIR="${1:?--dir needs a path}" ;;
36+
agents|claude|codex|a3s-code|all) AGENTS="$AGENTS $1" ;;
37+
-h|--help) sed -n '2,23p' "$0"; exit 0 ;;
38+
*) echo "error: unknown arg '$1'" >&2; exit 1 ;;
39+
esac
40+
shift
41+
done
42+
43+
# skills root for a named agent at the chosen scope
44+
root_for() {
45+
base="."; [ "$SCOPE" = home ] && base="$HOME"
46+
case "$1" in
47+
agents) echo "$base/.agents/skills" ;; # cross-tool: Codex/Gemini/Amp/Cursor/OpenCode/Zed
48+
claude) echo "$base/.claude/skills" ;;
49+
codex) echo "$base/.codex/skills" ;;
50+
a3s-code) echo "$base/.a3s/skills" ;; # agent-dir convention; pass --dir for a custom agent
51+
esac
52+
}
53+
54+
place() { # place <skills-root>
55+
dest="$1/a3s-box"
56+
mkdir -p "$dest"
57+
if [ "$COPY" -eq 1 ]; then
58+
cp "$SRC" "$dest/SKILL.md"; echo "copied -> $dest/SKILL.md"
59+
else
60+
ln -sf "$SRC" "$dest/SKILL.md"; echo "linked -> $dest/SKILL.md"
61+
fi
62+
}
63+
64+
[ -n "$DIR" ] && { place "$DIR"; }
65+
66+
case "$AGENTS" in *all*) AGENTS="agents claude codex a3s-code" ;; esac
67+
for a in $AGENTS; do place "$(root_for "$a")"; done
68+
69+
[ -z "$DIR$AGENTS" ] && { echo "nothing to do — pass an agent (agents|claude|codex|a3s-code|all) or --dir" >&2; exit 1; }
70+
echo "done. reload the agent to pick up the skill."

0 commit comments

Comments
 (0)