mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
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:
@@ -87,7 +87,9 @@ _BASH_ROLES = frozenset({"developer", "documenter", "cell_pm", "main_pm"})
|
||||
# The intake interviewer reads the codebase to draft a task and may fan out
|
||||
# exploration to subagents (parity with the Claude intake's ``Task`` allowance);
|
||||
# every other role drives work through the gateway verbs, never CLI subagents.
|
||||
_SUBAGENT_ALLOWED_ROLES = frozenset({"prompter"})
|
||||
_SUBAGENT_ALLOWED_ROLES: frozenset[str] = (
|
||||
frozenset()
|
||||
) # fleet-wide ban: no agent spawns subagents (CEO, 2026-07-09)
|
||||
|
||||
# Grok CLI tool IDs (from the CLI's --tools/--disallowed-tools reference).
|
||||
_TOOL_SHELL = "run_terminal_cmd"
|
||||
|
||||
@@ -27,7 +27,7 @@ class RoleConfig:
|
||||
flow_tools: tuple[str, ...] # roboco-flow verbs
|
||||
do_tools: tuple[str, ...] # roboco-do content tools
|
||||
allows_write: bool # Edit, Write to workspace
|
||||
allows_subagent: bool # `Agent` tool (parallel research)
|
||||
allows_subagent: bool # `Agent` tool — fleet-wide False (CEO ban, 2026-07-09)
|
||||
description: str
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ ROLE_CONFIGS: dict[str, RoleConfig] = {
|
||||
flow_tools=_CELL_PM_FLOW,
|
||||
do_tools=_CELL_PM_DO,
|
||||
allows_write=False,
|
||||
allows_subagent=True,
|
||||
allows_subagent=False,
|
||||
description="Triages, unblocks, and completes cell tasks; merges leaf PRs.",
|
||||
),
|
||||
"main_pm": RoleConfig(
|
||||
@@ -209,7 +209,7 @@ ROLE_CONFIGS: dict[str, RoleConfig] = {
|
||||
flow_tools=_MAIN_PM_FLOW,
|
||||
do_tools=_MAIN_PM_DO,
|
||||
allows_write=False,
|
||||
allows_subagent=True,
|
||||
allows_subagent=False,
|
||||
description="Coordinates across cells; opens master PR; escalates to CEO.",
|
||||
),
|
||||
"product_owner": RoleConfig(
|
||||
@@ -217,7 +217,7 @@ ROLE_CONFIGS: dict[str, RoleConfig] = {
|
||||
flow_tools=_PRODUCT_OWNER_FLOW,
|
||||
do_tools=_PRODUCT_OWNER_DO,
|
||||
allows_write=False,
|
||||
allows_subagent=True,
|
||||
allows_subagent=False,
|
||||
description="Product oversight; escalates strategic decisions to CEO.",
|
||||
),
|
||||
"head_marketing": RoleConfig(
|
||||
@@ -225,7 +225,7 @@ ROLE_CONFIGS: dict[str, RoleConfig] = {
|
||||
flow_tools=_HEAD_MARKETING_FLOW,
|
||||
do_tools=_HEAD_MARKETING_DO,
|
||||
allows_write=False,
|
||||
allows_subagent=True,
|
||||
allows_subagent=False,
|
||||
description="Marketing oversight; escalates to CEO.",
|
||||
),
|
||||
"auditor": RoleConfig(
|
||||
@@ -252,7 +252,7 @@ ROLE_CONFIGS: dict[str, RoleConfig] = {
|
||||
flow_tools=_PROMPTER_FLOW,
|
||||
do_tools=_PROMPTER_DO,
|
||||
allows_write=False,
|
||||
allows_subagent=True,
|
||||
allows_subagent=False,
|
||||
description=(
|
||||
"Intake interviewer; chats only with the human, reads the codebase, "
|
||||
"and drafts a task. No outward agent comms; never writes or merges."
|
||||
@@ -263,7 +263,7 @@ ROLE_CONFIGS: dict[str, RoleConfig] = {
|
||||
flow_tools=_SECRETARY_FLOW,
|
||||
do_tools=_SECRETARY_DO,
|
||||
allows_write=False,
|
||||
allows_subagent=True,
|
||||
allows_subagent=False,
|
||||
description=(
|
||||
"CEO's chief-of-staff; chats only with the CEO and carries gated CEO "
|
||||
"authority. Reads company state and executes the CEO's directives, "
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user