Installing this repo gives you the Go node, as pr-af - #64
Conversation
The Go node is the maintained PR review node — same reasoners, same
interface, one static binary, no per-node venv to build. But it lived in
`go/` under the name `pr-af-go`, so getting it meant knowing to type
`//go`, and anyone who had already installed the Python `pr-af` stayed on
it forever.
Two changes make the bare repo URL the whole story. The root manifest
carries a redirect:
superseded_by: https://github.com/Agent-Field/pr-af//go
and the Go node drops its `-go` suffix to take the product's name. 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 declare `pr-af`, `af install
https://github.com/Agent-Field/pr-af` replaces an existing install in
place: same node id, same triggers, node-scoped secrets untouched because
the scope name never changes. `pr-af.review` means 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 pr_af.app` and
docker-compose.yml run. Because the two now answer to the same ids,
running both against one control plane needs an explicit NODE_ID on one of
them — docker-compose.go.yml does exactly that, and is now the only place
carrying `-go` ids. 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.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follows the manifest rename through the code that carries the node's runtime identity: the NODE_ID default, the port doc comments, the package docs, and every test that asserted the old default. Callers now reach `pr-af.review`, matching what the manifest registers. The 16 semantic role tags are unchanged — they were never node-identity tags, and the comment that explained the distinction by contrast with SWE-AF's `-go` role tags no longer has anything to contrast with. `-go` survives in exactly one place: docker-compose.go.yml, which runs the Go node beside the Python one against a single control plane and therefore has to override NODE_ID to keep the two distinguishable. The functional compose stack does not — it runs the Go node alone, so it uses the real id. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both READMEs still told the reader the Python node was the default and that the Go node was an opt-in sibling reached by typing `-go` into the reasoner path. Neither is true once the root manifest redirects: `af install https://github.com/Agent-Field/pr-af` gives you the Go node as `pr-af`, and an existing Python install is replaced in place. Documents the local-path escape hatch in both files, since that is now the only way to install the Python node deliberately, and keeps the Go add-on Compose file described as what it is — the way to run both at once during a changeover, which is why it still carries `-go` ids. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Heads-up on sequencing, from testing this against a real `af` built from agentfield `main`. Selecting a subdirectory bypasses the redirect: `resolvePackageRoot` resolves `//go` and only `go/agentfield-package.yaml` is parsed, so the root manifest's `superseded_by` is never read. That means renaming this manifest to `pr-af` breaks the `pr-af-go` catalog row that shipped clients already carry (it is in `v0.1.120`, sourced at `.../pr-af//go`). Verified with a local repo fixture carrying this PR's manifests:
Nothing in this PR is wrong — this is inherent to the rename, and no merge order protects binaries already on disk. But merging Agent-Field/agentfield#873 and cutting a release first removes the stale `pr-af-go` row from subsequent builds, so only users who have not updated are exposed. Suggest holding this until that release is out. The redirect itself is verified working end to end: fresh install of the bare repo URL registers the Go node as `pr-af`; an existing Python `pr-af` is replaced in place with its node-scoped secrets intact; the built binary registers on a live control plane as `node_id: pr-af` with reasoners at `pr-af.*` and no `pr-af-go` residue. |
…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>
Makes
af install https://github.com/Agent-Field/pr-afgive you the Go node, registered aspr-af. Mirrors what Agent-Field/SWE-AF#122 does for the SWE fleet.The problem
The Go node is the maintained PR review node — same reasoners, same interface, one static binary, no per-node venv to build. But it lived in
go/under the namepr-af-go, so getting it meant knowing to type//go, and anyone who had already installed the Pythonpr-afstayed on it forever.The change
The root manifest carries a redirect:
and the Go node drops its
-gosuffix — package name and node id — to take the product's name. 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 declare
pr-af, installing the repo replaces an existing install in place: same node id, same triggers, node-scoped secrets untouched because the scope name never changes.pr-af.reviewmeans 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.
What is deliberately unchanged
The Python package is untouched and still what
python -m pr_af.appanddocker-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. Both READMEs document that escape hatch.
The 16 semantic role tags are unchanged — they were never node-identity tags.
Validation
Ran the literal steps from
.github/workflows/ci.yml:ruff check src/ scripts/— all checks passeddocker build -t pr-af:test .— passedgo build ./...,go vet ./...,go test ./...— all cleantest -z "$(gofmt -l .)"— cleanpytest— 73 passedpr-af+superseded_by→//go;go/pr-af,language: go,node_id: pr-af, port 8007Merge order
Nothing here depends on another PR. The desktop/CLI catalog change on the AgentField side points its PR-AF row at this bare repo URL and should land after this.
🤖 Generated with Claude Code