docs: make the box narrative agent-agnostic, not Claude-specific #58
4 changed files with 63 additions and 51 deletions
40
README.md
40
README.md
|
|
@ -1,12 +1,13 @@
|
||||||
# box
|
# box
|
||||||
|
|
||||||
**Headless, trust-less, throwaway dev VMs.** One command mints a fresh,
|
**Headless, trust-less, throwaway dev VMs.** One command mints a fresh,
|
||||||
network-isolated Incus box from a **template**; the flagship template is
|
network-isolated Incus box from a **template**; the coding-agent templates
|
||||||
`claude` — Debian 13 with Claude Code installed. The box is the product —
|
ship a CLI agent on Debian 13 — `claude` (Claude Code), `codex` (OpenAI
|
||||||
you log in and work; destroying it loses nothing you didn't push.
|
Codex), `grok` (xAI Grok). The box is the product — you log in and work;
|
||||||
|
destroying it loses nothing you didn't push.
|
||||||
|
|
||||||
**Strictly creds-free.** A box ships with everything installed and **no**
|
**Strictly creds-free.** A box ships with everything installed and **no**
|
||||||
credentials — no Claude token, no git PAT, nothing. You authenticate
|
credentials — no agent token, no git PAT, nothing. You authenticate
|
||||||
interactively *inside* the box. The tool never stores or injects a secret. That
|
interactively *inside* the box. The tool never stores or injects a secret. That
|
||||||
means there's nothing shared or committed, so it's safe for multiple operators
|
means there's nothing shared or committed, so it's safe for multiple operators
|
||||||
out of the box.
|
out of the box.
|
||||||
|
|
@ -18,8 +19,8 @@ nobody home — not a box with the safety off.
|
||||||
|
|
||||||
**The tool knows nothing about your projects.** You just `git clone` inside a
|
**The tool knows nothing about your projects.** You just `git clone` inside a
|
||||||
box. A repo can ship an optional [`.box/`](docs/box-recipe.md)
|
box. A repo can ship an optional [`.box/`](docs/box-recipe.md)
|
||||||
runbook that Claude Code reads and acts on — there is no `install` step and no
|
runbook that the box's coding agent reads and acts on — there is no `install`
|
||||||
host-run setup. See [docs/box-design.md](docs/box-design.md) for the
|
step and no host-run setup. See [docs/box-design.md](docs/box-design.md) for the
|
||||||
design rationale.
|
design rationale.
|
||||||
|
|
||||||
> **0.5.0**: two new templates (`codex`, `grok`), `box expose` — a
|
> **0.5.0**: two new templates (`codex`, `grok`), `box expose` — a
|
||||||
|
|
@ -66,23 +67,26 @@ legacy box remains.
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
box new --name work --template claude # a creds-free Claude box (~10 min cold)
|
box new --name work --template claude # a creds-free coding-agent box (~10 min cold)
|
||||||
box shell work # enter as the template's user
|
box shell work # enter as the template's user
|
||||||
```
|
```
|
||||||
|
|
||||||
Inside the box, authenticate as needed:
|
Pick whichever coding-agent template you like — `claude`, `codex`, `grok` — or
|
||||||
|
`blank` for none. Inside the box, authenticate as needed. The `claude` template
|
||||||
|
looks like this; the others follow the same shape with their own login step:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
claude # then run /login — copy the URL (press c), open it
|
claude # then run /login — copy the URL (press c), open it
|
||||||
# in YOUR browser, paste the code back. No host CLI needed.
|
# in YOUR browser, paste the code back. No host CLI needed.
|
||||||
gh auth login # or drop a PAT in — your git credentials, your call
|
gh auth login # or drop a PAT in — your git credentials, your call
|
||||||
git clone https://github.com/you/project && cd project
|
git clone https://github.com/you/project && cd project
|
||||||
claude # if the repo has .box/, Claude reads it and sets up
|
claude # if the repo has .box/, the agent reads it and sets up
|
||||||
```
|
```
|
||||||
|
|
||||||
## Templates
|
## Templates
|
||||||
|
|
||||||
The claude box is one template among several. What ships today:
|
No coding agent is special — each is one template among several, and adding
|
||||||
|
another is just another directory. What ships today:
|
||||||
|
|
||||||
| Template | What's in it |
|
| Template | What's in it |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
|
@ -122,7 +126,7 @@ box snapshot work authed # checkpoint after you've logged in
|
||||||
box new --name feature --from work/authed # clone the authed state into a new box
|
box new --name feature --from work/authed # clone the authed state into a new box
|
||||||
```
|
```
|
||||||
|
|
||||||
`--from` copies the whole box (Claude login, git creds, clones and all) while
|
`--from` copies the whole box (agent login, git creds, clones and all) while
|
||||||
preserving isolation. You can also `box new --name x --from work` to clone
|
preserving isolation. You can also `box new --name x --from work` to clone
|
||||||
a box's live state, or roll a box back with `box restore work authed`.
|
a box's live state, or roll a box back with `box restore work authed`.
|
||||||
|
|
||||||
|
|
@ -233,8 +237,9 @@ enforces it, layer by layer:
|
||||||
that door only ever opens onto the host's own loopback (`127.0.0.1`), never
|
that door only ever opens onto the host's own loopback (`127.0.0.1`), never
|
||||||
the network.
|
the network.
|
||||||
|
|
||||||
The VM is the trust boundary: whatever runs inside — Claude, or anything a
|
The VM is the trust boundary: whatever runs inside — the coding agent, or
|
||||||
template ships — can run arbitrary code and touch nothing you care about.
|
anything a template ships — can run arbitrary code and touch nothing you care
|
||||||
|
about.
|
||||||
|
|
||||||
### Measured, not claimed
|
### Measured, not claimed
|
||||||
|
|
||||||
|
|
@ -265,9 +270,10 @@ inherit.
|
||||||
## Recipes: the `.box/` convention
|
## Recipes: the `.box/` convention
|
||||||
|
|
||||||
A repo that wants to be easy to stand up in a box ships an optional `.box/`
|
A repo that wants to be easy to stand up in a box ships an optional `.box/`
|
||||||
folder — a runbook Claude reads and follows (install deps, start services,
|
folder — a runbook the box's coding agent reads and follows (install deps,
|
||||||
template env, seed data, smoke-test). It is agent-facing documentation, not a
|
start services, template env, seed data, smoke-test). It is agent-facing
|
||||||
host-executed script. See [docs/box-recipe.md](docs/box-recipe.md).
|
documentation, not a host-executed script. See
|
||||||
|
[docs/box-recipe.md](docs/box-recipe.md).
|
||||||
|
|
||||||
## Uninstall
|
## Uninstall
|
||||||
|
|
||||||
|
|
@ -280,6 +286,6 @@ rm -rf ~/.local/share/box ~/.local/bin/box # the CLI itself
|
||||||
## Non-goals
|
## Non-goals
|
||||||
|
|
||||||
- **No unattended/CI bring-up.** The flow is interactive (log in, clone, ask
|
- **No unattended/CI bring-up.** The flow is interactive (log in, clone, ask
|
||||||
Claude). Reproducible-by-construction provisioning is out of scope.
|
the agent). Reproducible-by-construction provisioning is out of scope.
|
||||||
- **No credential storage or injection by the tool.** Boxes are creds-free;
|
- **No credential storage or injection by the tool.** Boxes are creds-free;
|
||||||
snapshots are the reuse mechanism, not a secrets store.
|
snapshots are the reuse mechanism, not a secrets store.
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,30 @@
|
||||||
# box design
|
# box design
|
||||||
|
|
||||||
`box` is a CLI that mints and manages **trust-less, network-isolated VMs
|
`box` is a CLI that mints and manages **trust-less, network-isolated VMs
|
||||||
with Claude Code installed**. It is infrastructure, not a project provisioner.
|
with a coding agent installed** (`claude`, `codex`, `grok`, or `blank` for
|
||||||
|
none). It is infrastructure, not a project provisioner.
|
||||||
|
|
||||||
See issue #3 for the full reframe and rationale. This doc captures the durable
|
See issue #3 for the full reframe and rationale. This doc captures the durable
|
||||||
design decisions.
|
design decisions.
|
||||||
|
|
||||||
## Principle: separate the tool from the agent
|
## Principle: separate the tool from the agent
|
||||||
|
|
||||||
- **The tool** mints isolated boxes with Claude installed but **unauthenticated**.
|
- **The tool** mints isolated boxes with the agent installed but **unauthenticated**.
|
||||||
It knows nothing about projects, secrets, recipes, or memory.
|
It knows nothing about projects, secrets, recipes, or memory.
|
||||||
- **The agent** (Claude Code, inside the box) reads an optional `.box/`
|
- **The agent** (Claude Code, Codex, Grok — whichever template, inside the box)
|
||||||
runbook in a cloned repo and acts on it. The recipe's consumer is the
|
reads an optional `.box/` runbook in a cloned repo and acts on it. The recipe's
|
||||||
reasoning agent, not host machinery.
|
consumer is the reasoning agent, not host machinery.
|
||||||
|
|
||||||
## Boxes are strictly creds-free
|
## Boxes are strictly creds-free
|
||||||
|
|
||||||
`box new --name <n>` launches a blank box: everything installed, **no**
|
`box new --name <n>` launches a blank box: everything installed, **no**
|
||||||
git credentials and **no** Claude credentials. The operator authenticates
|
git credentials and **no** agent credentials. The operator authenticates
|
||||||
interactively *inside* the box:
|
interactively *inside* the box:
|
||||||
|
|
||||||
- **Claude** — `claude` → `/login` (paste-a-code OAuth: copy the URL, open it in
|
- **The coding agent** — e.g. `claude` → `/login` (paste-a-code OAuth: copy the
|
||||||
your own browser, paste the code back). Works because the box is outbound-only;
|
URL, open it in your own browser, paste the code back); `codex` and `grok`
|
||||||
the tool never handles a token.
|
have their own login step. Works because the box is outbound-only; the tool
|
||||||
|
never handles a token.
|
||||||
- **Git** — the operator adds their own PAT / `gh auth login` inside the box.
|
- **Git** — the operator adds their own PAT / `gh auth login` inside the box.
|
||||||
|
|
||||||
The tool stores and injects **no** credentials, ever. This dissolves the
|
The tool stores and injects **no** credentials, ever. This dissolves the
|
||||||
|
|
@ -36,15 +38,16 @@ snapshots, not a secrets store:
|
||||||
- `box snapshot <n> [label]` — checkpoint after login + clone.
|
- `box snapshot <n> [label]` — checkpoint after login + clone.
|
||||||
- `box new --name <n2> --from <src>[/<snapshot>]` — clone an existing box
|
- `box new --name <n2> --from <src>[/<snapshot>]` — clone an existing box
|
||||||
or snapshot (authed state and all). Isolation is preserved: the clone keeps
|
or snapshot (authed state and all). Isolation is preserved: the clone keeps
|
||||||
the `claude-dev` profile + `claudenet` + ACL.
|
the `box-net` profile + `boxnet` + ACL.
|
||||||
- `box restore <n> <snapshot>` — roll a box back to a checkpoint.
|
- `box restore <n> <snapshot>` — roll a box back to a checkpoint.
|
||||||
|
|
||||||
Log in once → snapshot → spin up authed boxes from it.
|
Log in once → snapshot → spin up authed boxes from it.
|
||||||
|
|
||||||
## The box announces itself to the agent
|
## The box announces itself to the agent
|
||||||
|
|
||||||
cloud-init installs a global `~/.claude/CLAUDE.md` in every box telling Claude it
|
cloud-init installs a global agent-context file in every coding-agent box
|
||||||
is running in a box (trust-less, ephemeral, creds-free) and to treat a
|
(`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, `~/.grok/AGENTS.md`) telling the
|
||||||
|
agent it is running in a box (trust-less, ephemeral, creds-free) and to treat a
|
||||||
repo's `.box/` folder as its bootstrap runbook. No "tell it" step, no host
|
repo's `.box/` folder as its bootstrap runbook. No "tell it" step, no host
|
||||||
execution.
|
execution.
|
||||||
|
|
||||||
|
|
@ -62,7 +65,7 @@ forever — and wrapping them one at a time grows a worse `incus`. The rule:
|
||||||
|
|
||||||
> **box owns a command when it must enforce an invariant Incus cannot see:**
|
> **box owns a command when it must enforce an invariant Incus cannot see:**
|
||||||
> the `user.box=1` boundary (never touch an instance we didn't mint), the
|
> the `user.box=1` boundary (never touch an instance we didn't mint), the
|
||||||
> isolation stack (`claude-dev` profile + `claudenet` + ACL), or the creds-free
|
> isolation stack (`box-net` profile + `boxnet` + ACL), or the creds-free
|
||||||
> snapshot→clone workflow. Everything else is Incus's job.
|
> snapshot→clone workflow. Everything else is Incus's job.
|
||||||
|
|
||||||
The rule cuts both ways, and that's the point:
|
The rule cuts both ways, and that's the point:
|
||||||
|
|
@ -92,7 +95,7 @@ warns and proceeds — from there the trust boundary is yours to keep.
|
||||||
|
|
||||||
## Isolation
|
## Isolation
|
||||||
|
|
||||||
Dedicated NAT bridge `claudenet` + Incus `claude-isolate` ACL dropping all
|
Dedicated NAT bridge `boxnet` + Incus `box-isolate` ACL dropping all
|
||||||
RFC1918/CGNAT/link-local egress, plus host-firewall rules blocking instance →
|
RFC1918/CGNAT/link-local egress, plus host-firewall rules blocking instance →
|
||||||
host. Entry is `incus exec` over the local socket — no inbound path. The VM is
|
host. Entry is `incus exec` over the local socket — no inbound path. The VM is
|
||||||
the trust boundary.
|
the trust boundary.
|
||||||
|
|
@ -101,12 +104,12 @@ the trust boundary.
|
||||||
That last clause is the one that was assumed and turned out to be false, so it
|
That last clause is the one that was assumed and turned out to be false, so it
|
||||||
is spelled out here with the mechanism, and `drill/` tests it on every run.
|
is spelled out here with the mechanism, and `drill/` tests it on every run.
|
||||||
|
|
||||||
- **Box → host, LAN, RFC1918, CGNAT, link-local:** the `claude-isolate` ACL.
|
- **Box → host, LAN, RFC1918, CGNAT, link-local:** the `box-isolate` ACL.
|
||||||
- **Box → box: an nftables *bridge-family* rule** (`host/box-firewall.sh`).
|
- **Box → box: an nftables *bridge-family* rule** (`host/box-firewall.sh`).
|
||||||
It cannot be an ACL rule. Two boxes on one bridge share an L2 segment, so
|
It cannot be an ACL rule. Two boxes on one bridge share an L2 segment, so
|
||||||
their frames are *switched* between bridge ports and never traverse the
|
their frames are *switched* between bridge ports and never traverse the
|
||||||
netfilter path an L3 ACL lives on — the ACL looked airtight (it drops
|
netfilter path an L3 ACL lives on — the ACL looked airtight (it drops
|
||||||
`10.0.0.0/8`, which contains `claudenet`) while box→box was in fact wide open.
|
`10.0.0.0/8`, which contains `boxnet`) while box→box was in fact wide open.
|
||||||
A live probe found box A's SYN arriving at box B. The bridge family's forward
|
A live probe found box A's SYN arriving at box B. The bridge family's forward
|
||||||
hook fires exactly on port-to-port frames, which on this bridge means box→box
|
hook fires exactly on port-to-port frames, which on this bridge means box→box
|
||||||
and nothing else: gateway traffic and routed egress are delivered locally, not
|
and nothing else: gateway traffic and routed egress are delivered locally, not
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
# The `.box/` convention
|
# The `.box/` convention
|
||||||
|
|
||||||
`box` mints trust-less, creds-free, isolated VMs with Claude Code already
|
`box` mints trust-less, creds-free, isolated VMs with a coding agent already
|
||||||
installed (`box new/shell/snapshot/restore/exec/down/start/rm/status`). The
|
installed (`box new/shell/snapshot/restore/exec/down/start/rm/status`) — the
|
||||||
tool knows **nothing** about your project. There is no `install` step and no
|
`claude`, `codex`, and `grok` templates each ship a CLI agent. The tool knows
|
||||||
host-run setup script.
|
**nothing** about your project. There is no `install` step and no host-run
|
||||||
|
setup script.
|
||||||
|
|
||||||
A project makes itself easy to stand up inside a box by shipping an optional
|
A project makes itself easy to stand up inside a box by shipping an optional
|
||||||
`.box/` folder. This folder is **agent-facing documentation** — read and
|
`.box/` folder. This folder is **agent-facing documentation** — read and
|
||||||
acted on by Claude Code (the reasoning agent) running inside the box. It is not
|
acted on by the box's coding agent (the reasoning agent) running inside the
|
||||||
shell that the host executes.
|
box, whichever template you minted. It is not shell that the host executes.
|
||||||
|
|
||||||
> The folder was named `.claudebox/` before the 0.5.0 rename. Repos that still
|
> The folder was named `.claudebox/` before the 0.5.0 rename. Repos that still
|
||||||
> ship `.claudebox/` keep working — the agent is told to read either — but new
|
> ship `.claudebox/` keep working — the agent is told to read either — but new
|
||||||
|
|
@ -26,15 +27,17 @@ shell that the host executes.
|
||||||
|
|
||||||
## How it's consumed
|
## How it's consumed
|
||||||
|
|
||||||
Every box ships a global `~/.claude/CLAUDE.md` telling Claude it is inside a
|
Every coding-agent box ships a global agent-context file — `~/.claude/CLAUDE.md`
|
||||||
box and to treat a repo's `.box/` folder as its bootstrap runbook.
|
for `claude`, `~/.codex/AGENTS.md` for `codex`, `~/.grok/AGENTS.md` for `grok` —
|
||||||
So the whole flow is:
|
telling the agent it is inside a box and to treat a repo's `.box/` folder as its
|
||||||
|
bootstrap runbook. So the whole flow is (shown with `claude`; the other agents
|
||||||
|
follow the same shape):
|
||||||
|
|
||||||
```
|
```
|
||||||
box new # get a box
|
box new # get a box
|
||||||
box shell # get in
|
box shell # get in
|
||||||
git clone <repo> && cd <repo>
|
git clone <repo> && cd <repo>
|
||||||
claude # Claude reads .box/ and brings the project up
|
claude # the agent reads .box/ and brings the project up
|
||||||
```
|
```
|
||||||
|
|
||||||
The operator can also just say: *"set this project up per .box"*.
|
The operator can also just say: *"set this project up per .box"*.
|
||||||
|
|
@ -45,9 +48,9 @@ Author everything here for a reasoning agent.
|
||||||
|
|
||||||
- **`.box/SETUP.md`** — the prose runbook. Prerequisites, how to install
|
- **`.box/SETUP.md`** — the prose runbook. Prerequisites, how to install
|
||||||
deps, how to start services, how to template the env, how to seed data, and
|
deps, how to start services, how to template the env, how to seed data, and
|
||||||
how to smoke-test. Written as instructions to Claude.
|
how to smoke-test. Written as instructions to the agent.
|
||||||
- **Helper scripts** (e.g. `.box/dev-up.sh`) that the runbook tells Claude
|
- **Helper scripts** (e.g. `.box/dev-up.sh`) that the runbook tells the agent
|
||||||
to run. Claude decides to run them; the host never does.
|
to run. The agent decides to run them; the host never does.
|
||||||
- **`.box/env.template`** — example env the runbook explains how to fill.
|
- **`.box/env.template`** — example env the runbook explains how to fill.
|
||||||
Staging values the operator pastes in. **Never commit real secrets.**
|
Staging values the operator pastes in. **Never commit real secrets.**
|
||||||
- **`.box/compose.yml`** — optional services the runbook starts.
|
- **`.box/compose.yml`** — optional services the runbook starts.
|
||||||
|
|
@ -70,7 +73,7 @@ This is a Node service backed by Postgres.
|
||||||
6. Smoke-test: `curl -sf localhost:3000/health` should return `{"ok":true}`.
|
6. Smoke-test: `curl -sf localhost:3000/health` should return `{"ok":true}`.
|
||||||
```
|
```
|
||||||
|
|
||||||
That's it — Claude reads it top to bottom and adapts if reality differs.
|
That's it — the agent reads it top to bottom and adapts if reality differs.
|
||||||
|
|
||||||
## Guidance
|
## Guidance
|
||||||
|
|
||||||
|
|
@ -79,4 +82,4 @@ That's it — Claude reads it top to bottom and adapts if reality differs.
|
||||||
- **Never put real credentials in `.box/`.** Templates and staging
|
- **Never put real credentials in `.box/`.** Templates and staging
|
||||||
placeholders only. The operator pastes real values at runtime.
|
placeholders only. The operator pastes real values at runtime.
|
||||||
- **No `.box/` is fine.** The operator can stand the project up by hand,
|
- **No `.box/` is fine.** The operator can stand the project up by hand,
|
||||||
or let Claude infer the steps from the repo's `README` / `CLAUDE.md`.
|
or let the agent infer the steps from the repo's `README` / `CLAUDE.md`.
|
||||||
|
|
|
||||||
|
|
@ -84,9 +84,9 @@ a **design veto** for #16, caught before the code is written.
|
||||||
|
|
||||||
## What it does not check
|
## What it does not check
|
||||||
|
|
||||||
`claude /login` — it's interactive by design, and the box is creds-free by
|
agent login (e.g. `claude /login`) — it's interactive by design, and the box is
|
||||||
design. The drill confirms Claude Code is installed and runnable; authenticating
|
creds-free by design. The drill confirms each coding-agent template's CLI is
|
||||||
is yours.
|
installed and runnable; authenticating is yours.
|
||||||
|
|
||||||
If the host has no `/dev/kvm`, box falls back to container mode. The drill
|
If the host has no `/dev/kvm`, box falls back to container mode. The drill
|
||||||
still runs, but it says loudly that **the VM trust boundary was not validated**
|
still runs, but it says loudly that **the VM trust boundary was not validated**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue