Run the container-side python as package modules - #34
Merged
Conversation
The translator and the opencode.json merger were flat files copied into /usr/local/lib/swarmforge next to the package they already imported, which left the image with two notions of where Swarmforge's python lives. Move both into the package -- swarmforge/agents/translate.py and swarmforge/config/merge_opencode.py -- so the Dockerfile copies one tree and the entrypoint runs modules out of it. `python3 -m` resolves imports differently from `python3 <script>`: it puts the working directory first on sys.path instead of the script's directory. The entrypoint's working directory is the workspace and these run as root, before privileges are dropped, so a repository carrying its own swarmforge/ would have shadowed the image's copy and been executed. -P restores the old behaviour of never consulting the working directory. The image-layout tests followed the same seam. They had pinned the entrypoint to a literal copy destination; now they check the properties that actually have to hold -- every module the entrypoint runs ships in the package, names the import root the package lands in, and keeps the workspace off sys.path -- and they exercise the merger through the staged layout the way they already exercised the translator.
The README, the contributor notes, the tongs docstring, and the dockerignore header all described anvil/ as holding the unified-agent translator and swarmforge/ as a package the container-side scripts import. There are no container-side scripts any more: the entrypoint runs the package directly, and anvil/ is down to the Dockerfile and the entrypoint itself.
The image-layout tests checked the entrypoint's translator guard and its `python3 -m` runs independently, so a guard probing one module while the run named another passed. Derive the module the guard covers from its path and require the entrypoint to actually run it. Three narrower holes alongside it. The staged runs did not pass -P, so they did not execute the flag set the entrypoint uses. -P itself needs python 3.11 and the image builds its own interpreter from a pinned version, with nothing tying the two together -- on an older pin the merge would exit on an unknown option and take the container with it. And that merge's --replace-mcp-entries argv, the one with no fallback behind it, was never exercised end to end. The invocation scan also read comments, so prose about running a module counted as running one.
Three of the guards were looser than they read. The translator guard only had to name some module the entrypoint runs, so pointing it at the config merge left translation unguarded and the suite green; scope it to the runs inside the function the guard sits in. The python pin was read with a single search, which misses a stage redeclaring the arg with its own default -- and the stage that compiles the interpreter is not the one carrying the global default. And stripping whole comment lines left a trailing comment able to forge an invocation. The staged runs now pass -P only when the host python has it. The image's always does, but these tests run on whatever the contributor has, and the staging dir has no swarmforge/ of its own for the working directory to resolve through.
…nto-package
Conflicts:
AGENTS.md
- Adjacent lines conflicted: the `anvil/` bullet and the `swarmforge/`
bullet, for different reasons.
- Master added the Claude status line and its seeder to `anvil/`'s
contents while still listing the unified-agent translator, which
this branch moved into the package -> kept master's statusline.sh
and seeder, dropped the translator.
- Master describes the package as what the container-side scripts
import, this branch as everything the entrypoint runs with
`python3 -m` -> narrowed to agent translation and config merging,
since master's seeder still runs as a script.
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.
Stacked on
swarmforge-package-skeleton.The agent translator and the
opencode.jsonmerger were flat files copied into/usr/local/lib/swarmforge, sitting next to theswarmforgepackage they already imported. That left the image with two notions of where Swarmforge's python lives — one of them a directory whose whole purpose was "files that getCOPY'd in".Both move into the package:
anvil/translate_agents.py→swarmforge/agents/translate.pyanvil/merge_opencode_json.py→swarmforge/config/merge_opencode.pyThe Dockerfile drops its two flat
COPYlines and keeps only the package copy; the entrypoint runspython3 -P -m swarmforge.agents.translateandpython3 -P -m swarmforge.config.merge_opencodewith the package's parent as the import root.anvil/is now just the Dockerfile and the entrypoint.Both modules are otherwise untouched — the only edits inside them are the usage string and the
warn()prefix, which named files that no longer exist. Nothing reads or parses either string.Why
-Ppython3 -mdoes not resolve imports the waypython3 <script>does: it puts the working directory first onsys.path, where the script form put the script's own directory. The entrypoint's working directory is/workspace, and these run as root, before privileges are dropped. Without-P, a repository that happens to carry its ownswarmforge/would shadow the image's copy and have it executed.-Psuppresses that entry, which restores exactly what the script form did. The image builds CPython 3.12.7, so the flag is available.Tests
The image-layout tests had pinned the entrypoint to literal copy destinations, which is the wrong thing to assert once the layout is a package. They now check the properties that actually have to hold, each verified to fail when its subject regresses:
sys.path, and the pinned interpreter is new enough for the flag that guarantees it,[ -f ]guard covers the module its own function runs,sys.pathand off the working directory.The merger gained coverage it never had here, including its
--replace-mcp-entriesargv — the tong MCP merge, which has no|| continuebehind it, so a rejected argument is a container that never starts rather than a degraded merge.Suite goes 478 → 483, all green. The passthrough and launch-path invariant tests are untouched and pass.