Skip to content

Put harness dispatch behind a registry - #49

Open
CrypticSwarm wants to merge 5 commits into
masterfrom
harness-locality-refactor
Open

Put harness dispatch behind a registry#49
CrypticSwarm wants to merge 5 commits into
masterfrom
harness-locality-refactor

Conversation

@CrypticSwarm

Copy link
Copy Markdown
Owner

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 introduces swarmforge/harness/ — one package per harness plus a static registry — and moves that dispatch behind it.

HarnessSpec describes 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 explicit Waiver(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, and HARNESS_OVERRIDE_KEYS are registry-derived views; the codex command translator moves to swarmforge/harness/codex/commands.py with 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 four build_* 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, and swarmforge/anvil and swarmforge/tongs no longer contain a harness-name branch.

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.
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