Put harness dispatch behind a registry - #49
Open
CrypticSwarm wants to merge 5 commits into
Open
Conversation
The argv tests assert the properties that carry a rationale -- guard mounts, tmpfs masks, env passthrough -- so a recipe edit that changes anything else passes silently. Record the complete launcher argv of every run_* target and the docker argv of every build_* target, with machine-specific words normalized to placeholders, and compare each target against its recording. - build_grok and build_codex had no argv coverage; they gain both the recording and the Dockerfile/context shape assertions. - The recordings live in tests/make_argv_fixtures.py and are never rewritten by the tests themselves.
The translator tests check individual rules -- a model rewrite here, a
dropped field there -- so the rendered output as a whole is unpinned:
field order, quoting, filenames, and the codex registration file could
all drift without a failure. Translate one source tree that exercises
every emitter, every per-harness override block, and the skip, drop,
and name-normalization branches, and compare every produced file byte
for byte against recordings under tests/translate_fixtures/.
The codex registration embeds the destination directory as an absolute
path, so the expected files spell it {DEST} and the test substitutes
the real one. Nothing rewrites the recordings automatically.
Each supported harness gets a module under swarmforge/harness/ holding its agent emitter and a HarnessSpec recording the facts the container acts on per harness: config destination and reset, layer excludes, asset destinations, MCP fragment shape and delivery, and chown paths. A static registry maps names to modules and replaces the name-keyed Python dispatch tables that had to be kept in sync by hand. Every field is mandatory; a harness opts out of one with a Waiver carrying the reason, so a declared gap (grok has no agent emitter and no agents destination) reads differently from a forgotten one. The translator keeps only the generic driver: emitters return the native filename and file text, and a finalize hook lets codex write its config.toml registrations without the driver special-casing the target. The rendering and frontmatter helpers the emitters share move to the leaf swarmforge/agents/emit.py; every previously public name stays importable from swarmforge.agents.translate. The codex command translator moves to swarmforge/harness/codex/commands.py with its old module path kept as the invocation shim the container runs.
The per-harness MCP behavior lived in two places the registry now covers: a hand-written emitter table in tongs/mcp.py and a harness-name branch in the launcher deciding between flag and env delivery. Both now read the harness spec, so the launcher no longer names any harness and a new harness brings its own fragment shape and delivery with it. MCP_EMITTERS and the mcp_config_* names remain as registry-derived views with their merged-set signatures; harnesses sharing a fragment share one emitter object, so the grok/codex identity holds.
The harness modules ship into the container image and are dispatched through by tongs and anvil; an import back into either would tie the package's two halves together and pull launcher-only code onto the image's import path. Pin the direction in the layering suite: no harness module reaches tongs or anvil, at import time or deferred into a function body -- the harness modules defer one import on purpose, so the ordinary cycle-breaking idiom must not wave the forbidden edge through. Companion checks prove the scans see the harness modules, the deferred shape, and the launcher edge into them.
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.
Adding a harness means finding every place that branches on its name: the agent translator's emitter table, the tong MCP emitter table kept in sync with it by a comment, and an
if harness == "claude"in the launcher's MCP delivery. This introducesswarmforge/harness/— one package per harness plus a static registry — and moves that dispatch behind it.HarnessSpecdescribes each harness as data: binary, config destination and reset policy, layer excludes, keyed files, asset destinations, MCP fragment/delivery/merge, agent emitter, and extra chown paths. Opting out of a capability takes an explicitWaiver(reason), so a harness that declares no agent translation (grok) is distinguishable from one that was forgotten.translate.main()becomes a generic driver over the registry;EMITTERS,MCP_EMITTERS, andHARNESS_OVERRIDE_KEYSare registry-derived views; the codex command translator moves toswarmforge/harness/codex/commands.pywith a shim at the old path. Harness modules sit below the launcher layers — they import only leaf helpers, and the layering tests enforce the new forbidden edges.The first two commits pin current behavior before anything moves: baseline snapshots of the full launcher argv for all four
run_*targets and the docker argv for all fourbuild_*targets, recorded through real make with a stubbed docker, plus recorded fixtures for every translator emitter and override block. Those recordings pass unchanged after the registry lands, andswarmforge/anvilandswarmforge/tongsno longer contain a harness-name branch.