mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
* fix(agents): raise the tool-call budget — 150 halted legitimate work mid-task Repeated budget-sweep bounces: a dev at the 150 ceiling gets its container killed seconds after a real commit, burning a spawn and the resumed agent's re-verification turns. 300 keeps the runaway guard while clearing a real task's footprint; warn scales to 100. * chore(docs): reflow hard-wrapped prose from the #401 merge * chore(foundation): regenerate lifecycle artifacts; reflow inherited prose --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com>
97 lines
3.7 KiB
Python
97 lines
3.7 KiB
Python
"""Tier 1 — agent_loop budgets + verb retry limits."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
import pytest
|
|
from roboco.agent_sdk import server
|
|
from roboco.foundation.policy import agent_loop
|
|
|
|
# Canonical defaults the foundation guarantees. Asserting against named
|
|
# constants keeps the contract explicit (and keeps PLR2004 happy — these
|
|
# are not magic numbers, they are the public API surface).
|
|
EXPECTED_TOOL_CALL_WARN_AT = 100
|
|
EXPECTED_TOOL_CALL_HALT_AT = 300
|
|
EXPECTED_LOOP_THRESHOLD = 3
|
|
EXPECTED_LOOP_WINDOW = 10
|
|
EXPECTED_PM_RESPAWN_MAX_UNPRODUCTIVE = 3
|
|
EXPECTED_VERB_RETRY_MAX_PER_MINUTE = 3
|
|
EXPECTED_HANDOFF_VERB_CAP = 3
|
|
|
|
|
|
def test_default_budget_has_canonical_thresholds() -> None:
|
|
b = agent_loop.DEFAULT_BUDGET
|
|
assert b.tool_call_warn_at == EXPECTED_TOOL_CALL_WARN_AT
|
|
assert b.tool_call_halt_at == EXPECTED_TOOL_CALL_HALT_AT
|
|
assert b.loop_threshold == EXPECTED_LOOP_THRESHOLD
|
|
assert b.loop_window == EXPECTED_LOOP_WINDOW
|
|
assert b.loop_action == "halt" # NEW: was "warn-only"
|
|
assert b.pm_respawn_max_unproductive == EXPECTED_PM_RESPAWN_MAX_UNPRODUCTIVE
|
|
assert b.verb_retry_max_per_minute == EXPECTED_VERB_RETRY_MAX_PER_MINUTE
|
|
|
|
|
|
def test_warn_threshold_below_halt_threshold() -> None:
|
|
assert (
|
|
agent_loop.DEFAULT_BUDGET.tool_call_warn_at
|
|
< agent_loop.DEFAULT_BUDGET.tool_call_halt_at
|
|
)
|
|
|
|
|
|
def test_loop_threshold_below_loop_window() -> None:
|
|
"""Loop detection requires N repeats in a window of M; N must be < M."""
|
|
assert (
|
|
agent_loop.DEFAULT_BUDGET.loop_threshold < agent_loop.DEFAULT_BUDGET.loop_window
|
|
)
|
|
|
|
|
|
def test_verb_retry_limits_cover_critical_handoff_verbs() -> None:
|
|
"""The verbs that surfaced in the 2026-05-10 retry storm are capped."""
|
|
assert agent_loop.VERB_RETRY_LIMITS["i_am_done"] == EXPECTED_HANDOFF_VERB_CAP
|
|
assert agent_loop.VERB_RETRY_LIMITS["complete"] == EXPECTED_HANDOFF_VERB_CAP
|
|
assert agent_loop.VERB_RETRY_LIMITS["submit_up"] == EXPECTED_HANDOFF_VERB_CAP
|
|
|
|
|
|
def test_unlimited_retry_verbs_includes_discovery_verbs() -> None:
|
|
"""give_me_work / triage / evidence aren't subject to circuit breaker."""
|
|
assert "give_me_work" in agent_loop.UNLIMITED_RETRY_VERBS
|
|
assert "triage" in agent_loop.UNLIMITED_RETRY_VERBS
|
|
assert "evidence" in agent_loop.UNLIMITED_RETRY_VERBS
|
|
assert "i_am_idle" in agent_loop.UNLIMITED_RETRY_VERBS
|
|
|
|
|
|
def test_retry_limit_for_known_verb_returns_int() -> None:
|
|
assert agent_loop.retry_limit_for("i_am_done") == EXPECTED_HANDOFF_VERB_CAP
|
|
|
|
|
|
def test_retry_limit_for_unlimited_verb_returns_none() -> None:
|
|
assert agent_loop.retry_limit_for("give_me_work") is None
|
|
|
|
|
|
def test_retry_limit_for_unknown_verb_returns_default() -> None:
|
|
"""Unknown verbs fall through to the policy default."""
|
|
assert (
|
|
agent_loop.retry_limit_for("not_a_real_verb")
|
|
== agent_loop.DEFAULT_BUDGET.verb_retry_max_per_minute
|
|
)
|
|
|
|
|
|
def test_agent_sdk_thresholds_match_foundation_defaults() -> None:
|
|
"""Without env overrides, the agent_sdk constants equal foundation defaults."""
|
|
# If a test environment sets ROBOCO_AGENT_*, the env override path is in
|
|
# effect and this default-parity check no longer applies.
|
|
if any(
|
|
k in os.environ
|
|
for k in (
|
|
"ROBOCO_AGENT_TOOL_CALL_WARN",
|
|
"ROBOCO_AGENT_TOOL_CALL_HALT",
|
|
"ROBOCO_AGENT_LOOP_THRESHOLD",
|
|
"ROBOCO_AGENT_LOOP_WINDOW",
|
|
)
|
|
):
|
|
pytest.skip("env override set; skipping default-parity check")
|
|
assert agent_loop.DEFAULT_BUDGET.tool_call_warn_at == server._WARN_THRESHOLD
|
|
assert agent_loop.DEFAULT_BUDGET.tool_call_halt_at == server._HALT_THRESHOLD
|
|
assert agent_loop.DEFAULT_BUDGET.loop_threshold == server._LOOP_THRESHOLD
|
|
assert agent_loop.DEFAULT_BUDGET.loop_window == server._LOOP_WINDOW
|