feat: make AForge exec the default harness - #72
Merged
Conversation
Both images pulled the AForge binary out of ghcr.io/agent-field/aforge-v2:chat-v2-exec. That package is private, so `docker build` failed with a 403 for anyone without registry access — including CI. Replace the image stage in the root (Python) and go/ Dockerfiles with a fetch stage that downloads the published, gzipped release asset over HTTPS, verifies the decompressed binary against the release checksums.txt, and chmod +x's it. AFORGE_BASE_URL and AFORGE_VERSION are build args, so CI or a local mirror can point the fetch elsewhere without editing the Dockerfile. Both runtime stages already install ca-certificates, which AForge needs to reach the model provider over HTTPS. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The branch pinned the SDK to a git commit on the adapter branch, which also forced hatchling's allow-direct-references escape hatch on. The AForge provider has shipped on PyPI since 0.1.127, so a normal version floor is enough: 0.1.129 registers the "aforge" provider and honours HarnessConfig.aforge_bin, which is all src/pr_af/app.py passes. Verified against the installed wheel: harness.providers._factory SUPPORTED_PROVIDERS contains "aforge" and builds AforgeProvider from config.aforge_bin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The branch bumped the sdk/go require but left the previous v0.1.127-rc.5.0.20260810165835-72f3d00baf58 hashes behind, so go.sum carried two versions of a module that only one require references. `go mod tidy` removes exactly those two lines; go.mod is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The branch dropped a prose paragraph into the middle of the env-var table in both READMEs, which truncates the rendered table at that point and leaves the remaining rows as literal pipe text. Move the prose below the table in each file, and drop the duplicate PR_AF_HARNESS_BIN row the branch added to go/README.md (the table already documented it further down). Also state where the AForge binary comes from now (checksum-verified release asset, pinned by AFORGE_VERSION), and scope AGENTFIELD_AFORGE_COMMAND honestly: the Go SDK adapter reads it, the Python SDK on PyPI always runs `exec`. 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. Both the Python and the Go image fetch the same coordinate, so both ARG defaults move together. The AgentField SDK pins are deliberately left alone — they bump on their own release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0.1.130 is the release that ships the AForge-default harness resolution both nodes rely on, so both pins move to it: - Python (pyproject.toml, Dockerfile): agentfield>=0.1.129 -> >=0.1.130. - Go (go/go.mod, go/go.sum): replace the v0.1.127-rc.5 pseudo-version — whose commit is not reachable from any branch — with the released module tag sdk/go/v0.1.130. go build/vet/test and gofmt are clean against the released module. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… write The runtime stage runs as praf but never set a WORKDIR, so the process cwd was root-owned `/`. The Go SDK creates its schema output dir under the cwd when a harness call carries no Cwd — planning, coverage, compound dedup, worthiness and the intake fallback all do — and every such call failed with `creating temp output dir: mkdir ./.agentfield-out-…: permission denied`, after the review had already spent its LLM budget. /workspaces is praf-owned and already PR_AF_WORKDIR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on 0.1.130 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
Make AForge
execthe default harness in both the Python and the maintained GoPR-AF nodes, ship the released AForge CLI in both images, and keep OpenCode
installed as a configuration-only rollback.
This revision replaces the two blockers that made the branch unbuildable:
FROM ghcr.io/agent-field/aforge-v2:chat-v2-exec AS aforge+COPY --from=aforge /aforge. That package is private, sodocker buildreturned a 403 for anyone without registry access — including the
docker-buildCI job. Both now use a fetch stage that downloads thepublished, gzipped release asset over HTTPS and verifies it against the
release
checksums.txtbefore installing it.pinned
agentfieldto a commit on the adapter branch (and turned onhatchling's
allow-direct-referencesto make that legal). The AForge providerhas shipped on PyPI since 0.1.127, so
agentfield>=0.1.129is enough.What changed
Docker (
Dockerfile,go/Dockerfile)Both
ARGs are overridable, so CI or a local mirror can serve the assets fromsomewhere else without editing the Dockerfile.
checksums.txtis copied verbatimfrom the aforge-v2 release and hashes the uncompressed binaries, so the
verification runs after
gunzip. Both runtime stages already installca-certificates, which AForge needs to reach the model provider over HTTPS.Python SDK pin —
pyproject.tomland the builder stage ofDockerfilenowuse
agentfield>=0.1.129; the[tool.hatch.metadata] allow-direct-referencesescape hatch (added only for the git dependency) is gone.
Go SDK pin —
go/go.modkeepsgithub.com/Agent-Field/agentfield/sdk/go v0.1.127-rc.5.0.20260815031530-bfd34426d1bd,which resolves through proxy.golang.org and is the only pin that carries the Go
AForge adapter today.
go mod tidyremoved the supersededv0.1.127-rc.5.0.20260810165835-72f3d00baf58hashes the branch had left ingo/go.sum.Docs — the branch dropped a prose paragraph into the middle of the env-var
table in
README.mdandgo/README.md, which truncates the rendered table andleaves the remaining rows as literal pipe text. Both tables are repaired (prose
moved below), and the duplicate
PR_AF_HARNESS_BINrow the branch added togo/README.mdis dropped.Known-inert knobs until agentfield#905 releases
The Python SDK on PyPI (0.1.129) and the pinned Go SDK do not behave
identically yet:
AGENTFIELD_AFORGE_COMMANDexec|do)aforge execAFORGE_BIN(SDK-level)PR_AF_AFORGE_BIN/AFORGE_BINstill work, becausesrc/pr_af/config.pyreads them and passesHarnessConfig.aforge_bin--timeoutpassed toaforge execThe docs in this PR state that scope rather than implying the Python node reads
those vars. When agentfield#905 ships as a tagged release, bump these exact
lines:
pyproject.toml:14—"agentfield>=0.1.129",→ the #905 release floorDockerfile:50—"agentfield>=0.1.129" \→ the same floor (the constraintstring itself must change, or Docker's layer cache keeps reinstalling 0.1.129)
go/go.mod:8— the sdk/go pseudo-version → the tagged release, thengo mod tidyValidation contract
Observable behaviours this change must exhibit:
docker build .succeeds without any GHCR credentials.aforgebinary inside both images is byte-identical to the publishedrelease asset for the target architecture.
checksums.txtfails the build rather thanshipping an unverified binary.
aforge --helpruns inside both images.aforge exec --json -w <dir>inside the image returns exit 0 and a JSONenvelope for a real prompt, given
OPENROUTER_API_KEY.PR_AF_PROVIDERset, both nodes resolve the harness provider toaforgeand point it at the bundled binary.PR_AF_PROVIDER=opencodestill resolves to the OpenCode provider, with theOpenCode CLI still present in both images (rollback without a rebuild).
go build/vet/test/gofmt.How it was verified
Locally, against a mirror of the published layout (the assets are not on
agentfield.ai yet — see below), overriding only
AFORGE_BASE_URL:docker build -t pr-af-aforge-test .anddocker build -f go/Dockerfile -t pr-af-go-aforge-test .— both exit 0.sha256sum /usr/local/bin/aforgein both images →61217a18135f392d50c337d971fa5b228f436f8df144800b37fbb340020c79f5, matchingthe
aforge-linux-amd64line in the releasechecksums.txt.--no-cache --target aforgeagainst a mirrorwhose
checksums.txthad the amd64 hash zeroed →sha256sum: WARNING: 1 computed checksum did NOT match/aforge: FAILED,build exit 1. The verification is not vacuous.
docker run --rm --entrypoint aforge <img> --help→ exit 0 in bothimages.
echo "Reply with exactly OK" | aforge exec --json -w /tmp/w --timeout 120 --budget 20000 --turns 3inside both images →
{"text":"OK","stop":"done","usage":{...},"artifacts":[],"turns":1,...},exit 0.
pr_af.config.AIIntegrationConfig.from_env()→provider = aforge;agentfield.harness.providers._factory.build_provider→
AforgeProvider, binaforge -> /usr/local/bin/aforge;opencode -> /home/praf/.opencode/bin/opencodestill present. WithPR_AF_PROVIDER=opencode→OpenCodeProvider.config.AIConfigFromEnv()+node.BuildAgent(...)+harness.BuildProvider→resolved provider = aforge,*harness.AforgeProvider, binaforge -> /usr/local/bin/aforge;with
PR_AF_PROVIDER=opencode→*harness.OpenCodeProvider.ruff check src/ scripts/→ all checks passed.pytest→ 107 passed.In
go/:go build ./...,go vet ./...,go test ./...(all packages ok),gofmt -l .empty.CI expectation —
docker-buildstays red for nowhttps://agentfield.ai/downloads/aforge/v0.1.0/currently returns 404. Thehost itself is live and already serves the older
build-<sha>coordinate — whatis missing is the v0.1.0 publish. Until it lands, the default
AFORGE_BASE_URLhas nothing to fetch and the
docker-buildjob will fail on the aforge stage.lintandgoshould be green.Stays in draft until
https://agentfield.ai/downloads/aforge/<version>/servesaforge-linux-{amd64,arm64}.gz+checksums.txt, anddocker-buildgoesgreen on a re-run with no build args.
are bumped so
AGENTFIELD_AFORGE_COMMAND, SDK-levelAFORGE_BINand the--timeoutpass-through stop being inert on the Python node.Both are external; nothing else in this PR is waiting on review feedback.
Follow-ups (not in this PR)
af install(non-Docker) path too — today onlythe images bundle it, so a bare-metal
af runneedsaforgeonPATH.--timeoutderived fromPR_AF_MAX_DURATION_SECONDS.🤖 Generated with Claude Code