fix(runtime): fleet-wide subagent ban — no role fans out, not one (#372)

CEO directive 2026-07-09: every allows_subagent in role_config flips to
False (was True for cell_pm, main_pm, product_owner, head_marketing,
prompter, secretary); the grok path's drifted _SUBAGENT_ALLOWED_ROLES
allowlist empties to match. The spawn manifest already consumes the
flag, so Claude-path agents lose the Agent tool and grok-path agents
get it in disallowed-tools by construction. New invariant test iterates
every role config so a single role can't quietly regain it.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-09 17:14:13 +02:00
committed by GitHub
co-authored by Renn F
parent d585260087
commit 7bc42ec114
5 changed files with 26 additions and 16 deletions
+8 -1
View File
@@ -46,7 +46,14 @@ class TestRoleConfigCatalog:
assert "complete" in cfg.flow_tools
assert "unblock" in cfg.flow_tools
assert "triage" in cfg.flow_tools
assert cfg.allows_subagent is True # PMs may need parallel research
assert cfg.allows_subagent is False # fleet-wide subagent ban (2026-07-09)
def test_no_role_allows_subagents(self) -> None:
# Fleet-wide invariant (CEO, 2026-07-09): not a single role fans out.
from roboco.services.gateway.role_config import ROLE_CONFIGS
for role, cfg in ROLE_CONFIGS.items():
assert cfg.allows_subagent is False, role
def test_main_pm_config(self) -> None:
cfg = get_role_config("main_pm")
@@ -97,14 +97,14 @@ def test_main_pm_keeps_shell_but_denies_git(monkeypatch: pytest.MonkeyPatch) ->
assert "--effort" not in args
def test_prompter_allows_subagents_but_no_shell_or_edit(
def test_prompter_gets_no_subagents_shell_or_edit(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.delenv("ROBOCO_GROK_REASONING_EFFORT", raising=False)
dis = _disallowed(gc.grok_cli_args_for_role("prompter"))
# The intake interviewer may fan out to subagents (parity with Claude's Task)…
assert "Agent" not in dis
# …but it is still a read-only conversational role: no shell, no editing.
# Fleet-wide subagent ban (CEO, 2026-07-09): no role fans out, intake included.
assert "Agent" in dis
# And it remains a read-only conversational role: no shell, no editing.
assert "run_terminal_cmd" in dis
assert "search_replace" in dis
+4 -3
View File
@@ -36,7 +36,7 @@ class TestBuildForRole:
assert m.bash_allowed is True
assert "ROBOCO_SDK_URL" in m.env or "ROBOCO_PUBLIC_BASE_URL" in m.env
def test_main_pm_manifest_subagent_uses_parent_model(self) -> None:
def test_main_pm_manifest_denies_subagents(self) -> None:
m = build_for_role(
SpawnInputs(
agent_id=uuid4(),
@@ -46,8 +46,9 @@ class TestBuildForRole:
agent_model="minimax-m3:cloud",
)
)
assert m.subagent_allowed is True
assert m.subagent_model == "minimax-m3:cloud"
# Fleet-wide subagent ban (CEO, 2026-07-09) covers coordinators too.
assert m.subagent_allowed is False
assert m.subagent_model is None
def test_qa_manifest_no_write(self) -> None:
m = build_for_role(