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.
This commit is contained in:
ZeyuFu
2026-05-16 05:34:04 -04:00
parent 7af3007e67
commit 860c663c80
3 changed files with 70 additions and 2 deletions
+7 -1
View File
@@ -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
@@ -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 {}
@@ -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 () => {