Make installing the repo give you the Go node as swe-planner, with the bundled coding engine as an opt-in preview - #122
Open
AbirAbbas wants to merge 33 commits into
Open
Make installing the repo give you the Go node as swe-planner, with the bundled coding engine as an opt-in preview#122AbirAbbas wants to merge 33 commits into
AbirAbbas wants to merge 33 commits into
Conversation
…adapter internal/pro runs a prebuilt coding-engine binary as a supervised sidecar (SWE_PRO_ENGINE gate, env translation, crash backoff, parent-death cleanup) and adapts the execute_fn_target contract onto the engine's task interface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pro_execute registers on swe-planner-go only when the opt-in flag is set, so the default surface (and its Python-parity test) is unchanged. Adds a gated-surface test and pins the flag off in the exact-surface test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Opt-in via SWE_PRO_ENGINE; the sidecar registers its own node on the same control plane and is stopped with a bounded wait when Run returns. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SWE_PRO_MODELS_HIGH / SWE_PRO_MODELS_LOW / SWE_PRO_VARIANT forward the engine's sub-agent model pools and reasoning-effort variant per dispatch, alongside the existing SWE_PRO_MAX_COST. Unset keeps engine defaults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With the opt-in flag set, build/execute requests that name no execute_fn_target route per-issue coding through pro_execute, with a per-run note; explicit targets always win and the default path is byte-identical with the flag unset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Startup log states the engine is enabled, how coding routes, and how to return to the classic engine; docs cover the opt-in surface and rollout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The black-box parity lists were not updated when implement_issue landed on both nodes: the live surface is 31/30, the test expected 30/29 and failed on main. Pre-existing; surfaced while verifying this branch's default surface is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 4, 2026
AbirAbbas
force-pushed
the
release/pro-engine-optin
branch
from
August 4, 2026 16:34
04109f8 to
7503b29
Compare
The image ships the engine at /usr/local/bin/swe-pro so an opted-in node finds it with no extra setup. It is inert unless SWE_PRO_ENGINE is set. Engine source c4f3fbe — requires a reachable control plane at startup. That build carries Agent-Field/swe-pro-go#19, which stops the engine attributing the commits it authors to a hardcoded internal identity. They now default to SWE-AF <swe-af@users.noreply.github.com> and follow SWE_AF_GIT_NAME / SWE_AF_GIT_EMAIL, matching the node's own commits. This only changed behaviour outside the container images: those export GIT_AUTHOR_* / GIT_COMMITTER_*, which git ranks above the engine's `-c user.name` overrides, so a host install was the exposed path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…y runtime note Battle-testing the image surfaced two gaps: Python repos cannot produce test evidence in-container (no pytest), which fails evidence-based verification of correct work; and OpenRouter-only deployments inherit the compose default SWE_DEFAULT_RUNTIME=claude_code, silently degrading advisory/verification roles that then have no credential. Adds the test tooling to the image and documents the runtime setting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move GH_TOKEN from user_environment.required to optional in both node manifests (Python swe-planner and Go swe-planner-go): the af install / af run env gate no longer refuses to start without it. Builds on local or public repos run end-to-end with just an OpenRouter (or Anthropic) key — the PR phase already degrades to a logged note when push/PR auth is missing. GH_TOKEN is still offered at setup for private repos, pushing branches and opening pull requests. Docs updated to match, and the Go README now documents the runtime auto-select (open_code when only an OpenRouter key is present). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docker compose was baking SWE_DEFAULT_RUNTIME=claude_code whenever the var was unset, which broke OpenRouter-only deployments: the in-code auto-selection (open_code when only an OpenRouter key is present) never got a chance to run. Pass the empty string through instead — both the Python and Go nodes treat blank as unset. Also default the swe-fast secret vars to empty so compose does not warn about unset ANTHROPIC / CLAUDE_CODE_OAUTH_TOKEN / GH_TOKEN vars that are genuinely optional. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follows the compose change that stopped forcing claude_code: the
expected line is SWE_DEFAULT_RUNTIME=${SWE_DEFAULT_RUNTIME:-} so the
in-code auto-selection (open_code when only an OpenRouter key is
present) decides the runtime.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ssing SWE_PRO_ENGINE=1 with no engine binary used to register pro_execute and default-route every issue to a sidecar node that never joins the control plane, burning every retry per issue. Registration and default routing now gate on pro.Available() (flag AND binary present), and ResolveBin() also searches next to the running executable — the layout an af install checkout produces (bin/swe-planner and bin/swe-pro side by side) — so the opt-in works outside the Docker image too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mode Three gaps broke the OpenRouter-key-only contract: - Explicitly setting SWE_DEFAULT_RUNTIME=open_code opted OUT of the auto-selected deepseek-v4-flash and silently fell back to minimax-m2.5. The runtime base and the auto default are now the same model, so opting in explicitly resolves identically. - Fast mode had no OpenRouter auto-detect: with only an OpenRouter key it resolved claude_code/haiku and could not run at all. DefaultFastRuntime now uses the same detection as the main path. - Fast mode ignored SWE_DEFAULT_MODEL / AI_MODEL / HARNESS_MODEL entirely. FastResolveModels now applies the same env cascade, below caller-supplied models. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Python side of the same contract repair as the Go node, kept in lockstep: the open_code runtime base is now the shared OpenRouter default (so an explicit SWE_DEFAULT_RUNTIME=open_code no longer swaps the model), fast builds auto-select open_code when an OpenRouter key is the only credential, and fast model resolution honors the SWE_DEFAULT_MODEL / AI_MODEL / HARNESS_MODEL cascade below caller-supplied models. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
.env.example shipped ANTHROPIC_API_KEY and GH_TOKEN uncommented with placeholder values. Since any non-empty ANTHROPIC_API_KEY disables the OpenRouter auto-select, the documented `cp .env.example .env` quick start forced the claude_code runtime with a bogus key — and the GH_TOKEN placeholder made git and gh attempt (and fail) authentication instead of degrading to the no-token path. Both are now commented out, with OpenRouter presented first as the single key needed to get started. Also surfaces the coding-engine opt-in where users look for it: the go manifest gains SWE_PRO_ENGINE/VARIANT/MAX_COST, the root manifest gains the SWE_DEFAULT_RUNTIME knob it was missing next to SWE_DEFAULT_MODEL, and .env.example documents the engine env surface and warns that AI_MODEL also repoints every agent role. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three gaps, all breaking the shell-exported-key workflow or the opt-in: - swe-agent (root and local compose) relied entirely on env_file: .env for provider keys, so a user who exported OPENROUTER_API_KEY in their shell got a working swe-fast but a keyless swe-planner. Both now pass the same keys through as swe-fast already did. - docker-compose.go.yml declared no SWE_PRO_* variables at all, so the only way to opt in was writing SWE_PRO_ENGINE into .env and relying on blanket env_file passthrough. It now passes the engine vars, defaulting the sidecar's public URL to a container-reachable address instead of the engine's own http://localhost:8801. - compose.functional.yml still baked SWE_DEFAULT_RUNTIME=claude_code, which the earlier unforcing sweep missed; it now passes through empty like the three deployment compose files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two statements went stale one commit after they were written. The doc said the compose files force SWE_DEFAULT_RUNTIME=claude_code (untrue since the unforcing commit) and told OpenRouter-only users to set SWE_DEFAULT_RUNTIME=open_code — advice that opts out of the auto-selected default and was silently downgrading the model. It also described SWE_PRO_PUBLIC_URL as 'derived' when nothing derives it; unset means the engine advertises localhost, which a control-plane container cannot reach. Also documents the missing-binary degradation and the binary search order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The docs still described the pre-auto-select world: Railway said to set two variables and presented GH_TOKEN as required, deployment.md listed Anthropic as the required key with open models as an 'alternative', and the runtime defaults table claimed open_code resolves to minimax-m2.5. All three contradict what the code now does. Also gives the opt-in coding engine a discovery path — go/docs/pro-engine.md previously had zero inbound links from anywhere — and documents the SWE_PRO_* knobs in the Go node's env table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
os.Stat succeeds on a file that has lost its execute bit, so a binary copied without its mode looked available: pro_execute was registered and every issue was routed to an engine node whose process could never start, crash-looping on 'permission denied' until the supervisor gave up. That is strictly worse than not opting in. Availability now requires a regular file with an execute bit, so the not-runnable case takes the same graceful path as the missing case, and the warning names the offending path instead of the default one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fast mode repeats the model id rather than importing it (a module-scope import would be circular), so the two copies could diverge with every existing test still passing. Assert they are equal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The engine does an issue's coding in one long call, so a control plane that reaps by time-since-last-activity can report a healthy build as timed out while the engine is still working. Records the fix and the workaround for older control planes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Go node in go/ is the maintained SWE node — same reasoners, same interface, one static binary, and it ships the coding engine. Until now getting it meant knowing to append //go, and anyone who installed this repo earlier stayed on the Python node with no way across. Declare the root manifest superseded by the Go node, so `af install https://github.com/Agent-Field/SWE-AF` installs it and replaces an existing swe-planner. That URL is now the only thing a user has to know, before and after the switch. The Python node is unchanged and still what `python -m swe_af` and the Docker images run; the manifest stays here as the redirect. Requires an `af` that understands superseded_by — older ones simply install the Python node as before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Go node carried -go suffixes on its package name, node ids, and reasoner tags. That existed so it could run beside the Python node on one control plane while the port was in progress; it is not something a user should ever see. This is the SWE node now, so it takes the product's names — the same ones the Python node uses and the same ones callers already have in their triggers. Nothing about a build changes when the implementation underneath it does. The package name matching the root manifest is deliberate: the root declares itself superseded_by this directory, so installing the repo replaces an existing swe-planner in place rather than leaving two half-equivalent SWE nodes installed side by side. Side-by-side is still available where it is genuinely useful — running both implementations against one control plane to compare them — but it is now an explicit NODE_ID override rather than the default, and docker-compose.go.yml is the one place that does it. The functional stack brings up its own control plane with no Python node on it, so it moves to the defaults like everything else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AbirAbbas
force-pushed
the
release/pro-engine-optin
branch
from
August 4, 2026 19:01
7503b29 to
b1a369a
Compare
…exec
The vendored engine was a single linux/amd64 binary at go/bin/swe-pro, and
ResolveBin's sibling search looked for exactly that name. On macOS an
`af install` of this repo therefore put a Linux ELF next to swe-planner,
resolved it as the engine, and the supervisor crash-looped on
fork/exec .../bin/swe-pro: exec format error
until it gave up — with SWE_PRO_ENGINE=1 set and pro_execute registered,
because the binary was present and executable and so passed every gate.
macOS is where this node is developed and demoed, so the opt-in preview was
unreachable on the platform most likely to try it.
One repo has to serve both platforms, and there is no way to tell two builds
apart under one name. go/bin now carries swe-pro-darwin-arm64 and
swe-pro-linux-amd64, and the sibling search tries swe-pro-<GOOS>-<GOARCH>
first, falling back to plain swe-pro for layouts that place a single
hand-built engine beside the node. SWE_PRO_BIN stays authoritative and
/usr/local/bin/swe-pro stays the default: the image is one platform, so the
Dockerfile copies its own swe-pro-linux-amd64 to that path and nothing about
the container layout changes.
Both binaries are built with CGO_ENABLED=0 -trimpath -ldflags="-s -w" from
swe-pro-go 609ae34a ("fix(codeaf): build on darwin, where stat ctime is
named Ctimespec", the fix/darwin-build branch rebased onto main 93743e4,
which includes c4f3fbe — the rev the previous go/bin/swe-pro was built
from). That commit is the whole reason a darwin build exists: the engine
read the Linux-only Ctim field off syscall.Stat_t and would not compile for
GOOS=darwin.
bin/ is now a versioned directory, so go/.gitignore ignores the unsuffixed
swe-planner / swe-fast / swe-pro names a local build or the installer's build
step drops there, and the suffixed engine builds stay tracked.
Verified on macOS arm64 end to end: af install from this branch, control
plane + node up, SWE_PRO_ENGINE=1 spawns the darwin sidecar, engine node
swe-pro registers (code_task/code_resume), and a capped code_task produced
and committed the requested file.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An OpenRouter-only install resolves every role model to config.openRouterAutoDefaultModel = "openrouter/deepseek/deepseek-v4-flash". That id is correct — it is LiteLLM-style, which is the currency of this repo's config and exactly what the open_code harness runtime wants. But run_qa_synthesizer does not go through a harness: it calls the direct client (Deps.AI), which talks to OpenRouter's OpenAI-compatible endpoint, and that API names the same model "deepseek/deepseek-v4-flash". Handed the routing prefix it returns a 400. mapSynthModel passed any id containing "/" straight through, so the prefix survived to the wire. The synthesizer then took its "any failure" branch and returned the deterministic fallback — Synthesizer failed — defaulting to FIX. QA passed=True, review approved=True. — which reads like a QA verdict but is really a swallowed transport error. Every iteration on an OpenRouter-only install was being pushed back to FIX with no model ever consulted. Translate at the boundary that owns the wire format: when the direct client targets OpenRouter, strip a leading "openrouter/". openRouterAutoDefaultModel is untouched — the harness path needs the prefix and is not affected — and the strip is conditional on IsOpenRouter() so a LiteLLM-style proxy behind AI_BASE_URL still receives the prefix it routes on. run_qa_synthesizer remains the only direct-LLM consumer: deps.AI.AI( and ai.WithModel each appear exactly once in the module, both in this file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Product decision: the engine is the coding path users should get, so an
`af install` node should run it without anyone reading a doc first. Flipping
the polarity does not need a code change — the af env resolver resolves each
declared user_environment var as process env → node secret → global secret →
manifest default, then appends the result to the node process env. Declaring
default: "1"
on the existing optional SWE_PRO_ENGINE entry is therefore enough: installed
nodes get the engine, and because the process env still wins,
`SWE_PRO_ENGINE=0 af run swe-planner` still opts out. The entry stays optional
so nothing prompts.
pro.Enabled() already whitelists "1"/"true"/"yes"/"on" and treats everything
else as off, so "0" and "false" disable it as required — no parsing change.
That contract now matters far more than it did (a value is always present on an
installed node, so opting out can only mean writing a falsy one), so
TestEnabledOptOutContract pins all four corners including a genuinely unset
variable, which must stay off: a bare binary launched with no manifest has no
business starting a sidecar.
Wording follows the new reality. The startup line no longer calls this a
preview or promises a future default:
pro engine enabled: engine node "swe-pro" joins the control plane; builds
route per-issue coding through it. Set SWE_PRO_ENGINE=0 to use the classic
coding loop instead.
The per-run routing note, the pro_execute description, docs/pro-engine.md,
go/README.md and the .env.example block get the same treatment, and
docker-compose.go.yml defaults to ${SWE_PRO_ENGINE:-1} to match the manifest.
The Dockerfile deliberately sets no ENV: the manifest and compose are the
controlled runtime surfaces, and an image that hardcoded the flag would take
the choice away from whatever runs it.
Also de-flakes TestSupervisorStopsOnCancel, which failed this change's
acceptance run twice. It slept a fixed 300ms before cancelling, so under
whole-module load it could cancel before the sidecar had printed, and it read a
strings.Builder that the pipeLines goroutines were concurrently writing — a
real data race, not just a timing guess. It now polls a mutex-guarded buffer
until the line lands. `go test ./... -count=1` and `go test ./internal/pro/
-race -count=2` are both clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The on-by-default decision is scoped to `af install`, where the manifest injects SWE_PRO_ENGINE=1 and `af secrets`/env give the user an opt-out. A checkout deployed with docker compose is production infrastructure someone configured deliberately; defaulting the engine on there changes running systems on a pull. Compose goes back to passing SWE_PRO_ENGINE through unset, which the binary treats as off. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cation fix Picks up swe-pro-go 7cab26990feb16133cfce6ec1bee37b6fb34e6e8, "fix(fullverification): stop demanding a test suite from a workspace with no project in it". The engine's full-project verification gate demanded a test entrypoint from every workspace, while the sibling build gate had always been conditional. On a repository with no project in it — a `git init` carrying a README, which is exactly what a first `code_task` against a fresh directory looks like — both demands are unsatisfiable, so the acceptance contract would pass, verification would fail on "no standard test entrypoint was discoverable", and the audit-fix loop plus the auto-resume supervisor would repair that non-defect until the run hit its cost ceiling. The deliverable was already written and committed seconds in. Both gates now stand down for a workspace carrying no language manifest, no build system and no test suite, and the verification section says outright that it proved nothing so the audit stage is not left reading a vacuous pass as a green one. Anything that looks like a project is held to the same floor as before, undiscoverable test command included. Both binaries: CGO_ENABLED=0, -trimpath -ldflags="-s -w", mode 755. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ct fix Picks up swe-pro-go 9336a0b7a14c362a748e887e55b10f0b574279c5, "fix(contract): separate the files a check lives in from the files it asserts about" (swe-pro-go issue #22). The engine's base contract check copies a contract's registered `paths` into a detached checkout of the base commit so it can answer whether the bug reproduced before the run started. `paths` means "the files the check lives in", but nothing enforced that, and a coder that also listed its deliverable had the deliverable copied into the base checkout — so the check passed there, the engine concluded the work was already done, re-scoped the task to "report + regression test, no behavioral change", then deadlocked on done-criteria against the deliverable it had just cancelled. On a real bug fix the same path discards the fix. The contract schema now separates `asserted_paths` (what the check asserts about — never copied, even when also listed in `paths`) from `paths` (the test files — still copied), and the staleness adjudication is told whether each copied file existed at base so a non-compliant contract is recognized rather than believed. Both binaries: CGO_ENABLED=0, -trimpath -ldflags="-s -w", mode 755. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e fix Picks up swe-pro-go c361467fbebb300d3f52309bb29121a901a18266, "fix(auditorgate): let a passing acceptance contract satisfy done-criteria evidence" (swe-pro-go issue #23). The engine's audit stage refused to trust a passing auditor verdict unless it could see fresh test evidence, and it recognized test evidence only by matching step-2 commands against ecosystem runners (`go test`, `pytest`, `npm test`, …). A repo with no test framework gets the bespoke acceptance script the contract protocol asks for instead, so the check saw no evidence, flipped a correct pass to fail with "done-criteria evidence incomplete", and looped audit-fix cycles across resume forever — no edit can make a shell script spell itself `pytest`. Two runs ended that way with the requested file already correct on disk. The registered acceptance contract now counts as that evidence. It is stronger than the regex it replaces: the harness runs it itself each cycle, and the base-contract check requires it to have failed at the base commit. Scoped to the criteria check only, so a criterion the evidence genuinely does not meet still fails the audit. Both binaries: CGO_ENABLED=0, -trimpath -ldflags="-s -w", mode 755. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… fix Picks up swe-pro-go 0cefff72f3d3b21703f335eed5e4e9669ad18f3d, "fix(auditorgate): an empty diff is only a pass when the run delivered something" (swe-pro-go issue #24). The engine's audit gate skipped auditing and terminated a run as PASS on "no changes to audit" without ever checking whether the run had produced anything. A leaf whose coder wrote neither code nor an acceptance contract failed cycle 1 correctly, resumed, and then passed on that skip with no deliverable on disk. An empty diff now passes only when an acceptance contract passes or an audit already accepted the tree; otherwise it fails with an actionable verdict so the audit-fix loop re-dispatches within its existing cycle and budget caps. Both binaries: CGO_ENABLED=0, -trimpath -ldflags="-s -w", mode 755. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AbirAbbas
added a commit
to Agent-Field/agentfield
that referenced
this pull request
Aug 4, 2026
…erseded-aware (#873) * fix(packages): an install job reports the package the installer actually installed `Manager.run` inferred the installed package by diffing the registry's names before and after: whichever name is new must be the one this job installed. That inference breaks on exactly the case `superseded_by` was added for. A successor may declare its predecessor's name — an in-place rename, which is what both Agent-Field/SWE-AF#122 and Agent-Field/pr-af#64 use, and what keeps a node id, its triggers, and its node-scoped secrets intact across the swap. The set of installed names is then identical before and after, so the diff finds nothing and the job reports an empty package name. AgentField Desktop streams that job's output, so the user watched a successful install end in "install completed: " with the name missing. When the successor's name *does* differ, the diff happened to work, but only by luck: it returns the first registry name that is new, so any unrelated entry appearing during the install is misattributed to this job. The installer already knows the answer — `GitInstaller` tracks it in `installedName` and propagates it through a redirect. Export it, thread it out through the package service as `InstallPackageWithResult`, and have the job prefer it, keeping the before/after diff as the fallback for installers that cannot report a name. Node-dependency discovery used the same diff idiom and is switched to the authoritative name too, which also stops it from walking the dependencies of a package some other caller installed concurrently. Updates take the authoritative name as well. `StartUpdate` pre-seeds the job with the name being updated, so previously the installer's answer was discarded — and an update whose recorded source redirects to a differently named successor would then try to restart the package the redirect had just uninstalled. It now reports and restarts the node that exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(desktop): name the node an install actually landed on Every install result in the app was phrased from the request: the catalog row's name, or the URL that was pasted. A `superseded_by:` redirect makes that a guess — the manifest at the source hands the install off to a successor, which may register under its own name. Now that the control plane reports what it installed, repeat that instead: - a pasted repo says "pr-af installed" rather than "Installed from https://github.com/Agent-Field/pr-af", which is the more useful half of the sentence and the only one that tells you what to run next; - a catalog install names the successor if it ever disagrees with the row — the two agree for every entry today (that is the invariant catalog.ts documents), so a disagreement is drift worth seeing rather than hiding behind the row's own label; - an update that followed a rename reads "<old> replaced by <new>" instead of claiming it updated a node that no longer exists. Each falls back to the previous wording when the control plane names nothing, so an older control plane behaves exactly as it does today. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(catalog): one PR-AF row, and install both consolidated nodes by repo The catalog offered PR-AF twice — a Python row and a Go row shipping the same reasoners under a name the user had to know to type. The two were indistinguishable in the Install view except by the `-go` suffix, which is an implementation detail leaking into a product list. Agent-Field/pr-af#64 collapses them the way Agent-Field/SWE-AF#122 collapsed the SWE fleet: the root manifest redirects to `//go`, and the Go node takes the product's name. So this is one `pr-af` row, language go. Both consolidated rows now install from the bare repo URL rather than naming `//go` directly. Selecting the subdirectory would install the same node, but it skips the redirect — and the redirect is the part that carries an existing install across: it puts the successor in place first, migrates node-scoped secrets, and only then retires the predecessor. Someone who already has the Python node gets migrated by pressing Update; someone naming `//go` would only collide with it. Naming the repo and letting the manifest decide is also simply what a user can be told to type. That changes the rule both catalogs are written against, so both header comments now say the new one: an entry's `name` must equal the name the package ends up REGISTERED under once the install settles, which under a redirect is not the `name:` in the manifest at the source, and may live in a subdirectory the catalog never mentions. sec-af and cloudsecurity-af are untouched — neither ships a second implementation, so neither has anything to collapse. The SWE guard test generalizes to cover both repos: exactly one row per repo, named for the product, sourced at the bare URL, language go, and the retired implementation-suffixed name absent from the whole catalog — so a re-added row fails here instead of quietly reappearing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(skills): the PR review node is pr-af, not pr-af-go The agentfield-use skill is what a harness reads to learn how to call the nodes on this machine, and skillkit installs it into Claude Code, Codex, Cursor and the rest — so its examples are the ids an agent will actually try. Its `executions/active` sample still showed a run targeting `pr-af-go`, a name that stops existing once Agent-Field/pr-af#64 lands. Applied identically to the embedded copy under skillkit/skill_data so the two stay byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(packages): pin that the production installer can report what it installed The job reaches the authoritative name through a type assertion, and a failed assertion is silent — it falls back to inferring the name from a registry diff, which is exactly the path that returns nothing for an in-place `superseded_by` replacement. Every other test in this file uses a stub that satisfies the interface by construction, so none of them would notice a production wiring change (a decorator, a swapped implementation) that quietly reverted the fix. This one asserts against the service the server actually constructs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(packages): a node-dependency cycle must terminate Switching dependency discovery from a registry snapshot to the authoritative installed name dropped the only thing that stopped a cycle. The snapshot version terminated by accident but reliably: the recursive call received a snapshot that already contained the package just reinstalled, so the second lap skipped it. Recursing on a single name removed that, and the remaining guard — `depName != "" && isPackageInstalled(depName)` — cannot substitute. It only knows a dependency's name for `af://registry/…` refs, and a forced install reinstalls whatever is already there. Every update is forced (`StartUpdate` → `startJob(JobUpdate, …, true)`), so two packages declaring each other by bare git URL or local path recursed until the process died — with the package-job manager's `active` latch held, blocking every later install. Tracks the packages this install pass has walked instead, which does not depend on ref form, on Force, or on registry state. The accompanying suite pins the seam's behaviour end to end through the real git installer rather than a stub: a redirect reports the successor — including when the successor takes the predecessor's own name, the case a registry diff cannot see and the reason this seam exists — a failed install reports no name at each stage it can fail, an uninstallable dependency does not fail its parent, and a cycle terminates. That last one fails in 30s against this fix reverted. `manager_test.go` covers the other side: an installer that cannot report a name still installs and falls back to the registry diff, so the old path stays intact for anything that does not implement the newer seam. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ing fix Picks up swe-pro-go 67d00765322d298eda4e48043d53d7f92d60143d, "fix(specidentifiers): a file the spec names and the fix changed is not missing". The engine's spec-identifier gate required a filename quoted in an issue to appear inside the text of the changed files. A source file never contains its own name, so an issue whose "## Files — Modify: `ordinals.go`" line named the target made the gate unsatisfiable: it reported the file missing while that file was the one the fix changed, and re-dispatched a green tree until the run ran out of wall clock. The changed-file list is now a fallback for the content check, so naming a file and changing it satisfies the gate while a coder that touched the wrong file is still caught. Both binaries: CGO_ENABLED=0, -trimpath -ldflags="-s -w", mode 755. 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.
Makes
af install <this repo>give you the Go node, makes an OpenRouter key the only thing you need to get it working, and ships the Go node's bundled high-performance coding engine behind an opt-in flag.Installing the repo gives you the Go node — as
swe-plannerThe Go node is the maintained SWE node — same reasoners, same interface, one static binary, and it ships the coding engine. But it lived in
go/under the nameswe-planner-go, so getting it meant knowing to type//go, and anyone who had already installed the Pythonswe-plannerstayed on it forever.Two changes make the bare repo URL the whole story. The root manifest carries a redirect:
and the Go node drops its
-gosuffix — package name, node ids, and reasoner tags — to take the product's names. The suffix existed so the port could run beside the Python node during the migration; it is not something a user should ever have had to type. Since both manifests now declareswe-planner,af install https://github.com/Agent-Field/SWE-AFreplaces an existing install in place: same node id, same triggers, node-scoped secrets untouched because the scope name never changes.swe-planner.buildmeans the same thing before and after, whichever implementation is serving it.The mechanism behind the redirect is a generic manifest key in AgentField (Agent-Field/agentfield#864) — including the part that lets a successor share its predecessor's name, which is what an in-place rename needs. This repo only supplies the data.
The Python package is untouched and still what
python -m swe_afanddocker-compose.ymlrun. Because the two now answer to the same ids, running both against one control plane needs an explicitNODE_IDon one of them —docker-compose.go.ymldoes exactly that, and is now the only place carrying-goids. To install the Python node deliberately, clone the repo and install the checkout as a local path; local-path installs do not follow the redirect.Getting started is one key now
Several things quietly made the OpenRouter-only path fail:
.env.exampleshippedANTHROPIC_API_KEYuncommented with a placeholder. Any non-empty value disables the OpenRouter auto-select, so the documentedcp .env.example .envquick start forced theclaude_coderuntime with a bogus key.GH_TOKENhad the same problem — a placeholder madegit/ghattempt (and fail) auth instead of degrading to the no-token path. Both are commented out now, with OpenRouter presented first.SWE_DEFAULT_RUNTIME=open_codesilently swapped the model. It opted out of the auto-selected default and fell back to minimax. The runtime base and the auto default are now the same model, so opting in explicitly resolves identically.claude_code/haikuand could not run. It now uses the same detection as the main path, and honours theSWE_DEFAULT_MODEL→AI_MODEL→HARNESS_MODELcascade it previously ignored.env_file: .envfor provider keys on the planner services, so exporting a key in your shell gave you a workingswe-fastand a keylessswe-planner. The functional compose file also still bakedSWE_DEFAULT_RUNTIME=claude_code.With only
OPENROUTER_API_KEYset, the planner and fast nodes — Go and Python — now auto-selectopen_codeand resolve every role toopenrouter/deepseek/deepseek-v4-flash.GH_TOKENstays optional. Theclaude_codepath is unchanged.Coding engine (opt-in preview)
The Go node bundles a prebuilt engine that is inert unless
SWE_PRO_ENGINE=1. With it set, builds route per-issue coding through it; unset it and you are back on the classic loop. Branch, push and PR creation stay with the standard pipeline.If the flag is on but no runnable binary is found, the node logs a warning and comes up on the classic loop. That check is deliberately about runnability, not mere existence: a binary that lost its execute bit used to look available, get
pro_executeregistered, and route every issue to an engine that could never start — worse than not opting in.The opt-in is now discoverable: it is declared in the Go node's manifest (so it appears in the
af installprompt), in.env.example, and in both READMEs.go/docs/pro-engine.mdpreviously had zero inbound links.Validation
gofmt,go build ./...,go vet ./...,go test -race -count=1 ./...— all cleanpytest— 1143 passed, 1 skippedcode_taskproduced correct code, and the tests it wrote passaf install: the engine resolves next to the built node binary, the sidecar spawns; with the execute bit removed it degrades cleanly instead of crash-loopingaf installagainst a local repo carrying it, with the Python node already installed: it warned, installed the successor asswe-plannerwith its source recorded as…//go, replaced the predecessor's files in place, and left both the node-scoped and global secrets intact. The installed binary, started withNODE_IDunset, registers asnode_id: swe-planner, and the engine binary lands next to it at0755implement_issuewith onlyOPENROUTER_API_KEYsetNote for reviewers
The engine binary is vendored at
go/bin/swe-pro, refreshed to the engine's currentmain(15 commits past the previous build, including the in-process search fallback that removes the hard dependency onripgrepbeing installed on the host). It is a staticlinux/amd64build, reproducible — building it twice gives byte-identical output.The branch is laid out so that binary is added exactly once in history: the refresh amends the commit that introduces it rather than layering a second 20 MB blob, so the branch still carries one.
🤖 Generated with Claude Code