Skip to content

Build each harness from its package directory - #51

Open
CrypticSwarm wants to merge 5 commits into
harness-makefile-fragmentsfrom
harness-dockerfile-localization
Open

Build each harness from its package directory#51
CrypticSwarm wants to merge 5 commits into
harness-makefile-fragmentsfrom
harness-dockerfile-localization

Conversation

@CrypticSwarm

Copy link
Copy Markdown
Owner

The Dockerfile installed every harness from one case "${AGENT}" and kept four near-identical <name>-runtime stages. Each harness's install now lives in swarmforge/harness/<name>/install.sh — the package is already copied into the image before the install stage, so the scripts ride along with no new COPY and unchanged cache behavior — and the four runtime stages collapse into a single harness-runtime stage that runs the harness's image.sh when one exists. Claude is the only harness with one: it installs the status line and default settings at their existing image paths, and statusline.sh/claude-settings.json move next to it under swarmforge/harness/claude/.

ARG OPENCODE_VERSION generalizes to ARG SWARMFORGE_HARNESS_VERSION, with opencode's make fragment mapping the unchanged OPENCODE_VERSION user knob onto it. The build argv baselines change in exactly those two ways — the --target word and the build-arg name; run argv is untouched. Invoking docker build --target claude-runtime by hand needs the new stage name; the README notes the rename.

Tests pin the extraction: the Dockerfile executes each package's script, a missing script fails the build, every script sets errexit, and the recipes' --target names a declared stage.

The agent-runtime stage installed the selected binary through a case on
AGENT, so every harness's install recipe lived in one Dockerfile block.
Each arm now ships verbatim as swarmforge/harness/<name>/install.sh and
the stage runs the script for the requested AGENT out of the copied
package. An AGENT with no script fails the build with the same
diagnostic the case's fallthrough arm printed.

The scripts run under their own set -eux, matching the options the RUN
set for the case arms. Cache behavior is unchanged: the package copy
already preceded the install layer, so an edit under swarmforge/ busts
it today too.

Two new layout tests hold the dispatch together: the install RUN must
assemble its path from the same destination the package COPY names, and
every harness that declares a harness.mk (and therefore gets a
build_<name> target) must ship an install script.
The Dockerfile's version build arg was named for opencode, the one
harness whose install script reads a pin, so any other harness growing
one would have meant a second one-off arg. The stage now declares a
single SWARMFORGE_HARNESS_VERSION and opencode's fragment maps its
existing user-facing OPENCODE_VERSION knob onto it, so
`make build_opencode OPENCODE_VERSION=1.4.14` behaves as before.

The build_opencode argv recording changes by exactly that one word:
the build-arg name.
The status line script and the image settings layer that turns it on
are Claude-only, so they belong beside Claude's install script in
swarmforge/harness/claude/ rather than in anvil/, which now holds only
the assets every image shares: the Dockerfile and the entrypoint. The
claude-runtime stage copies them from their new source paths to the
same image paths as before, so the entrypoint and the settings build
are untouched.
Three of the four <name>-runtime stages were empty and the fourth
existed only to bake in Claude's status line and settings defaults, so
every harness paid a Dockerfile stage for one harness's assets. A
single harness-runtime stage now runs the selected harness's optional
image.sh out of the copied package; claude ships one that installs the
status line and settings defaults to the same image paths the deleted
stage copied them to, and a harness without one adds nothing to its
image.

The build recipes retarget the new stage, changing each build_* argv
recording by one word: --target harness-runtime instead of the
per-harness stage name. Anyone hand-running docker build against a
<name>-runtime stage now names harness-runtime with the matching AGENT
build arg; the README build section covers it.

The status-line agreement tests read the install destinations out of
image.sh instead of Dockerfile COPY lines, and two new drift guards tie
image.sh to the package COPY destination from both ends: the Dockerfile
must look for it where the package lands, and its harness_dir literal
must name that same directory.
The layout tests pinned where the Dockerfile looks for a harness's
install.sh and image.sh but not that it runs them, that a missing
install script still exits the build, that the scripts carry their own
errexit (a fresh sh does not inherit the RUN's), or that the stage the
build recipes target exists at all -- every one of those regressions
kept the suite green while producing a binary-less image or four
failing builds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant