feat(smolmachines): provision_prompt + provision_skills (PRD 0023 chunk 4a) #69

Merged
didericis merged 2 commits from prd-0023-chunk-4a-provision-prompt-skills into main 2026-05-27 05:21:26 -04:00
Collaborator

Summary

First slice of chunk 4. Implements the two provisioning methods that don't require agent-image tooling beyond cp and mkdirprovision_prompt and provision_skills. The remaining three (provision_ca, provision_git, provision_supervise) wait on:

  • provision_ca — needs the inner Plans (PipelockProxyPlan / EgressPlan) threaded through prepare + launch for the CA path, AND update-ca-certificates in the agent image.
  • provision_git — needs the git-gate inner Plan for the gate's host + git in the agent image.
  • provision_supervise — needs the claude binary in the agent image so we can run claude mcp add.

The alpine placeholder image used since chunk 2d has cp and mkdir. That's enough for chunks 4a; the other three provision methods land once the agent-image-conversion gap is closed.

What landed

  • claude_bottle/backend/smolmachines/provision/{__init__,prompt,skills}.pysmolvm machine cp / machine_exec routed implementations. Same contract as the docker backend (provision_prompt returns the in-VM path iff the agent has a non-empty prompt; provision_skills mkdir + cp per named skill, no-op when empty).
  • prepare.py now writes the agent's prompt file under agent_state_dir(slug)/prompt.txt (mode 0o600, content from the agent's prompt field, possibly empty).
  • launch.py calls provision(plan, machine_name) after machine_start; the returned prompt path threads to SmolmachinesBottle so exec_claude adds --append-system-prompt-file when relevant.
  • backend.py: provision_prompt / provision_skills wired to the new module; provision_git is a deliberate stub (waiting on git-gate inner Plan + git tooling).

Tests

  • 7 new unit cases (tests/unit/test_smolmachines_provision.py): argv shape, prompt return-value contract, skills no-op when empty, CLAUDE_BOTTLE_GUEST_SKILLS_DIR override, fail-on-missing-skill.
  • 1 new integration case (tests/integration/test_smolmachines_launch.py::test_prompt_file_lands_in_guest): end-to-end verification against the alpine guest — bottle.exec("cat /root/.claude-bottle-prompt.txt") returns the manifest's prompt text.

552 unit + 4 integration tests passing locally (Darwin + smolvm + docker gated).

Roadmap for the rest of chunk 4

  • 4b — thread PipelockProxyPlan / EgressPlan / GitGatePlan / SupervisePlan through prepare + launch so the bundle's daemons run (currently daemons_csv="").
  • 4c — agent-image-conversion: get claude-code + git + curl + ca-certificates into the guest. Two paths: build a .smolmachine via smolvm pack create --from-vm after manual setup, or push the existing docker image to a registry smolvm can pull from.
  • 4dprovision_ca + provision_git + provision_supervise once 4b + 4c land.
## Summary First slice of chunk 4. Implements the two provisioning methods that don't require agent-image tooling beyond `cp` and `mkdir` — `provision_prompt` and `provision_skills`. The remaining three (`provision_ca`, `provision_git`, `provision_supervise`) wait on: - **provision_ca** — needs the inner Plans (`PipelockProxyPlan` / `EgressPlan`) threaded through prepare + launch for the CA path, AND `update-ca-certificates` in the agent image. - **provision_git** — needs the git-gate inner Plan for the gate's host + `git` in the agent image. - **provision_supervise** — needs the `claude` binary in the agent image so we can run `claude mcp add`. The alpine placeholder image used since chunk 2d has `cp` and `mkdir`. That's enough for chunks 4a; the other three provision methods land once the agent-image-conversion gap is closed. ## What landed - `claude_bottle/backend/smolmachines/provision/{__init__,prompt,skills}.py` — `smolvm machine cp` / `machine_exec` routed implementations. Same contract as the docker backend (`provision_prompt` returns the in-VM path iff the agent has a non-empty prompt; `provision_skills` mkdir + cp per named skill, no-op when empty). - `prepare.py` now writes the agent's prompt file under `agent_state_dir(slug)/prompt.txt` (mode 0o600, content from the agent's `prompt` field, possibly empty). - `launch.py` calls `provision(plan, machine_name)` after `machine_start`; the returned prompt path threads to `SmolmachinesBottle` so `exec_claude` adds `--append-system-prompt-file` when relevant. - `backend.py`: `provision_prompt` / `provision_skills` wired to the new module; `provision_git` is a deliberate stub (waiting on git-gate inner Plan + git tooling). ## Tests - **7 new unit cases** (`tests/unit/test_smolmachines_provision.py`): argv shape, prompt return-value contract, skills no-op when empty, `CLAUDE_BOTTLE_GUEST_SKILLS_DIR` override, fail-on-missing-skill. - **1 new integration case** (`tests/integration/test_smolmachines_launch.py::test_prompt_file_lands_in_guest`): end-to-end verification against the alpine guest — `bottle.exec("cat /root/.claude-bottle-prompt.txt")` returns the manifest's prompt text. **552 unit + 4 integration tests passing locally** (Darwin + smolvm + docker gated). ## Roadmap for the rest of chunk 4 - **4b** — thread `PipelockProxyPlan` / `EgressPlan` / `GitGatePlan` / `SupervisePlan` through prepare + launch so the bundle's daemons run (currently `daemons_csv=""`). - **4c** — agent-image-conversion: get `claude-code` + `git` + `curl` + `ca-certificates` into the guest. Two paths: build a `.smolmachine` via `smolvm pack create --from-vm` after manual setup, or push the existing docker image to a registry smolvm can pull from. - **4d** — `provision_ca` + `provision_git` + `provision_supervise` once 4b + 4c land.
didericis-claude added 1 commit 2026-05-27 05:08:39 -04:00
feat(smolmachines): provision_prompt + provision_skills (PRD 0023 chunk 4a)
test / unit (pull_request) Successful in 21s
test / integration (pull_request) Successful in 43s
9e3b7e441e
First slice of chunk 4: implement the two provisioning methods
that don't depend on agent-image tooling beyond `cp` and
`mkdir`. provision_ca / provision_git / provision_supervise
land once the agent-image gap is solved (chunk 4b+) — they need
update-ca-certificates, git, and the claude binary respectively,
none of which the chunk-2d alpine placeholder provides.

What this PR ships:

- `claude_bottle/backend/smolmachines/provision/` subpackage
  with `prompt.py` + `skills.py`. Each routes through
  `smolvm.machine_cp` / `machine_exec`. provision_prompt mirrors
  the docker contract (file always copied; return value drives
  --append-system-prompt-file iff the agent has a non-empty
  prompt). provision_skills mkdir + cp per skill, matching
  the docker backend's loop.
- prepare.py now writes the prompt file under
  agent_state_dir(slug) with the agent's `prompt` body, mode
  0o600. The in-guest path is `/root/.claude-bottle-prompt.txt`
  (alpine has no `node` user; will become `/home/node/...` once
  the real claude-bottle image lands).
- launch.py calls `provision(plan, machine_name)` after
  machine_start. The returned prompt path threads to
  SmolmachinesBottle so exec_claude can add
  --append-system-prompt-file when the agent has a prompt.
- backend.py: provision_prompt / provision_skills now real;
  provision_git is a deliberate stub (waiting on the git-gate
  inner Plan + git in the agent image). provision_supervise
  stays the chunk-2d stub.

Tests:
- 7 new unit cases (test_smolmachines_provision.py): argv
  shape (mocked smolvm.machine_cp / .machine_exec),
  prompt return-value contract, no-op-with-no-skills,
  CLAUDE_BOTTLE_GUEST_SKILLS_DIR override, fail-on-missing-skill.
- 1 new integration case in test_smolmachines_launch.py:
  end-to-end verification that the prompt file lands in the
  alpine guest at /root/.claude-bottle-prompt.txt with the
  expected content (via `bottle.exec("cat ...")`). The smoke +
  the two TSI probes stay green.

552 unit + 4 integration (Darwin+smolvm+docker gated) passing.

What's left in chunk 4:
- 4b: thread the inner Plans (PipelockProxyPlan / EgressPlan /
  GitGatePlan / SupervisePlan) through prepare + launch so the
  bundle daemons actually run (currently daemons_csv="").
- 4c: the agent-image-conversion gap — get claude-code + git +
  curl + ca-certificates into the guest image (build a
  .smolmachine via `pack create --from-vm` after manual setup,
  or push the docker image to a registry smolvm can pull).
- 4d: provision_ca + provision_git + provision_supervise once
  4b + 4c land.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
didericis reviewed 2026-05-27 05:17:29 -04:00
@@ -55,0 +53,4 @@
# Chunk 4 follow-on: needs the git-gate inner Plan (so the
# gitconfig insteadOf URL points at the gate's host) and
# the agent image must contain `git`. Stub for chunk 4a.
del plan, target
Owner

why delete these, even as a stub? Why not just do pass?

why delete these, even as a stub? Why not just do pass?
didericis marked this conversation as resolved
didericis added 1 commit 2026-05-27 05:18:22 -04:00
style(smolmachines): provision_git stub uses pass not del
test / integration (pull_request) Successful in 43s
test / unit (push) Successful in 22s
test / integration (push) Successful in 39s
test / unit (pull_request) Successful in 21s
085a0c1923
Addresses PR #69 review comment: `del plan, target` was just a
silence-the-unused-arg gesture but reads oddly for a stub. `pass`
is the standard "this is a stub" sentinel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
didericis-claude reviewed 2026-05-27 05:18:29 -04:00
didericis-claude left a comment
Author
Collaborator

Fair — fixed in 085a0c1 (pass instead of del plan, target). The del was just to silence unused-arg warnings, but pass is the idiomatic stub marker and reads cleaner.

Fair — fixed in 085a0c1 (`pass` instead of `del plan, target`). The `del` was just to silence unused-arg warnings, but `pass` is the idiomatic stub marker and reads cleaner.
didericis merged commit 085a0c1923 into main 2026-05-27 05:21:26 -04:00
didericis deleted branch prd-0023-chunk-4a-provision-prompt-skills 2026-05-27 05:21:27 -04:00
Sign in to join this conversation.