Build each harness from its package directory - #51
Open
CrypticSwarm wants to merge 5 commits into
Open
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Dockerfile installed every harness from one
case "${AGENT}"and kept four near-identical<name>-runtimestages. Each harness's install now lives inswarmforge/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 singleharness-runtimestage that runs the harness'simage.shwhen one exists. Claude is the only harness with one: it installs the status line and default settings at their existing image paths, andstatusline.sh/claude-settings.jsonmove next to it underswarmforge/harness/claude/.ARG OPENCODE_VERSIONgeneralizes toARG SWARMFORGE_HARNESS_VERSION, with opencode's make fragment mapping the unchangedOPENCODE_VERSIONuser knob onto it. The build argv baselines change in exactly those two ways — the--targetword and the build-arg name; run argv is untouched. Invokingdocker build --target claude-runtimeby 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'
--targetnames a declared stage.