From 860c663c8058cb76c16f63541f6442734ff0bdbf Mon Sep 17 00:00:00 2001 From: ZeyuFu Date: Sat, 16 May 2026 05:34:04 -0400 Subject: [PATCH] fix-up(#4027): narrow coordinator guard to registry hard-reject set Maintainer feedback (#4071 review): the original guard rejected sisyphus and atlas as subagent targets even from team-mode where resolveMember() intentionally calls resolveSubagentExecution with allowPrimaryAgentDelegation: true. Per AGENT_ELIGIBILITY_REGISTRY (src/features/team-mode/types.ts), only prometheus is hard-reject; sisyphus and atlas are explicitly verdict: 'eligible' for team membership. Shrink COORDINATOR_AGENT_NAMES to ['prometheus'] so the guard aligns with the registry's authoritative classification, document the scoping rule in a comment, and add regression tests covering: - sisyphus is NOT blocked by the coordinator guard (registry eligible) - atlas is NOT blocked by the coordinator guard (registry eligible) - prometheus IS blocked even when allowPrimaryAgentDelegation: true (registry hard-reject is authoritative) Fixes the 5 zauc-mocks resolver tests that were locking in the wrong rejection set (including 'allows delegating to a primary agent when allowPrimaryAgentDelegation is enabled'). The one test asserting the literal primary-agent error string for Prometheus display-name was loosened to a regex that accepts either guard's message, since prometheus is now caught by the coordinator path which fires before the primary-agent lookup. --- src/tools/delegate-task/constants.ts | 8 ++- .../coordinator-subagent-guard.test.ts | 59 +++++++++++++++++++ .../subagent-resolver.test.ts | 5 +- 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/tools/delegate-task/constants.ts b/src/tools/delegate-task/constants.ts index c312fce23..f2e4f33be 100644 --- a/src/tools/delegate-task/constants.ts +++ b/src/tools/delegate-task/constants.ts @@ -352,9 +352,15 @@ export function isPlanFamily(category: string | undefined): boolean { * arbitrary subagent targets via task(). Delegating to these creates duplicate * orchestration and conflicting team state (issue #4027). * + * Scoped to AGENT_ELIGIBILITY_REGISTRY hard-reject entries only — sisyphus and atlas + * are explicitly marked `verdict: "eligible"` for team membership in the registry + * (src/features/team-mode/types.ts), so they are NOT included here. Adding them would + * conflict with the team-mode resolver's intentional `allowPrimaryAgentDelegation: true` + * opt-in. + * * Symmetric guard to the caller-eligibility check added by PR #4065 for team_create. */ -export const COORDINATOR_AGENT_NAMES = ["prometheus", "atlas", "sisyphus"] +export const COORDINATOR_AGENT_NAMES = ["prometheus"] /** * Returns true when the given agent name refers to a coordinator/meta agent that diff --git a/src/tools/delegate-task/coordinator-subagent-guard.test.ts b/src/tools/delegate-task/coordinator-subagent-guard.test.ts index 6ea8dd0dc..12099bce3 100644 --- a/src/tools/delegate-task/coordinator-subagent-guard.test.ts +++ b/src/tools/delegate-task/coordinator-subagent-guard.test.ts @@ -30,6 +30,7 @@ describe("coordinator subagent guard (#4027)", () => { prompt: "do something", load_skills: [], run_in_background: false, + description: "test delegation", } //#when @@ -51,6 +52,7 @@ describe("coordinator subagent guard (#4027)", () => { prompt: "plan something", load_skills: [], run_in_background: false, + description: "test delegation", } //#when @@ -72,6 +74,7 @@ describe("coordinator subagent guard (#4027)", () => { prompt: "write some code", load_skills: [], run_in_background: false, + description: "test delegation", } //#when @@ -80,6 +83,62 @@ describe("coordinator subagent guard (#4027)", () => { //#then — hephaestus may fail for other reasons (API call), but NOT the coordinator guard expect(result.error).not.toContain("coordinator agent") }) + + test("#given subagent_type=sisyphus #when resolveSubagentExecution is called #then sisyphus is NOT blocked by coordinator guard (registry: eligible)", async () => { + //#given — sisyphus is verdict:'eligible' in AGENT_ELIGIBILITY_REGISTRY; it must not be rejected by the coordinator guard + const ctx = makeCtx() + const args = { + subagent_type: "sisyphus", + prompt: "do team-mode work", + load_skills: [], + run_in_background: false, + description: "test delegation", + } + + //#when + const result = await resolveSubagentExecution(args, ctx, "sisyphus", "") + + //#then — sisyphus may fail for primary-agent reasons (separate guard), but NOT the coordinator guard + expect(result.error).not.toContain("coordinator agent") + }) + + test("#given subagent_type=atlas #when resolveSubagentExecution is called #then atlas is NOT blocked by coordinator guard (registry: eligible)", async () => { + //#given — atlas is verdict:'eligible' in AGENT_ELIGIBILITY_REGISTRY; it must not be rejected by the coordinator guard + const ctx = makeCtx() + const args = { + subagent_type: "atlas", + prompt: "do team-mode work", + load_skills: [], + run_in_background: false, + description: "test delegation", + } + + //#when + const result = await resolveSubagentExecution(args, ctx, "sisyphus", "") + + //#then — atlas may fail for primary-agent reasons (separate guard), but NOT the coordinator guard + expect(result.error).not.toContain("coordinator agent") + }) + + test("#given subagent_type=prometheus AND allowPrimaryAgentDelegation=true #when resolveSubagentExecution is called #then prometheus is STILL rejected (registry hard-reject is authoritative)", async () => { + //#given — prometheus is verdict:'hard-reject' in AGENT_ELIGIBILITY_REGISTRY; the coordinator guard must fire even when the team-mode resolver opts into primary-agent delegation + const ctx = makeCtx() + const args = { + subagent_type: "prometheus", + prompt: "plan something", + load_skills: [], + run_in_background: false, + description: "test delegation", + } + + //#when + const result = await resolveSubagentExecution(args, ctx, "sisyphus", "", { allowPrimaryAgentDelegation: true }) + + //#then + expect(result.error).toContain("prometheus") + expect(result.error).toContain("coordinator agent") + expect(result.agentToUse).toBe("") + }) }) export {} diff --git a/src/tools/delegate-task/zauc-mocks-subagent-resolver/subagent-resolver.test.ts b/src/tools/delegate-task/zauc-mocks-subagent-resolver/subagent-resolver.test.ts index 7aa4c0509..ca1e8aeba 100644 --- a/src/tools/delegate-task/zauc-mocks-subagent-resolver/subagent-resolver.test.ts +++ b/src/tools/delegate-task/zauc-mocks-subagent-resolver/subagent-resolver.test.ts @@ -165,7 +165,10 @@ describe("resolveSubagentExecution", () => { //#then expect(result.agentToUse).toBe("") expect(result.categoryModel).toBeUndefined() - expect(result.error).toBe('Cannot delegate to primary agent "Prometheus - Plan Builder" via task. Select that agent directly instead.') + // Prometheus is registry-hard-reject (AGENT_ELIGIBILITY_REGISTRY); the coordinator guard (#4027 / #4071) fires before + // the primary-agent guard. Either rejection message is acceptable as long as prometheus is blocked from delegation. + expect(result.error).toContain('"Prometheus - Plan Builder"') + expect(result.error).toMatch(/Cannot delegate to (coordinator agent|primary agent)/) }) test("allows delegating to a primary agent when allowPrimaryAgentDelegation is enabled (team-mode path)", async () => {