Skip to content

fix(guardrails): cap deferred-tool + memory spirals (#1185, #1186, #1187)#1189

Merged
Lexus2016 merged 1 commit into
mainfrom
evolution/issue-1185-1187-spiral-cap-cluster
Jul 20, 2026
Merged

fix(guardrails): cap deferred-tool + memory spirals (#1185, #1186, #1187)#1189
Lexus2016 merged 1 commit into
mainfrom
evolution/issue-1185-1187-spiral-cap-cluster

Conversation

@Lexus2016

Copy link
Copy Markdown
Owner

Summary

Extends the existing always-on spiral-prone-tool failure cap to cover the whole deferred-tool loading chain (tool_searchtool_describetool_call) plus memory operations. These three tools had no circuit breaker — the agent blind-retried the same failing call up to 13 consecutive times with no fallback directive, while the core tools (terminal, execute_code, read_file, process, search_files) were already capped.

This is the established, tested mechanism (_SPIRAL_PRONE_TOOLS + _TOOL_FALLBACK_DIRECTIVE in agent/tool_guardrails.py): consecutive same-tool failures hit the spiral_failure_cap (default 5) and halt the turn with a concrete fallback directive, regardless of hard_stop_enabled. Adding these three tool names is a 2-line-per-tool change that takes effect through the existing before_call/after_call guards — no new dispatch-site code, no new surface.

Evidence (trace-miner, aggregated/anonymized, 7-day window)

Changes

agent/tool_guardrails.py (2 edits):

  1. _SPIRAL_PRONE_TOOLS — added tool_call, tool_describe, memory alongside the existing five, with a comment citing [FIX] tool_call failures recur — 168 failures / 21 sessions with 13-deep consecutive spirals #1185/[FIX] memory tool failures regress — 94 failures / 21 sessions (up from 10x that triggered #1135/#1136) #1186/[FIX] tool_describe failures recur — 59 failures in the deferred-tool loading chain #1187.
  2. _TOOL_FALLBACK_DIRECTIVE — added actionable directives for each:
    • tool_call → run tool_search for an alternative, use tool_describe to validate schema, or invoke a core tool instead of blind-retrying.
    • tool_describe → re-run tool_search to refresh the catalog, verify the exact name, don't keep describing the same failing name.
    • memory → distinguish busy/locked (transient, retry once) from genuine failure (skip-and-continue, log the unmet persistence need).

tests/agent/test_tool_guardrails.py (+8 tests, mirroring the existing test_cross_turn_process_spiral_cap_fires / test_cross_turn_search_files_spiral_cap_fires pattern):

  • 3 cross-turn cap-fires tests (one per tool) asserting action == "block", code == "spiral_prone_tool_failure_cap", and a non-empty fallback directive.
  • 1 membership-invariant test (>= 8 tools, extending the existing >= 5 invariant).
  • 3 fallback-directive content tests asserting the directive routes to the right alternative (search refresh / transient-vs-hard distinction).

Validation

  • Pre-PR targeted test shard (scripts/evolution_pre_pr_test_runner.py): PASS (1.2s)
  • Full guardrails suite: 62 passed (54 pre-existing + 8 new) in 0.86s
  • ruff check: All checks passed
  • Diff size: 2 files, 135 insertions(+), 0 deletions — within the autonomous-merge cap (≤200)

Why this approach (not error-message enrichment)

The issues also proposed enriching tool_describe/tool_call failure messages. On inspection, those dispatch paths (_dispatch_tool_describe_inner in tools/tool_search.py, the bridge unwrap in model_tools.py) already produce structured, actionable errors: "not a deferrable tool, did you mean X", "not currently available, re-run tool_search to refresh", "'' is not available in this session. Use tool_search...". The missing piece was the circuit breaker — the cap is what stops the spiral; the error messages were already guiding the agent, it just had no reason to listen while it could keep retrying. This PR closes that gap with the smallest, most-tested surface.

Closes #1185
Closes #1186
Closes #1187

)

Add tool_call, tool_describe, and memory to _SPIRAL_PRONE_TOOLS and
_TOOL_FALLBACK_DIRECTIVE so the existing always-on per-tool failure cap
covers the whole deferred-tool loading chain (search→describe→call) plus
memory operations.

Trace-miner evidence:
- #1185: tool_call — 168 failures / 21 sessions, 13-deep spirals
- #1186: memory — 94 failures / 21 sessions, 11-deep (regressed 10x
  from #1135/#1136)
- #1187: tool_describe — 59 failures (search→describe→call middle step)

These had no circuit breaker — the agent blind-retried the same failing
call up to 13 consecutive times with no fallback directive. Extending the
existing, tested spiral-cap mechanism covers them consistently with the
core tools (terminal, execute_code, read_file, process, search_files).

Closes #1185
Closes #1186
Closes #1187

Co-Authored-By: Hermes Evolution <evolution@hermes.ai>
@Lexus2016
Lexus2016 merged commit c1837d2 into main Jul 20, 2026
35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant