feat(agent): detect cyclic tool-call loops (A→B→A→B) in tool guardrails - #69695
Closed
teknium1 wants to merge 1 commit into
Closed
feat(agent): detect cyclic tool-call loops (A→B→A→B) in tool guardrails#69695teknium1 wants to merge 1 commit into
teknium1 wants to merge 1 commit into
Conversation
Port from google-gemini/gemini-cli#28429: the LoopDetectionService there gained detection of alternating/cyclic tool-call execution patterns (A->B->A->B and longer cycles) that per-call repetition counters structurally cannot see, because every call differs from its predecessor. Adapted to Hermes' ToolCallGuardrailController: - Track the per-turn sequence of exact tool-call signatures (success or failure alike) and detect the trailing k-call window (k=2..5) repeating consecutively. - warn_after.cycle (default 3 full repetitions) injects the standard soft warning; hard_stop_after.cycle (default 5) halts the turn when hard_stop_enabled is on, via the existing warn/halt plumbing (zero runtime changes). - Length-1 cycles are deliberately excluded: pure self-repeats are already covered by exact_failure / idempotent_no_progress, and successful self-repeats of mutating tools (e.g. polling a background process) are legitimate. - More specific failure/no-progress warnings take precedence over the cycle warning; cycle halt takes precedence over everything. Validation: 30/30 tests in tests/agent/test_tool_guardrails.py + tests/run_agent/test_tool_call_guardrail_runtime.py; E2E via real DEFAULT_CONFIG -> from_mapping -> controller -> append_toolguard_guidance.
Contributor
૮ >ﻌ< ა ci reviewran on f21f388 ℹ️ InfoDesktop E2E visual evidence · View test artifacts · View job1 visual diff. inline evidence is publishing... debug infoCI timingsCI timings · View jobWall time 7m20s vs 8m7s (-9.7%). 10 job(s) slower, 10 faster, 1 unchanged.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The tool-loop guardrails now detect cyclic tool-call loops — an agent bouncing between two or more identical calls (A→B→A→B… or A→B→C→A→B→C…) — which the existing per-call repetition counters structurally cannot see, because each call differs from its predecessor.
Ported/adapted from google-gemini/gemini-cli#28429 (weekly Gemini CLI PR scout), where the same gap let indirect prompt injection drive infinite ReAct loops and quota-drain. Hermes' existing guards (
exact_failure,same_tool_failure,idempotent_no_progress) key on a single signature or tool, so a success-status alternating loop never accumulates a count anywhere.Changes
agent/tool_guardrails.py: track the per-turn sequence of exact call signatures (success and failure alike); detect the trailing k-call window (k = 2..5) repeating consecutively. New decision codestool_call_cycle_warning/tool_call_cycle_haltflow through the existing warn/halt plumbing — zero changes torun_agent.pyor the executor.hermes_cli/config.py:tool_loop_guardrails.warn_after.cycle: 3andhard_stop_after.cycle: 5(full cycle repetitions). Nested-key addition, no_config_versionbump needed.website/docs/user-guide/configuration.md: documented the new thresholds.tests/agent/test_tool_guardrails.py: 7 new tests (alternating, length-3 cycle, hard-stop halt, broken pattern, uniform self-repeat exclusion, precedence, turn reset) + config parsing coverage.Adaptation notes (vs. the gemini-cli original)
exact_failure/idempotent_no_progress, and successful self-repeats of mutating tools — e.g. polling a background process — are legitimate in Hermes.hard_stop_enabledswitch, matching the established guardrail posture (interactive sessions get a nudge; unattended deployments circuit-break). gemini-cli hard-halts unconditionally.Validation
tests/agent/test_tool_guardrails.py+tests/run_agent/test_tool_call_guardrail_runtime.pyDEFAULT_CONFIG→from_mapping→ controller →append_toolguard_guidancehard_stop_enabledruff checkon changed filesRelated open work this complements (none of which detects signature cycles): #52139 (cross-tool failure counter), #57816 (idempotent streaks), #59638 (content/stream loops), issues #67889 / #60084 (varying-args loops — different gap, not addressed here).