Files
clawmates/images/agent-claude/Dockerfile
T
Omar SobhandClaude Opus 5 0c291ed1bb feat(fleet): B4.4a — a real agent-claude microVM image, and a check that it has an agent in it
The only rootfs on this track came from clawmates/agent-terminal:dev. Mounted,
it held git and nothing else: no claude, no node, no cargo. A VM booted from it
looks perfect and cannot run a mission, so B4.5 could have been written and
never verified.

images/agent-toolchain — the shared mission toolchain (node 22, git, rust +
cargo-audit, gitleaks/trivy/semgrep, tea/gitea-mcp), lifted from the proven
deploy/clawmates-runtime image minus the zeroclaw daemon: a microVM mission runs
the direct-session model, so there is no daemon to host. A base image rather
than three self-contained Dockerfiles because this layer is ~3 GB and the real
risk is scanner and toolchain versions drifting between per-CLI images — the
evaluator runs the project's own suite to check a claim, so `cargo` present in
one image and absent in another makes the same mission pass or fail by backend
with nothing saying why.

images/agent-claude — plan A6, first of three: the pinned CLI and its env
contract only, so bumping Claude Code does not rebuild the toolchain and cannot
disturb agent-kimi / agent-glm. HOME=/root with an empty .claude for B4.4 to
inject into; no ANTHROPIC_API_KEY, since it silently overrides the subscription
OAuth we already pay for.

Both the builder and the node selftest now ASK the guest for the CLI the image
is named for, instead of trusting the name. `required_cli` maps claude/kimi/glm
to a probe; an unrecognised backend reports unchecked and prints SKIP rather
than passing quietly.

Verified on tank:
  - rootfs-claude.ext4 boots; git, node, cargo, a real git commit all work
  - `claude --version` → 2.1.220 over vsock, in both the builder and
    `--vm-selftest` (11/11, create 1498 ms)
  - negative control: the same builder run against agent-terminal with
    FC_CLI forced reports `cli rc=127 claude: not found` and exits 1, so the
    green result above is a measurement and not a default
  - `claude -p hello` fails with "Not logged in · Please run /login" — the CLI
    runs headless in the VM, and B4.4 only has to supply the credential
  - no leaked firecracker processes or vm dirs afterwards

437 tests pass, clippy clean.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-05 11:33:07 -07:00

40 lines
1.9 KiB
Docker

# Plan A6, first of three: one image per agent CLI, independently versioned.
#
# Everything shared lives in agent-toolchain (git, node, rust, scanners, tea).
# This layer is only the CLI and its env contract, so bumping Claude Code does
# not rebuild 3 GB of toolchain and cannot disturb agent-kimi / agent-glm.
#
# Build (on the node that will run it — see agent-toolchain for why this is not
# in AGENT_IMAGES):
#
# ssh osobh@tank "cd ~/clawmates && \
# docker build -f images/agent-toolchain/Dockerfile -t clawmates/agent-toolchain:dev images/agent-toolchain/ && \
# docker build -f images/agent-claude/Dockerfile -t clawmates/agent-claude:dev images/agent-claude/"
#
# Then turn it into a microVM rootfs and prove a VM boots from it:
#
# scripts/fc-build-rootfs.sh osobh@tank clawmates/agent-claude:dev claude 8G
FROM clawmates/agent-toolchain:dev
# Pinned: an unpinned `npm i -g` makes the image's behaviour depend on the day
# it was built, and a mission that regresses would have no version to compare.
ARG CLAUDE_CODE_VERSION=2.1.220
RUN npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" \
&& npm cache clean --force \
&& rm -rf /root/.npm \
&& claude --version
# The CLI reads its credentials from $HOME/.claude. On the container path HOME is
# /zeroclaw-data because the daemon owns it; here there is no daemon, so HOME is
# just root's home. Credential injection (B4.4) writes into this directory over
# vsock at VM start so the credentials live and die with the VM and are never
# baked into the image.
ENV HOME=/root \
CLAWMATES_AGENT_CLI=claude
RUN mkdir -p /root/.claude
# No ANTHROPIC_API_KEY, and none is accepted: this backend authenticates by
# subscription via CLAUDE_CODE_OAUTH_TOKEN. An API key present in the
# environment silently overrides the subscription OAuth (fixed once already,
# task #16) and would bill per-token against a plan we already pay for.