feat: full Go port of SWE-AF on the AgentField Go SDK#94
Draft
AbirAbbas wants to merge 55 commits into
Draft
Conversation
Module github.com/Agent-Field/SWE-AF/go with replace directive to the sibling agentfield Go SDK. Stub entrypoints for swe-planner (:8003) and swe-fast (:8004) wire env -> agent.Config. afx.Bind[T] marshals a reasoner input map into a typed struct, routing through UnmarshalJSON so pydantic-style defaults seed correctly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ucts Every planning/execution/fast/hitl data model ported with exact snake_case JSON tags and no omitempty. 13 structs with non-zero pydantic defaults get defaultXxx() + UnmarshalJSON seeding so zero-input unmarshal matches Python (e.g. CoderResult.complete=true, IssueGuidance.needs_new_tests=true). Enums are typed string consts with exact Python values; KNOWN_SERVICES ported verbatim; BuildResult.MarshalJSON computes pr_url like the Python model_dump override. Round-trip tests cover a Python-shaped checkpoint fixture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… mapping Verbatim port of runtime/providers.py including the provider/adapter asymmetry (claude_code -> "claude" vs "claude-code") and exact 'Unsupported runtime provider' error text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fatal ports the 13 non-retryable harness error patterns and FatalHarnessError; envelope.UnwrapCallResult reproduces unwrap_call_result semantics including verbatim failure messages and the error_message -> error -> "unknown" fallback chain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Run-scoped in-memory credential store with env injection (scoped values override base), matching hitl/credentials_store.py: values are never persisted or returned; empty values filtered; mutex-guarded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replicates ReasonerFailed(result=...) without SDK changes: best-effort POST of status=failed + structured result + error to the executions status endpoint, then a plain error return so the SDK marks the execution failed while the control plane keeps the result. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Byte-identical port of the Exa websearch guardrail, applied only when OPENCODE_ENABLE_EXA=1 and EXA_API_KEY are set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…application Kahn's level scheduling with deterministic insertion ordering matching Python dict semantics, transitive downstream lookup, same-level file conflict detection, sequence numbering, and ApplyReplan with current_level reset. Cycle errors reproduce Python's list repr. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Run[T] is the single choke-point for role LLM calls: invopop schema reflection (cached), scoped-credential env injection, fatal-error classification before parse handling, and default-seeded fallback when structured output fails to parse. RoleOptions maps role config onto harness.Options with the adapter provider string. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verbatim port of ci_gate.py: pending/failure bucket classification, SHA anchoring that refuses a verdict until checks for the head SHA appear, per-check failed-log excerpts via gh run view, and byte-equal summary strings. Poll loop honors ctx cancellation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pper Direct hax API client replicating the Python SDK's create-request body (camelCase to_payload, publicKey omitted for plaintext webhook values), RequestUserInputAndPause built on the SDK's RequestApproval + WaitForApproval polling (execution shows waiting in the UI), verbatim decision-to-status mapping and values extraction, budget-bounded RunWithAskUser re-invocation, and deterministic service detection for the environment scout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…odel resolution Verbatim legacy-key rejection and repo normalization errors, strict decode matching pydantic extra=forbid, and the full model resolution cascade (runtime base < SWE_DEFAULT_MODEL/AI_MODEL/HARNESS_MODEL env < models.default < models.<role>) across the 17 role model fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, issue_writer Verbatim port with pyStr/pyRepr helpers so f-string interpolation (list reprs, True/False/None, zfill) matches Python byte-for-byte. Golden fixtures generated from the live Python modules. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ester, repo_finalize, github_pr Extracted evaluated Python strings (line-continuation collapses included) and machine-generated golden renders; exports WorkspaceContextBlock for other packages. Local manifest model stubs carry TODO(wiring) to swap for schemas types at wiring time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, scout Byte-verbatim port; golden tests assert against base64-captured output from the live Python prompt builders. Shared helpers (WorkspaceContextBlock, FormatPriorUserResponses) exported for role reasoners. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verbatim port of retry_advisor, issue_advisor, replanner, fix_generator, ci_fixer, pr_resolver, and fast planner prompts with Python repr/truthiness/rune-slicing parity helpers; golden fixtures rendered by the live Python builders. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Handler map keyed by exact Python reasoner names; qa_synthesizer uses the direct AI path (not the harness) with the verbatim deterministic fallback; notes, tool lists, guardrail application, and iteration_id semantics preserved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation tester, finalize, PR Seven handlers with verbatim notes/fallbacks, previous_error retry context for git_init, per-issue WorkspaceInfo results, and fatal propagation matching the Python try/except flow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sprint planner HITL-wrapped PM/scout via poll-based ask-user with budget 2; scout stores scoped credentials and excludes them from its response; tech lead writes plan/review.json; parse failures raise the exact Python error strings except scout's deterministic fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
run_ci_watcher purely delegates to the cigate poller (no LLM); fixer and resolver preserve tool lists, note formatting, and deterministic fallbacks with empty collections serialized as [] for model_dump parity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Retry advisor, issue advisor (HITL, ACCEPT_WITH_DEBT fallback), replanner (HITL, inner parse-retry loop + raw log writes), issue writer, verifier, and fix generator with verbatim fallbacks, notes, and fatal propagation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Default path (coder->reviewer) and flagged path (coder -> QA||reviewer -> synthesizer, errgroup barrier with gather(return_exceptions) semantics), stuck detection over a 3-iteration window, iteration-state checkpoints and per-iteration artifacts at the exact Python paths, and memory read/write seams. Errors propagate only for fatal harness failures or cancellation; all other terminals encode into IssueResult. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Six-stage build orchestrator (git_init -> plan -> execute -> verify -> finalize -> PR) with every stage routed through CallFn for DAG parity, sequential task execution with per-task timeouts, deterministic single-task planning fallback, and the seven delegating wrapper registrations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full port of run_dag: level barriers with bounded concurrency, per-issue timeouts, worktree setup/merge/integration-test/cleanup gates, debt/split/replan gates with apply_replan level reset, checkpoints at every Python save point, middle-loop issue advisor and retry advisor, and multi-repo init. gather(return_exceptions) semantics preserved at the barrier; race-clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full build pipeline: multi-repo clone with sparse checkout, git-init retry loop, optional scout, plan/execute via control-plane calls, verify+fix cycles, finalize, PR, CI-gate and approval-gate seams, empty-build guard through the reasonerfail carrier, and deferred scoped-credential cleanup. common.go carries the Deps/Handler surface the sibling orchestrators build on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Thin wrapper building ExecutionConfig, wiring the unwrapping CallFn and note function, forwarding resume/build_id/git_config/workspace manifest options, and supporting external execute_fn_target coders. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clone/fetch PR head with fallback, committer identity from env, run_pr_resolver call, base-merge classification, CI gate via the shared seam, and gh REST + GraphQL thread replies with resolution. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PM -> optional scout -> architect/tech-lead bounded review loop with auto-approve annotation -> sprint planner -> level computation and sequence assignment -> parallel issue writers tolerant of individual failures, with verbatim notes and artifact paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hax plan-approval revision loop on the poll-based approval client (request_changes replans architect->tech lead->sprint planner, bounded by max_plan_revision_iterations; rejected/expired terminal with verbatim summaries), plus resume_build reconstructing plan_result from the checkpoint and re-invoking execute with resume=true. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports _run_ci_gate with verbatim terminal statuses and the ci_fixer_model->coder_model resolution. Parity corrections against the Python source: the startup-grace sleep stays at resolve()'s call site (the gate never sleeps), and every watch keeps the original head_sha anchor — Python never re-anchors to the fixer's commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Registers the complete reasoner surface under exact Python names — swe-planner: 5 orchestrators + 25 roles; swe-fast: 25 roles + fast build/plan/execute/verify — with tags matching Python's registration (router roles tagged, @app.reasoner orchestrators untagged), input schemas for the orchestrator and fast reasoners, and the CI-gate / approval-gate seams wired. Both binaries boot and register their surfaces; whole-module build/vet/test green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Builder sparse-clones the agentfield SDK at a pinned AGENTFIELD_SDK_REF build arg (cache-busts on ref change) so the replace directive resolves; static swe-planner/swe-fast binaries; runtime mirrors the Python image's tool surface plus the claude CLI. docker-compose.go.yml mirrors the Python stack on the Go image; Python Dockerfile/compose untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Without Config.PublicURL the SDK registers http://localhost:<port> as the node's base URL, so a containerized control plane cannot route execute calls back to the node (agent_unreachable 504). The Python SDK reads the same AGENT_CALLBACK_URL env var (agent_server.py:758) before defaulting to localhost; mirror it in BuildAgent. Found by the T7.2 functional suite's deterministic run_ci_watcher call. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
T7.2 / design §11(b): live-stack tests behind the functional build tag. TestMain owns the compose lifecycle (up --build once, down -v always) under a dedicated project name with host ports remapped to 18080/18003/ 18004 so the stack coexists with anything on 8080/8003/8004; skips with a message when Docker is unavailable. Covered: /health on both nodes; exact reasoner-name registration parity (swe-planner 30, swe-fast 29); CIWatchResult key-set parity via the deterministic run_ci_watcher on both nodes; the ReasonerFailed carrier's CP status contract (failed+result+error persist together, resultless failed re-post does not clobber); env-gated (SWE_FUNCTIONAL_LLM=1) minimal real build asserting BuildResult keys + planning-DAG child reasoners. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The authoritative documents the Go port was executed from: full Python codebase inventory, Go SDK capability map and gap analysis, the module design (schema/defaults strategy, concurrency mapping, HITL and ReasonerFailed replication, checkpoint compatibility), and the wave breakdown with per-task validation contracts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the poll-based ApprovalClient workaround with the new SDK Pause API (agentfield#744): a single Pauser interface threads through the ask-user flow, planning/advisor roles, and the plan-approval gate; the control plane's approval webhook now resumes executions instantly, matching Python's app.pause semantics. Decision mapping and values extraction unchanged. Requires the agentfield feat/go-sdk-parity SDK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Also ignore local go.work files used for cross-repo dev builds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collaborator
Author
|
Update: SDK-native parity landed on this branch — the poll-based HITL workaround has been replaced with the Go SDK's new webhook-resumed Merge-order dependency:
Schema-mode and cost needed no SWE-AF changes: Python SWE-AF's effective schema mode is single-shot (never passes |
agentfield#744 (Agent.Pause, harness cost, schema mode) and #745 (installer Go support) merged and released as v0.1.107 — the Go port now builds against upstream main with no dev workspace shim. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds go/test/mockclaude, a drop-in mock for the `claude` binary the harness invokes, so the entire swe-planner pipeline can be exercised end-to-end with ZERO LLM calls. - Detects the reasoner role from the verbatim --system-prompt argv value (24 roles: planning, coding, advisor, ci, gitops) and constructs real typed structs from internal/schemas, guaranteeing schema validity. - Performs the REAL git/gh work the gitops + coder roles expect (the Go port drives all git via the CLI agent): git_init branches, workspace_setup worktrees, per-issue coder commits, merger --no-ff merges, github_pr push + `gh pr create` (non-draft, per the github_pr prompt / private-repo support). - Scenario engine (SWE_MOCK_SCENARIO override, flock-guarded per-role/issue counters and a JSONL invocation log under SWE_MOCK_STATE_DIR) whose default exercises: reviewer reject→approve (inner loop), reviewer block→issue_advisor retry_modified→approve (middle loop), verifier fail→fix→pass (outer loop), a needs_deeper_qa 4-call path, and a workflow-less repo → ci no_checks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
go/test/e2e-fast/run.sh builds mockclaude as a `claude` shim, builds swe-planner, starts (or reuses) `af server` on :18080 and the planner on :18003 with the shim on PATH, resets the private GitHub fixture repo swe-af-e2e-mock to a clean seed commit, POSTs an async swe-planner.build, polls to terminal, and asserts: build succeeded, draft/PR created + files pushed, the CP workflow DAG contains the expected reasoner set (incl. the flagged QA path, issue_advisor and generate_fix_issues), the mock invocation log shows every control-loop path fired, the CI gate reached no_checks, and notes were recorded. ~1 min wall clock, zero LLM calls. `--keep` leaves the stack up; runs/ is gitignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…thout CI
WatchPRChecks returned status="error" for any PR whose repo has no CI
workflows, so the documented no_checks gate verdict was unreachable in
practice. Two root causes in internal/cigate/watch.go:
1. The `gh pr checks --json` field list included `headSha`, which is not a
field of `gh pr checks` in any gh version (its schema is bucket,completedAt,
description,event,link,name,startedAt,state,workflow — headSha is a
`gh pr view` concept). gh rejects an unknown --json field for the WHOLE
call ("Unknown JSON field: headSha"), so every watch aborted immediately.
Request only valid fields; headSHAOf() now always returns "", so the
existing shaUnsupported fallback degrades SHA anchoring to PR-level verdicts
(per-check SHA anchoring is not achievable via `gh pr checks`).
2. `gh pr checks` exits NON-ZERO with an empty stdout and a "no checks reported
on the '<branch>' branch" stderr when a repo has no CI — the benign
no_checks signal, not a failure. The error handler treated any non-zero
exit with an empty body as status="error"; it now recognizes the
"no checks reported" message and keeps polling so the no_checks verdict
fires after the wait cap, while genuine gh failures still surface as error.
The prior TestNoChecksWhenPRHasNoCI stubbed `[]`+rc=0, a gh behavior that
never occurs, so it never caught this. Added TestNoChecksWhenGhReportsNoChecks
Reported (real behavior: rc=1, empty stdout, "no checks reported" stderr) and
TestGenuineGhErrorStillReported (a real failure must still be error).
Found via the go/test/e2e-fast harness: run_ci_watcher returned status="error"
(`Unknown JSON field: "headSha"`) against a workflow-less fixture repo.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reposition the Go port from a drop-in replacement to an opt-in sibling: the
Go nodes now default to distinct identities and ports so both stacks can
register against one control plane simultaneously.
- swe-planner -> swe-planner-go, default PORT 8003 -> 8005
- swe-fast -> swe-fast-go, default PORT 8004 -> 8006
NODE_ID / PORT env overrides still win — only the compiled defaults change.
Callers opt into the Go port via the -go reasoner path, e.g.
POST /api/v1/execute/async/swe-planner-go.build.
Changes:
- cmd/swe-planner, cmd/swe-fast: BuildAgent default id/port.
- node/register.go: registration tags swe-planner-go / swe-fast-go.
- Empty-Deps fallbacks (orch/common, orch/approval_gate, dag/executor,
fast/build) default to the -go identity; runtime call targets already
derive from the live NodeID, so no hardcoded call targets exist.
- Dockerfile EXPOSE/PORT/NODE_ID default to swe-planner-go:8005.
- Makefile: unify AGENTFIELD_SDK_REF with the Dockerfile pin.
- Unit-test fixtures updated to the -go identity. Python-parity comments
that quote Python's actual "swe-planner"/"swe-fast" ids are left intact.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restructure the Go compose from a standalone mirror of the Python stack into an ADD-ON layered on top of the running Python stack, matching the opt-in sibling model. It now defines ONLY the two Go nodes: - swe-agent-go -> node id swe-planner-go, :8005 - swe-fast-go -> node id swe-fast-go, :8006 Run story: `docker compose up -d` (Python stack, owns the control plane) then `docker compose -f docker-compose.go.yml up -d` (adds the Go nodes). It is a separate compose project (name: swe-af-go) that joins the Python stack's default network (external: swe-af_default) so control-plane:8080 resolves, and shares its workspaces volume (external: swe-af_workspaces). build-db is dropped: nothing in go/ source consumes it. The DATABASE_URL_TEST passthrough (used by target-repo integration tests) is kept, pointed at the Python stack's build-db over the shared network. control-plane/build-db depends_on is removed since they live in the Python project. `docker compose -f docker-compose.go.yml config` validates. The go/Makefile docker-up help is updated to describe the add-on precondition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Update the zero-LLM e2e-fast harness and the black-box functional suite to exercise the Go port's new opt-in-sibling identities. e2e-fast (run.sh): NODE_ID=swe-planner-go on :18005, poll /api/v1/nodes/swe-planner-go, and POST swe-planner-go.build — so the harness drives the new default node id end to end. The built binary is still cmd/swe-planner (only the runtime identity changed). Prompts/mockclaude are untouched, so golden outputs stay byte-identical. functional: the production docker-compose.go.yml is now an opt-in add-on with no control plane of its own, so it can no longer be brought up in isolation. Add a self-contained compose.functional.yml (control plane + both Go nodes + build-db, own network/volumes) and repoint main_test.go at it. Its build context / env_file use ../../.. so they still resolve to the repo root from the file's new location under go/test/functional. Host-port override remaps to 18080/18005/18006; node-id constants and docstrings updated to swe-planner-go / swe-fast-go. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
go/README.md: update the binaries table, run targets, compose section, and env-var table to the new -go identities/ports; add an "Opt-in alongside Python" section explaining that Python remains the default (swe-planner :8003 / swe-fast :8004) and the Go port registers separately as swe-planner-go :8005 / swe-fast-go :8006 against one control plane. The compose section now documents the add-on run story (Python stack first, then the Go nodes). README.md: add ONE short "Go implementation (opt-in)" section near the end pointing at go/README.md and stating the Python impl is the default with all existing instructions unchanged. No other root README edits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ate/role results Python pydantic rejects a present-but-null value on every list field, so the initial checkpoint (whose ~14 DAGState list fields are nil) and every role harness result must serialise lists as [] not null. dict|None fields (workspace_manifest, git_init_result, split_request, tests_passed) legitimately stay null. - schemas.EmptyForNilSlices: reflection walker replacing nil slices with empty ones, leaving maps and nil pointers untouched. - DAGState.MarshalJSON: applies the walker at one chokepoint (checkpoint.json, BuildResult.dag_state embedding, replanner context). - harnessx.Run: normalizes the parsed result (success + seeded-fallback paths). - Tests: DAGState/checkpoint marshal has [] for all 14 list keys and null for workspace_manifest; walker leaves nil maps/pointers untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`go test -race ./...` was failing on two concurrent-execution test doubles: - fast callScripter.call appends to calls from worker goroutines that callWithTimeout can leak past a timeout — guard with a mutex and read via snapshot()/count() in assertions. - orch mockApp.Note is shared across the two concurrent Build() goroutines in TestBuildIsolationConcurrent — guard notes with a mutex. Also gofmt the two files flagged by `gofmt -l`: fast/planner.go (import order) and functional/reasoner_failed_test.go. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…K validation The SDK now validates every harness output against the invopop-reflected schema (harness/schema.go validateAgainstSchema). invopop's raw reflection over-rejects valid output vs the source pydantic models, so schemaFor now post-processes each schema to match pydantic exactly: - enums: JSONSchemaExtend on AdvisorAction/IssueOutcome/ReplanAction/ QASynthesisAction emits the `enum` constraint (invopop emitted bare strings), so an invalid action now fails validation and drives the retry loop. - required: invopop marks every field required (no omitempty); schemas. MakePydanticFaithful replaces it with the pydantic no-default set per type (authoritative table cross-checked against defaults.go). - additionalProperties: relaxed from invopop's `false` (pydantic ignores extras). - nullable: Optional[...] fields (Go pointers) and map fields serialise as null when unset; their subschemas are made to accept null (a map cannot be distinguished from dict|None at the Go type level, so all maps accept null) — the minimal relaxation that never over-rejects. Verified against the santhosh-tekuri validator the SDK uses: valid role outputs (with null tests_passed/ask_user_form/agent_retro) pass; action:"banana" fails; extra keys are accepted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…chema validation The SDK now validates harness output against the reflected schema. mockclaude builds outputs by marshalling internal/schemas structs, whose nil slices marshal to null and fail `type: array` validation — which would break e2e-fast. Run the same schemas.EmptyForNilSlices normalization before writing so list fields are [] (nil pointers/maps stay null, accepted by the pydantic-faithful schema). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s for OpenRouter The run_qa_synthesizer direct-LLM branch was unreachable: BuildAgent never set cfg.AIConfig, so agent.AI() always returned "AI not configured" and only the deterministic fallback ran. - node.BuildAgent sets cfg.AIConfig = ai.DefaultConfig() when it validates (a key is present), guarded so a missing/invalid key leaves it nil and node startup still succeeds. - coding.mapSynthModel maps the short role aliases (haiku/sonnet/opus) to anthropic/claude-*-4.5 / -4.1 ids when the client targets OpenRouter (its OpenAI-compatible endpoint rejects "haiku"); ids with "/" and the non-OpenRouter path pass through unchanged. Applied at the RunQASynthesizer AI call seam. - Document the direct-LLM env vars (OPENAI/OPENROUTER key, AI_BASE_URL, AI_MODEL) in .env.example and both Go services in docker-compose.go.yml. - Tests: resolveAIConfig with/without key, BuildAgent constructs without a key, mapSynthModel alias mapping on/off OpenRouter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rrier
The SDK now exports a result-carrying agent.ReasonerFailed whose Result/ErrorDetails
are attached to the single 5x-retried status post by the async handler. Replace
SWE-AF's manual internal/reasonerfail carrier (POST status=failed then return a
plain error) with returning &agent.ReasonerFailed directly:
- build.go empty-build guard returns &agent.ReasonerFailed{Message, Result} with
the BuildResult attached only when it is a JSON object (mirrors the retired
buildBody guard); same message string, same preserved outcome.
- common.go drops the postFailedFn seam, PosterConfig, and the Deps Token/HTTPClient
fields (only the carrier used them); AgentFieldServer stays for the approval base.
- delete internal/reasonerfail (nothing else uses it); update build_test.go and
common_test.go to assert the *agent.ReasonerFailed contract; reframe the
functional CP-contract test comments (the CP persistence guarantee is unchanged).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bump AGENTFIELD_SDK_REF in the Dockerfile ARG default and the Makefile to the pushed tip of agentfield feat/install-path-selector, which carries the SDK changes SWE-AF's Go port now builds against (native ReasonerFailed, harness JSON-schema validation, codex/opencode fixes). Changing the ref string busts the sparse-clone layer cache so the new SDK is actually pulled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…K harness The pinned SDK (45311737) validates harness output with github.com/santhosh-tekuri/jsonschema/v5 v5.3.1. Because SWE-AF/go builds the SDK through the go.mod replace, that transitive dep must be recorded in SWE-AF's go.mod/go.sum or `GOWORK=off go build` (CI/Docker) fails with a missing go.sum entry. Added via `GOWORK=off go mod tidy`; version matches the SDK's go.mod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a `go` job to the CI workflow that reproduces the Docker sparse-clone of the AgentField SDK at the pinned AGENTFIELD_SDK_REF, then runs gofmt, go build, go vet and `go test -race` from SWE-AF/go with GOWORK=off (the same resolution CI/Docker use). The existing python `test` job is unchanged. Repos are public so no clone token is needed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…outage resilience) Real-LLM testing exposed that one timed-out status poll killed hour-long builds when the control plane stalled under system load. The SDK now retries transient poll failures with backoff (5-minute default window) and only re-submits when the request provably never reached the server. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6 tasks
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
Complete 1:1 port of SWE-AF from Python to Go using the AgentField Go SDK, per the full-transition plan. The Python implementation stays untouched during the transition; the Go module lives under
go/with two binaries (swe-planner:8003,swe-fast:8004).Parity contract honored:
build,plan,execute,resolve,resume_build+ 25 roles), swe-fast exposes 29 (25 roles + fastbuild/fast_plan_tasks/fast_execute_tasks/fast_verify). No grouping, no simplification.agent.Call(control-plane routed), so the execution DAG renders identically — the control plane builds edges solely fromX-Run-ID/X-Parent-Execution-ID, which the Go SDK sets identically to Python.UnmarshalJSONseeding), same registration tags as Python's routers.checkpoint.jsonfor resume compatibility, verbatim error strings and note messages throughout.Update (2026-07-10): repositioned as an opt-in sibling, not a drop-in replacement
The Go port no longer shadows the Python node's identity. It now registers under distinct default identities so both stacks can run against a single control plane at the same time:
swe-planner-goon:8005andswe-fast-goon:8006(the Python nodes remain the default —swe-planner:8003/swe-fast:8004— and are completely untouched). Callers opt in per request by targeting the-goreasoner path, e.g.POST /api/v1/execute/async/swe-planner-go.build;NODE_ID/PORTenv overrides still work.docker-compose.go.ymlis now an add-on to the Python stack rather than a parallel copy of it: bring up the Python stack first (docker compose up -d, which owns the control plane), thendocker compose -f docker-compose.go.yml up -dto add the Go nodes (they join the Python stack's network/volume as external references). All Python code, the root Dockerfile/compose, and existing instructions are byte-for-byte unchanged — every diff lives undergo/,docker-compose.go.yml, and a single new root-README section.Also since the last push: a zero-LLM e2e harness (
go/test/e2e-fast/run.sh, mockclaudeCLI) that runs the full pipeline (plan → DAG → control loops → verify → push → PR → CI gate) in ~60s and passes against the new-goidentity, plus a CI-gate fix (gh pr checksinvalidheadShaJSON field; benign "no checks reported" now yieldsno_checksinstead oferror).Key implementation notes
Agent.Pause(); the plan-approval and ask-user flows use the SDK's poll-basedRequestApproval/WaitForApproval(execution still showswaitingin the UI). Hax is called via its REST API directly, replicating the Python SDK's payload.ReasonerFailed(result=...): replicated without SDK changes — the node postsstatus=failed+ structured result + error to the executions status endpoint, then errors (contract verified against a live control plane).af installis Python-only, so the Go nodes ship viago/Dockerfile(multi-stage; agentfield SDK pinned viaAGENTFIELD_SDK_REFbuild arg for cache-busting) anddocker-compose.go.ymlmirroring the Python stack. Dev builds use ago.work+replacedirective against the sibling repo.AGENT_CALLBACK_URLas their public URL — without it every containerized deployment got504 agent_unreachablefrom the control plane.Known accepted differences (documented in go/docs/port/)
cost_usd(Go SDK gap; candidate SDK patch).schema_mode="incremental"has no Go counterpart (single-shot + schema retries instead).nullwhere pydantic emits[](role fallbacks seed the important ones explicitly).Test plan
go build ./... && go vet ./... && go test ./...— whole module green (27 packages, includes-raceon concurrency-heavy packages).go test -tags functional ./go/test/functional/) against a live compose stack: health, exact registration parity (30+29 names), result key-set parity, failed-with-result status contract. All PASS; stack torn down after.docker build -f go/Dockerfile .succeeds;docker compose -f docker-compose.go.yml configvalidates.python3.12 -m pytest tests/,compileall): zero Python files changed; the only local failures reproduce identically on unmodifiedmain(env leakage from.envviaload_dotenv+ a sibling repo's editable install shadowingtests.conftest) and cannot occur in CI.SWE_FUNCTIONAL_LLM=1, implemented ingo/test/functional/build_llm_test.go) — requires an Anthropic key; run before flipping production traffic.Docs
go/docs/port/carries the design doc, work breakdown, and the two research reports (Python inventory, Go SDK gap analysis) the port was executed from;go/README.mdcovers build/run/deploy.Update: external-review fixes (2026-07-10)
Adversarial review of the port surfaced six fixes, applied against the pinned SDK
(agentfield PR #750,
feat/install-path-selector@45311737), which now adds anative result-carrying
agent.ReasonerFailed, real JSON-schema validation insidethe harness retry loop, and codex/opencode structured-output fixes. This supersedes
the two "known differences" above about the manual
ReasonerFailedcarrier andnullempty collections.[]— a reflection walker (schemas.EmptyForNilSlices) plusDAGState.MarshalJSONandharnessx.Runnormalization ensure every list fieldserialises as
[](pydantic rejects present-but-null lists);dict|Nonefields(
workspace_manifest,git_init_result,tests_passed, …) legitimately staynull.the reflected schema,
schemaForadds enum constraints (viaJSONSchemaExtend),replaces invopop's all-fields
requiredwith the pydantic no-default set, relaxesadditionalProperties, and makes Optional/map fields nullable — so valid outputis no longer over-rejected while invalid enum values now are.
BuildAgentnow setsAIConfig(guarded; nil whenno key), making the direct-LLM branch reachable; short model aliases
(
haiku/sonnet/opus) are mapped to provider ids when the client targetsOpenRouter. Env vars documented in
.env.exampleanddocker-compose.go.yml.agent.ReasonerFailed— the empty-build guard returns the SDK'sresult-carrying
&agent.ReasonerFailed; the manualinternal/reasonerfailPOST-then-error carrier is deleted.
fast/orchconcurrent test doubles sogo test -race ./...is clean; formatted the twogofmt-flagged files.AGENTFIELD_SDK_REFto45311737(Dockerfile + Makefile,plus the new indirect
santhosh-tekuri/jsonschemadep the SDK harness pulls in),and added a
goCI job (sparse-clone the SDK at the pin, thengofmt/build/vet/
test -race; the existing Python job is untouched).Also normalized
mockclaudeoutput so the zero-LLM e2e harness stays schema-validunder the new SDK validation. Validation:
gofmt/go build/go vet/go test -racegreen in both workspace andGOWORK=offmodes;docker compose -f docker-compose.go.yml configvalid; the full mock e2e pipeline(
go/test/e2e-fast/run.sh) passes end-to-end against a live control plane withzero schema-validation retries; Python suite unchanged and green.
🤖 Generated with Claude Code