Merge pull request 'docs: add subagent tool-budget guardrails for MCP tasks (Closes #259)' (#284) from docs/issue-259-subagent-tool-budget into master

This commit was merged in pull request #284.
This commit is contained in:
2026-07-07 03:43:47 -05:00
5 changed files with 159 additions and 1 deletions
+2 -1
View File
@@ -134,7 +134,8 @@ class TestControlPlaneGuide(GuideTestBase):
"merge_confirmation", "redaction", "separation",
"profile_switching", "identity_verification",
"work_selection", "global_worktree",
"shell_spawn_hard_stop"):
"shell_spawn_hard_stop", "subagent_tool_budget",
"subagent_delegation"):
self.assertIn(key, rules)
self.assertIn("MERGE PR", json.dumps(rules["merge_confirmation"]))
self.assertTrue(rules["hard_stops"])
+89
View File
@@ -0,0 +1,89 @@
"""Doc-contract checks for subagent tool-budget guardrails (#259).
Single-step MCP tasks must not expand into 100+ tool-call retry spirals with
WebFetch/Playwright/manual-encoding fallbacks. These tests pin budget defaults,
native-MCP-first rules, and forbidden detours in the runbooks, portable skill,
and operator guide.
"""
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
RUNBOOK = REPO_ROOT / "docs" / "llm-workflow-runbooks.md"
SKILL = REPO_ROOT / "skills" / "llm-project-workflow" / "SKILL.md"
def _normalize(text: str) -> str:
"""Collapse whitespace so phrase checks survive markdown line wrapping."""
return " ".join(text.split())
def _runbook_text() -> str:
return _normalize(RUNBOOK.read_text(encoding="utf-8"))
def _skill_text() -> str:
return _normalize(SKILL.read_text(encoding="utf-8"))
def test_runbook_has_subagent_tool_budget_section():
text = _runbook_text()
assert "## Subagent Tool-Budget Guardrails" in text
for phrase in (
"15",
"5 minutes",
"40",
"15 minutes",
"gitea_commit_files",
"WebFetch",
"Playwright",
"no retry spirals",
):
assert phrase.lower() in text.lower(), f"runbook missing phrase: {phrase!r}"
def test_runbook_forbids_subagent_commit_delegation():
text = _runbook_text()
for phrase in (
"do not delegate commit authority to a subagent",
"main session first",
"native MCP before fallback",
):
assert phrase.lower() in text.lower(), f"runbook missing: {phrase!r}"
def test_runbook_rejects_fallback_detours_when_mcp_commit_available():
lower = _runbook_text().lower()
for forbidden in ("webfetch", "playwright", "manual llm-generated base64"):
assert forbidden in lower, f"runbook must name forbidden detour: {forbidden!r}"
assert "_encode_" in lower
assert "gitea_commit_files" in lower
def test_skill_doc_declares_subagent_tool_budget_guardrails():
text = _skill_text()
assert "## Subagent Tool-Budget Guardrails" in text
for phrase in (
"15 tool calls",
"5 minutes",
"gitea_commit_files",
"WebFetch",
"never resume a failed subagent",
):
assert phrase.lower() in text.lower(), f"SKILL.md missing phrase: {phrase!r}"
def test_operator_guide_rules_include_subagent_tool_budget():
import sys
sys.path.insert(0, str(REPO_ROOT))
import gitea_mcp_server
rule = gitea_mcp_server._GUIDE_RULES["subagent_tool_budget"]
for phrase in (
"15 tool calls",
"gitea_commit_files",
"WebFetch",
"retry loop",
):
assert phrase in rule, f"operator guide rule missing: {phrase!r}"