From cf4aa7b99e80df550d2a09fe0bc2c7f641bff110 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Fri, 21 Aug 2026 08:47:25 -0400 Subject: [PATCH] fix: with SWE_PRO_ENGINE on, expose only the pro surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the pro engine is enabled (the af-install / desktop default), the node still advertised the classic opencode-driven orchestrators (plan/build/execute/ resolve/resume_build) and implement_issue alongside the pro executor. Those entry points drive the opencode role harness, which fails wherever opencode isn't installed — the desktop bundle ships aforge, not opencode — so the Product Manager reasoner dies in ~500ms and swe-planner.plan/build are broken entries sitting next to swe-pro's working code_task. Make the flag a replacement, not an addition: with pro.Available(), register only the pro executor and withhold the classic entry points. Role reasoners stay registered (internal, undiscoverable) so pro_execute can still call them. The swe-pro sidecar (code_task/code_resume) is the coding surface. SWE_PRO_ENGINE=0 restores the full classic planning surface unchanged. Updated the surface/entrypoint tests for the pro-on case. Co-Authored-By: Claude Opus 4.8 --- go/internal/node/discovery_surface_test.go | 5 ++++- go/internal/node/pro_surface_test.go | 11 ++++++----- go/internal/node/register.go | 21 +++++++++++++++++---- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/go/internal/node/discovery_surface_test.go b/go/internal/node/discovery_surface_test.go index 6b44e22f..86fa7cc7 100644 --- a/go/internal/node/discovery_surface_test.go +++ b/go/internal/node/discovery_surface_test.go @@ -152,7 +152,10 @@ func TestProExecuteIsInternal(t *testing.T) { if m.Description == "" { t.Error("pro_execute lost its description") } - assertSurface(t, "swe-planner[pro][entrypoint]", entrypointNames(n), wantEntrypoints) + // With the engine on the classic entry points are withheld, so the node + // advertises no entry points of its own — the swe-pro sidecar carries the + // coding entry (code_task/code_resume). + assertSurface(t, "swe-planner[pro][entrypoint]", entrypointNames(n), nil) } // TestExecuteDescribesItsPlanResultInput: execute's plan_result is the one input diff --git a/go/internal/node/pro_surface_test.go b/go/internal/node/pro_surface_test.go index 804166ef..7e084227 100644 --- a/go/internal/node/pro_surface_test.go +++ b/go/internal/node/pro_surface_test.go @@ -20,9 +20,11 @@ func fakeEngineBin(t *testing.T) { } // TestRegisterPlannerProSurfaceGated: with SWE_PRO_ENGINE set and the engine -// binary present, the planner surface is the default 31 names plus exactly the -// pro handlers — and nothing on the fast node changes (the pro surface is -// planner-only). +// binary present, the pro engine REPLACES the classic surface — the internal +// role reasoners and the pro handlers register, but the opencode-driven +// orchestrators and implement_issue are withheld (they'd fail wherever opencode +// is absent; the swe-pro sidecar is the working coding entry). Nothing on the +// fast node changes (the pro surface is planner-only). func TestRegisterPlannerProSurfaceGated(t *testing.T) { t.Setenv(pro.EnvEnabled, "1") fakeEngineBin(t) @@ -33,8 +35,7 @@ func TestRegisterPlannerProSurfaceGated(t *testing.T) { } n.RegisterPlanner() - want := append(append([]string(nil), pythonRoleSurface...), pythonOrchestrators...) - want = append(want, pythonIssueReasoners...) + want := append([]string(nil), pythonRoleSurface...) for name := range pro.Handlers() { want = append(want, name) } diff --git a/go/internal/node/register.go b/go/internal/node/register.go index 598ab4aa..1fb47923 100644 --- a/go/internal/node/register.go +++ b/go/internal/node/register.go @@ -63,15 +63,28 @@ const ( tagInternal = "internal" ) -// RegisterPlanner registers the full swe-planner surface: 25 role reasoners + -// 5 orchestrators + the issue-level entry point (31 total). Ports swe_af/app.py. +// RegisterPlanner registers the swe-planner surface. With the pro engine off it +// is the full classic surface: 25 role reasoners + 5 orchestrators + the +// issue-level entry point (31 total), porting swe_af/app.py. With the pro engine +// on (pro.Available()), the classic entry points are withheld and only the pro +// executor is added — see the body for why. func (n *Node) RegisterPlanner() { n.registerRoles() - n.registerOrchestrators() - n.registerIssueReasoner() + // Pro engine on (the af-install / desktop default): the bundled swe-pro + // sidecar is the coding surface and needs no opencode, so register only the + // pro executor and withhold the classic opencode-driven entry points. The + // orchestrators (plan/build/execute/resolve/resume_build) and implement_issue + // drive the opencode role harness and fail wherever opencode is absent — the + // desktop bundle ships aforge, not opencode — so advertising them just + // surfaces broken entries next to swe-pro's working code_task. The role + // reasoners stay registered (internal, undiscoverable) so pro_execute can + // still call them. SWE_PRO_ENGINE=0 restores the full classic surface. if pro.Available() { n.registerProReasoners() + return } + n.registerOrchestrators() + n.registerIssueReasoner() } // RegisterFast registers the swe-fast surface: the same 25 role reasoners + the