Skip to content

Add a swarmforge package and move the YAML parser into it - #30

Closed
CrypticSwarm wants to merge 7 commits into
masterfrom
swarmforge-package-skeleton
Closed

Add a swarmforge package and move the YAML parser into it#30
CrypticSwarm wants to merge 7 commits into
masterfrom
swarmforge-package-skeleton

Conversation

@CrypticSwarm

Copy link
Copy Markdown
Owner

Why

scripts/tongs.py is a host-side launcher, and it loads anvil/translate_agents.py by file path — a module whose whole directory exists to be COPY'd into the harness image. It does that to borrow one function, parse_map. The layering runs backwards, and the price is a spec_from_file_location block that nothing can type-check, lint for import errors, or mock through ordinary means.

The parser is the genuinely shared piece. It reads the nested-map / flat-list subset that agent frontmatter and tong definitions both use, and it only lives in the translator because that is where it was first needed.

What changed

  • A swarmforge/ package at the repo root for code shared across the container boundary, with a pyproject.toml so editors and linters can resolve the imports. dependencies is empty and meant to stay that way: the image installs no third-party Python and the launcher runs on whatever python3 the host has.
  • parse_map and its helpers move verbatim to swarmforge/yamlite.py — a leaf module that imports nothing else, so either side of the boundary can depend on it alone. The translator imports them back; the launcher imports them directly and drops its path load.
  • The images now build from the repository root with -f anvil/Dockerfile. A build whose context is anvil/ cannot see the package — COPY has no way to reach a sibling directory. A .dockerignore admits only the two directories the Dockerfile reads from, so a root context does not sweep in ollama state or checkouts.
  • The unit suite runs with the repo root on PYTHONPATH rather than relying on make's working directory, and scripts/test_translate_agents.py stands in for that itself so it still runs standalone the way its docstring says.

Behavior

None intended. The moved functions are byte-identical to their originals, the translator's remaining code is untouched apart from the import, and inside the image every path is where it was — only the COPY sources gained an anvil/ prefix.

The launcher's docker run argv is unchanged, enforced by the passthrough tests in scripts/test_run_anvil.py and the recipe-driven tests in scripts/test_run_agent_container.py; both are untouched and green. The suite went from 473 tests to 478.

New coverage

The build recipes had no tests at all, which is uncomfortable now that the Dockerfile and the build context have to agree. scripts/test_image_layout.py adds three things: the argv the recipes assemble, a check that the Dockerfile's copy destination and the entrypoint's import root still name the same directory, and a run of the translator against the image's directory shape with the checkout deliberately off the path — so a translator that only imports because someone ran it from the repo does not pass.

Not verified here

make build_opencode and a no-tongs make run_opencode were not run: docker is not available in the environment this was written in. The tests above cover the wiring, but the real build is worth doing before merge.

Base automatically changed from refactor-test-discovery to master August 4, 2026 04:07
scripts/ holds flat modules that load each other by file path, and the
one genuinely shared primitive -- the YAML subset parser -- lives in
anvil/, the directory whose whole purpose is "files COPY'd into the
image". So the host launcher imports out of the build context to borrow
it, and nothing can be type-checked, linted for import errors, or mocked
through ordinary means.

Add an empty package at the repo root to hold that shared code. Both
sides can reach it: the launcher imports it from the checkout, and the
image copies it in.

pyproject.toml exists so editors, type checkers and linters can resolve
the imports; it is not a deployment mechanism, and its dependency list
is empty and meant to stay that way. The harness image installs no
third-party Python and the launcher runs on whatever python3 the host
provides, so stdlib-only is a constraint, not a preference.
The parser is not part of agent translation. It reads the nested-map and
flat-list subset that agent frontmatter and tong definitions both use,
and it only lives in the translator because that is where it was first
needed -- which is why the host launcher reaches into the image's build
context to get at it.

Move parse_map and its helpers to swarmforge/yamlite.py verbatim and
import them back. It is a leaf module: it imports nothing else from the
package, so either side of the container boundary can depend on it
without dragging the rest along.
The launcher loaded anvil/translate_agents.py by file path purely to
borrow parse_map, so a host-side module depended on a container-side one
and paid for it with a spec_from_file_location block.

Import swarmforge.yamlite instead. The launcher runs out of a checkout
rather than an installed distribution, so it still has to put the repo
root on sys.path first -- but that is one line in one place, and it
prepends so the checkout's own package wins over any stale copy
installed on the host.
The container-side translator now imports swarmforge.yamlite, and the
tests load it straight off disk, so the suite only passed because make
happened to run from the repo root and put it on sys.path. Say it
outright with PYTHONPATH instead of relying on the working directory --
which is also how the image supplies the same import root.
The translator now imports the shared package, and a build whose context
is anvil/ cannot see it -- COPY has no way to reach a sibling directory.
Build with an explicit `-f anvil/Dockerfile` from the root instead and
copy the package in beside the scripts that import it, at the import
root the entrypoint hands the translator.

A root context would otherwise sweep in ollama state, checkouts and
editor droppings, so .dockerignore admits only the two directories the
Dockerfile reads from; everything else would slow the build and bust its
cache for unrelated reasons.

The build recipes had no test coverage at all, which is uncomfortable
now that the Dockerfile and the context have to agree. Two go in: one on
the argv the recipes assemble, one that stages the image's layout and
translates an agent through it with the checkout off the path, so a
translator that only imports because someone ran it from the repo does
not pass.
scripts/test_translate_agents.py loads anvil/translate_agents.py off
disk, and that module now imports the shared package. Run under the make
target it works, because the target puts the repo root on PYTHONPATH;
run the way its own docstring says to -- and the way every other test
file in scripts/ still can -- it died with ModuleNotFoundError.

Stand in for the image's PYTHONPATH in the test's loader preamble, the
same one line tongs.py uses. No test body changes.

Two more, found alongside: run_anvil.py described its path loading as
"the same way tongs.py loads translate_agents.py", which is exactly the
block this branch deleted. And the Dockerfile's copy destination and the
entrypoint's import root are two strings in two files with nothing tying
them together -- a mismatch does not fail the build, it just stops
translating agents at runtime, so assert they agree.
The PYTHONPATH check matched the string anywhere in the entrypoint, so
moving it off the translator invocation onto some other line left the
test green. Anchor it to the invocation.

The Dockerfile reader also dropped any COPY carrying a flag, and the
lookup that followed then failed with a bare KeyError. Skip flag words
and say which COPY line is missing.
@CrypticSwarm
CrypticSwarm force-pushed the swarmforge-package-skeleton branch from 5ef7906 to 859f68b Compare August 4, 2026 04:07
@CrypticSwarm
CrypticSwarm deleted the swarmforge-package-skeleton branch August 4, 2026 04:10
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