From 381b2ff4af9e581984b4575fcb33356477bcd007 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Tue, 21 Jul 2026 11:24:10 -0400 Subject: [PATCH 1/4] feat: entrypoint tags + routing descriptions on build and implement_issue (Python) Both entry points now register with the control plane carrying an 'entrypoint' tag and a caller-facing description (when to pick build vs implement_issue), so a harness can discover them via af ls --entrypoints or the discovery API instead of hardcoding node knowledge. The remaining orchestrators self-describe through their docstring first paragraphs (agentfield >= 0.1.113 registers those automatically). Co-Authored-By: Claude Fable 5 --- README.md | 6 ++++++ swe_af/app.py | 11 ++++++++++- swe_af/fast/app.py | 10 +++++++++- swe_af/issue/build.py | 11 ++++++++++- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1210ab2..af7d3ae 100644 --- a/README.md +++ b/README.md @@ -641,6 +641,12 @@ Rule of thumb for the two prompt shapes: | "Implement X feature" (needs decomposition) | `swe-planner.build` / `swe-fast.build` | | "Change this code in this file, like this" (fully scoped, context supplied) | `swe-planner.implement_issue` / `swe-fast.implement_issue` | +A harness does not need this table hardcoded: both entry points register with +the control plane carrying an `entrypoint` tag and a routing description, so +`af ls --entrypoints` (or `GET /api/v1/discovery/capabilities`) lists them — +with when-to-use guidance — on any AgentField control plane the node joins +(agentfield ≥ 0.1.113). + Each call creates its own git worktree and an `issue/-` branch off `base_branch` (default: the current branch), implements the issue with the coder → reviewer loop (a QA + synthesizer path when `needs_deeper_qa` is set), diff --git a/swe_af/app.py b/swe_af/app.py index cd75c0a..ad42b5a 100644 --- a/swe_af/app.py +++ b/swe_af/app.py @@ -489,7 +489,16 @@ def _is_empty_build(success: bool, ever_completed: int, ever_merged: int) -> boo return not success and ever_completed == 0 and ever_merged == 0 -@app.reasoner() +@app.reasoner( + tags=["entrypoint"], + description=( + "Feature-level build: plans a PRD → architecture → issue DAG, then codes, " + "reviews, merges and verifies end-to-end. Give it a goal plus repo_path or " + "repo_url; returns a verified feature branch (optionally a draft PR). " + "Typical wall-clock 25-60 min. For one well-scoped change with known files, " + "prefer implement_issue." + ), +) async def build( goal: str, repo_path: str = "", diff --git a/swe_af/fast/app.py b/swe_af/fast/app.py index a2cb0c3..8b31e2f 100644 --- a/swe_af/fast/app.py +++ b/swe_af/fast/app.py @@ -60,7 +60,15 @@ def _runtime_to_provider(runtime: str) -> str: return "opencode" -@app.reasoner() +@app.reasoner( + tags=["entrypoint"], + description=( + "Fast-mode build: one planning pass into a small task list, then code and " + "verify with tight timeouts. Same goal/repo_path interface as " + "swe-planner.build, but lighter and cheaper — suited to small features " + "where full DAG planning is overkill." + ), +) async def build( goal: str, repo_path: str = "", diff --git a/swe_af/issue/build.py b/swe_af/issue/build.py index 0564afe..4907c54 100644 --- a/swe_af/issue/build.py +++ b/swe_af/issue/build.py @@ -361,7 +361,16 @@ def note(message: str, tags: list[str] | None = None) -> None: ).model_dump() -@issue_router.reasoner() +@issue_router.reasoner( + tags=["entrypoint"], + description=( + "Issue-level build (sub-harness entry): implements ONE fully-scoped issue " + "on an isolated branch of a local repo — no planning agents, ~4-8 LLM " + "calls, minutes not hours. Give it issue{title, description, " + "acceptance_criteria, files_to_*} plus repo_path; returns the deliverable " + "branch. Prefer this over build when you already know exactly what to change." + ), +) async def implement_issue( issue: dict, repo_path: str, From 3447c0c4860037906d46a1a2e516de4d2101fc72 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Tue, 21 Jul 2026 11:24:10 -0400 Subject: [PATCH 2/4] feat(go): mirror entrypoint tags + descriptions on both Go nodes build/implement_issue (and fast build) carry the same entrypoint tag and routing description as Python; the other orchestrators get the Python docstring summaries via an explicit map since Go has no docstrings. Descriptions transmit once the SDK pin advances to agentfield >= 0.1.113 (WithDescription already exists at the current pin, so this compiles today). Co-Authored-By: Claude Fable 5 --- go/internal/node/register.go | 51 +++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/go/internal/node/register.go b/go/internal/node/register.go index e1ab3d4..8e3b0ac 100644 --- a/go/internal/node/register.go +++ b/go/internal/node/register.go @@ -137,11 +137,18 @@ func (n *Node) registerOrchestrators() { handlers["resolve"] = orch.ResolveHandler handlers["resume_build"] = orch.ResumeBuildHandler - // Python registers the orchestrators via @app.reasoner() with NO tags - // (only router-registered roles carry tags) — keep the registration - // payload identical. + // Python registers the orchestrators via @app.reasoner(): only `build` + // carries tags (["entrypoint"]) plus an explicit routing description; the + // others get their docstring summaries as descriptions — keep the + // registration payload identical. for name, h := range handlers { var opts []agent.ReasonerOption + if name == "build" { + opts = append(opts, agent.WithReasonerTags("entrypoint")) + } + if d, ok := orchestratorDescriptions[name]; ok { + opts = append(opts, agent.WithDescription(d)) + } if s, ok := orchestratorSchemas[name]; ok { opts = append(opts, agent.WithInputSchema(s)) } @@ -149,6 +156,21 @@ func (n *Node) registerOrchestrators() { } } +// orchestratorDescriptions mirrors the Python side: build's explicit +// description= kwarg, and the docstring first paragraphs the Python SDK +// auto-registers for the other orchestrators (swe_af/app.py). +var orchestratorDescriptions = map[string]string{ + "build": "Feature-level build: plans a PRD → architecture → issue DAG, then codes, " + + "reviews, merges and verifies end-to-end. Give it a goal plus repo_path or " + + "repo_url; returns a verified feature branch (optionally a draft PR). " + + "Typical wall-clock 25-60 min. For one well-scoped change with known files, " + + "prefer implement_issue.", + "plan": "Run the full planning pipeline.", + "execute": "Execute a planned DAG with self-healing replanning.", + "resolve": "Update an existing PR: merge base, fix CI, address review comments, push.", + "resume_build": "Resume a crashed build from the last checkpoint.", +} + // --------------------------------------------------------------------------- // Fast reasoners (swe-fast only) // --------------------------------------------------------------------------- @@ -164,11 +186,20 @@ func (n *Node) registerFastReasoners() { // Python tags: fast_plan_tasks/fast_execute_tasks/fast_verify come from // fast_router (tags=["swe-fast"]); the fast `build` is @app.reasoner() - // with NO tags. Mirror that exactly. + // tagged ["entrypoint"] with a routing description. Mirror that exactly. tag := agent.WithReasonerTags(tagFast) for name, h := range fast.Handlers() { var opts []agent.ReasonerOption - if name != "build" { + if name == "build" { + opts = append(opts, + agent.WithReasonerTags("entrypoint"), + agent.WithDescription( + "Fast-mode build: one planning pass into a small task list, then code and "+ + "verify with tight timeouts. Same goal/repo_path interface as "+ + "swe-planner.build, but lighter and cheaper — suited to small features "+ + "where full DAG planning is overkill."), + ) + } else { opts = append(opts, tag) } if s, ok := fastSchemas[name]; ok { @@ -192,9 +223,15 @@ func (n *Node) registerIssueReasoner() { Note: n.App, NodeID: n.NodeID, } - tag := agent.WithReasonerTags("swe-issue-go") + tag := agent.WithReasonerTags("swe-issue-go", "entrypoint") for name, h := range issue.Handlers() { - opts := []agent.ReasonerOption{tag} + opts := []agent.ReasonerOption{tag, agent.WithDescription( + "Issue-level build (sub-harness entry): implements ONE fully-scoped issue " + + "on an isolated branch of a local repo — no planning agents, ~4-8 LLM " + + "calls, minutes not hours. Give it issue{title, description, " + + "acceptance_criteria, files_to_*} plus repo_path; returns the deliverable " + + "branch. Prefer this over build when you already know exactly what to change."), + } if s, ok := issueSchemas[name]; ok { opts = append(opts, agent.WithInputSchema(s)) } From 5c18122db1b421a7e2d84fec469e9b5cce57fb03 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Tue, 21 Jul 2026 11:24:10 -0400 Subject: [PATCH 3/4] chore: bump agentfield floor to 0.1.113 for reasoner description registration Floor bump (not just a compatible range) so Docker layer caches re-resolve and actually pull the SDK that transmits per-reasoner descriptions. Co-Authored-By: Claude Fable 5 --- pyproject.toml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 418eb8b..2404652 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires-python = ">=3.12" dependencies = [ # >=0.1.96 ships ReasonerFailed, which build() raises so an empty build # reports `failed` (not `succeeded`) with its result preserved (#82 Gap 2). - "agentfield>=0.1.111", + "agentfield>=0.1.113", "pydantic>=2.0", # Compatibility pin: newer SDK builds have surfaced # "Unknown message type: rate_limit_event" during streaming. diff --git a/requirements.txt b/requirements.txt index 4c9f988..53da52c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # # Install: python -m pip install -r requirements.txt -agentfield>=0.1.111 +agentfield>=0.1.113 pydantic>=2.0 claude-agent-sdk==0.1.20 hax-sdk>=0.2.4 From ecb027e9a98bf028781a72fea60c479e2748638a Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Tue, 21 Jul 2026 12:20:00 -0400 Subject: [PATCH 4/4] chore(go): re-pin agentfield SDK to v0.1.113 (054a7d18) for description transmission go.mod pseudo-version + AGENTFIELD_SDK_REF in go/Dockerfile and ci.yml bumped together to the v0.1.113 release commit, so the Go nodes' WithDescription metadata actually reaches the control plane. Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 2 +- go/Dockerfile | 2 +- go/go.mod | 2 +- go/go.sum | 6 ++---- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b9d595..929ae68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest env: # Reproduce go/Dockerfile's sparse SDK clone; keep in sync with its AGENTFIELD_SDK_REF. - AGENTFIELD_SDK_REF: dfb5c8a37f93f510f3e390bd515afd9154194066 + AGENTFIELD_SDK_REF: 054a7d18b4bfbd6b48f8582a337894c3c5975d36 AGENTFIELD_REPO: https://github.com/Agent-Field/agentfield.git GOWORK: off steps: diff --git a/go/Dockerfile b/go/Dockerfile index 6c020a8..3fb79b7 100644 --- a/go/Dockerfile +++ b/go/Dockerfile @@ -22,7 +22,7 @@ FROM golang:1.23-bookworm AS builder # Pinned AgentField SDK ref. Default = agentfield origin/main HEAD at port time # (v0.1.107-rc.1). Changing this string invalidates the clone layer below. -ARG AGENTFIELD_SDK_REF=dfb5c8a37f93f510f3e390bd515afd9154194066 +ARG AGENTFIELD_SDK_REF=054a7d18b4bfbd6b48f8582a337894c3c5975d36 ARG AGENTFIELD_REPO=https://github.com/Agent-Field/agentfield.git WORKDIR /src diff --git a/go/go.mod b/go/go.mod index 93db20b..f897fb8 100644 --- a/go/go.mod +++ b/go/go.mod @@ -5,7 +5,7 @@ module github.com/Agent-Field/SWE-AF/go go 1.21 require ( - github.com/Agent-Field/agentfield/sdk/go v0.0.0-20260720184209-dfb5c8a37f93 + github.com/Agent-Field/agentfield/sdk/go v0.0.0-20260721154150-054a7d18b4bf github.com/invopop/jsonschema v0.13.0 golang.org/x/sync v0.11.0 ) diff --git a/go/go.sum b/go/go.sum index 3df1eda..1ae02cc 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1,7 +1,5 @@ -github.com/Agent-Field/agentfield/sdk/go v0.0.0-20260713163335-795bdd57b4de h1:GYwOpQQheZ53FgmPta3PJ2CKQjATnPIMwAvNsBA7vF4= -github.com/Agent-Field/agentfield/sdk/go v0.0.0-20260713163335-795bdd57b4de/go.mod h1:08VZk14uw4GJH6a34psHkuLu+DcRr197Zi0IGmLlfrM= -github.com/Agent-Field/agentfield/sdk/go v0.0.0-20260720184209-dfb5c8a37f93 h1:J0MecY4o7QpRWdl5CVcIw75EZP5FGn01taJKJ9MmhnM= -github.com/Agent-Field/agentfield/sdk/go v0.0.0-20260720184209-dfb5c8a37f93/go.mod h1:08VZk14uw4GJH6a34psHkuLu+DcRr197Zi0IGmLlfrM= +github.com/Agent-Field/agentfield/sdk/go v0.0.0-20260721154150-054a7d18b4bf h1:wTYUlaz81NirvBpFC7tdXeCpHCWcmaKQZFAqamNZTJw= +github.com/Agent-Field/agentfield/sdk/go v0.0.0-20260721154150-054a7d18b4bf/go.mod h1:08VZk14uw4GJH6a34psHkuLu+DcRr197Zi0IGmLlfrM= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=