feat: make AForge exec the default harness - #129
Merged
Conversation
The image previously pulled AForge out of a `ghcr.io/agent-field/aforge-v2` container image, which made the build depend on a private registry tag that does not exist yet. Replace that with a fetch stage that downloads the published, gzipped release binary for the build's TARGETARCH, decompresses it, and verifies the *decompressed* SHA-256 against the release `checksums.txt` before it is copied into the runtime image. A tampered or truncated download fails the build instead of shipping. `AFORGE_BASE_URL` and `AFORGE_VERSION` stay overridable as build args (and are plumbed through docker-compose) so CI or a local mirror can point elsewhere without editing the Dockerfile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The branch depended on an unmerged agentfield commit via a direct git reference, so `pip install sec-af` only worked with VCS access and `allow-direct-references` turned on. agentfield 0.1.129 is on PyPI and already ships the `aforge` harness provider plus the `aforge_bin` field on `HarnessConfig`, which is everything this repo uses today. Pin the floor there and drop the now-unnecessary `[tool.hatch.metadata] allow-direct-references`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two behaviours had no coverage: - `AuditConfig` (the orchestrator's runtime config) defaults to the `aforge` provider, not just `AIIntegrationConfig`. - The pinned agentfield floor actually accepts the harness settings `app.py` sends it. `aforge_bin` only exists on `HarnessConfig` from 0.1.129 onward, so lowering the floor now fails a test instead of failing at agent startup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ND reserved The docs claimed `AGENTFIELD_AFORGE_COMMAND` selects the AForge subcommand and that `do` is an available override. It does not: agentfield 0.1.129's aforge provider always runs `aforge exec`, so the variable is forwarded to the harness environment and ignored. Label it reserved everywhere it is documented rather than promise behaviour that does not exist yet. Also document `AFORGE_BASE_URL` / `AFORGE_VERSION` and state that the image installs a checksum-verified AForge at /usr/local/bin/aforge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aforge-v2 cut its first semver release (v0.1.0), so the AFORGE_VERSION default moves off the build-<sha> coordinate onto the tag. Bumping the string is what busts the fetch layer's cache, so this is what actually pulls the released binary instead of restoring the stale one. Moves all four places the coordinate is written: the Dockerfile ARG, the docker-compose build arg fallback, the .env.example hint, and the README build-arg table. The README's override example loses its build-<sha> placeholder shape at the same time, since versions are tags now. The AgentField SDK pin is deliberately left alone — it bumps on its own release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
agentfield 0.1.130 is the first release that ships the aforge harness provider as the default, so the runtime pin moves up with it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eld>=0.1.130 The "reserved / no-op" wording was written against 0.1.129, whose aforge provider hardcoded `exec`. 0.1.130 reads AGENTFIELD_AFORGE_COMMAND (default `exec`, `do` opts into the routed workflow), so describe the real behaviour. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AbirAbbas
marked this pull request as ready for review
August 18, 2026 02:28
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.
Summary
Makes AForge the default SEC-AF harness and runs it through
aforge exec, and makes that default actually installable:HARNESS_PROVIDER/AIIntegrationConfig.provider/AuditConfig.providerall default toaforge. OpenCode stays installed in the image and remains a configuration-only rollback (HARNESS_PROVIDER=opencode).ghcr.io/agent-field/aforge-v2:chat-v2-exec, a private tag that does not exist. The Dockerfile now has a fetch stage that downloads the published gzipped release for the build'sTARGETARCH, gunzips it, and verifies the decompressed SHA-256 against the releasechecksums.txtbefore installing it to/usr/local/bin/aforge. A bad download fails the build.agentfield @ git+…@bfd34426…#subdirectory=sdk/python→agentfield>=0.1.129in bothpyproject.tomland the Dockerfile. 0.1.129 ships theaforgeharness provider and theaforge_binfield onHarnessConfig, which is everything this repo uses.[tool.hatch.metadata] allow-direct-referencesis dropped along with the direct reference.AGENTFIELD_AFORGE_COMMANDwas documented as a subcommand selector withdoas an override. It is not one today (see below); it is now labelled reserved wherever it appears.Build arguments
AFORGE_BASE_URLhttps://agentfield.ai/downloads/aforgeAFORGE_VERSIONv0.1.0Both are plumbed through
docker-compose.ymlso a mirror can be pointed at without editing files.Known no-ops against agentfield 0.1.129
Verified by reading the installed 0.1.129 wheel and by running the resolution inside the built image:
AGENTFIELD_AFORGE_COMMANDis a no-op.agentfield/harness/providers/aforge.pyin 0.1.129 hardcodes[bin, "exec", "--json", "-w", root]. The variable is forwarded into the harness subprocess environment and ignored. It becomes meaningful only if/when the SDK exposes a command selector (feat(harness): make aforge the default provider across Python, Go, and TypeScript agentfield#905).SEC_AF_AFORGE_BIN/AFORGE_BINdo work today — contrary to the earlier assumption.HarnessConfig.aforge_binexists in 0.1.129 andbuild_providerpasses it toAforgeProvider(bin_path=…); SEC-AF reads both env names and forwards the value. Confirmed in-container:SEC_AF_AFORGE_BIN=/usr/local/bin/aforge→aforge_bin: /usr/local/bin/aforge.--timeoutis passed to aforge. 0.1.129's provider wraps the subprocess inAGENTFIELD_HARNESS_TIMEOUT_SECONDS(default 1800s) but passes no--timeoutflag, so aforge's own 15-minute wall is the effective per-call limit.When agentfield#905 is released, bump the floor in exactly two places:
pyproject.toml:14—"agentfield>=0.1.129",Dockerfile:54—"agentfield>=0.1.129" \(A floor bump is required, not merely permitted: Docker layer caching keys off the literal constraint string, so leaving
>=0.1.129will keep restoring the cached layer and shipping 0.1.129.)There is no Go node in this repo (
go.modcount: 0), so there is nosdk/gopseudo-version to carry.Validation contract
Observable behaviours this change must exhibit:
docker build .succeeds with no registry credentials — only the public download host is required.aforgebinary installed in the image is byte-identical to the published release: its SHA-256 equals theaforge-linux-<arch>line inchecksums.txt.linux/amd64andlinux/arm64resolve to the correct asset and verify.aforgeis onPATHinside the image andaforge --helpexits 0.aforge exec --jsonrun inside the image returns a single JSON envelope and exits 0.HARNESS_PROVIDER=opencodestill resolves to the OpenCode provider (rollback intact).SEC_AF_AFORGE_BINoverrides the binary path.python -m sec_af.appimports cleanly against the PyPI-pinned SDK.How verified
This repo has no CI, so local verification is the gate. Everything below was run against the committed tree; the build used a staging mirror via
--build-arg AFORGE_BASE_URL=…(productionagentfield.ai/downloads/aforge/…currently returns 404 — see "What un-drafts this").docker build --build-arg AFORGE_BASE_URL=<mirror> -t sec-af-aforge-test .→writing image sha256:0a3a629043aa… DONE61217a18135f392d50c337d971fa5b228f436f8df144800b37fbb340020c79f5 aforge-linux-amd64. In-image:sha256sum /usr/local/bin/aforge→61217a18135f392d50c337d971fa5b228f436f8df144800b37fbb340020c79f5— exact matchprintf "tamper" >> aforge→sha256sum: WARNING: 1 computed checksum did NOT match/aforge: FAILED/ rc=1docker build --target aforge --no-cache-filter aforge --build-arg TARGETARCH=arm64→+ arch=arm64…aforge: OK…DONE 1.5sdocker run --rm --entrypoint aforge sec-af-aforge-test --help→ usage text, exit 0;command -v aforge→/usr/local/bin/aforgeecho "Reply with exactly OK" | aforge exec --json -w /tmp/w --timeout 120 --budget 20000 --turns 3in-container →{"text":"OK","stop":"done","usage":{"calls":1,"prompt_tokens":3548,"completion_tokens":2,"cost":0.000279…},"artifacts":[],"turns":1,"elapsed_ms":3352}, exit 0{"agentfield_version":"0.1.129","HARNESS_PROVIDER_env":"aforge","resolved_provider":"aforge","AuditConfig_default_provider":"aforge","which_aforge":"/usr/local/bin/aforge","provider_class":"AforgeProvider"}-e HARNESS_PROVIDER=opencode→provider: opencode -> OpenCodeProvider-e SEC_AF_AFORGE_BIN=/usr/local/bin/aforge→aforge_bin: /usr/local/bin/aforgepython -c "import sec_af.app"in-container →app import OK; harness provider = aforge | aforge_bin = aforgepytest -q→2 failed, 105 passed; same 2 failures onorigin/main(2 failed, 103 passed)Also:
docker compose configresolves with the AForge defaults and honoursAFORGE_BASE_URL/AFORGE_VERSIONoverrides.Pre-existing red, not caused by this PR
origin/main(9a72858) already fails these two, both intests/test_scoring.pyand both from the same root cause (reachability defaults to the externally-reachable multiplier when the tag set is empty):test_compute_exploitability_score_defaults_reachability_when_missingtest_reachability_multipliers_and_default_behavior[tags5-2.5]They are unrelated to the harness change and are deliberately not fixed here — #128 fixes them and should keep the credit. Everything else is green: 105 passed on this branch vs 103 on main (the +2 are the new tests added here).
ruff check .reports 113 findings on this branch and 113 onorigin/main— identical, all pre-existing.mypy srcreports 48 errors on both. Neither is enforced by CI today and neither is touched here.Follow-ups
AGENTFIELD_AFORGE_COMMANDstops being decorative and an explicit--timeoutcan be passed.AGENTFIELD_HARNESS_TIMEOUT_SECONDSas a documented SEC-AF knob; today the effective per-call ceiling is aforge's internal 15-minute wall, which is shorter than the SDK's 1800s subprocess timeout.What un-drafts this
Two external preconditions, both outside this repo:
https://agentfield.ai/downloads/aforge/v0.1.0/{aforge-linux-amd64.gz,aforge-linux-arm64.gz,checksums.txt}must serve 200 (they return 404 today — the host is live and serves the olderbuild-<sha>coordinate, but the v0.1.0 assets have not been published to it yet). Once they do, a plaindocker build .with no build-arg overrides is the real test — re-run it, then mark ready.Precondition 1 alone is enough to un-draft; precondition 2 can follow as its own bump.
🤖 Generated with Claude Code