Skip to content

[Bug]: a round can keep calling the same successful tool with identical arguments indefinitely #2688

Description

@1688mengdie

Problem

The tool-call loop in the execution engine is gated only by max_rounds, and
max_rounds=0 is the upstream default for unlimited rounds. A long-running round
could therefore keep calling the same tool with identical arguments many times:
each call succeeds, so no failure recovery fires, and the round never makes
progress toward a final response.

Root cause

Successful repeats were only logged at debug level
(log_policy_thresholds's has_repeated_tool_loop) and never surfaced as a signal
that the model is stuck. The failure path tracks a consecutive count of failed tool
calls, but the successful path had no equivalent convergence detection, so a
repeating successful call stream continued indefinitely.

Fix

  • Mirror the failed-tool recovery detection onto the successful path: track a
    consecutive count of identical successful tool signatures (reusing
    tool_call_signature + repeated_tool_signature_count).
  • Exempt rounds whose calls are all legitimate read/poll tools
    (is_legitimate_poll_tool): Read, Grep, Glob, LS, WebSearch, WebFetch, ListModels.
  • Once the effective loop threshold is crossed, inject a LoopRecovery
    internal reminder so the model changes strategy; after
    MAX_SUCCESSFUL_LOOP_RECOVERY_ATTEMPTS, finalize the round without further tool
    calls via the existing finalization_reason path.
  • Drop the dead = 0 initializer on successful_tool_signature_count
    (unused_assignments warning) and reflow the rustfmt deviations so the crate is
    warning-clean and formatted.

Testing

  • Test degree: tested (focused behavioral tests).
  • Added legitimate_poll_tools_are_exempt_from_successful_loop_detection and
    mutating_tools_are_not_exempt_from_successful_loop_detection to confirm
    legitimate read/poll tooling is not misfiring the detector.
  • cargo test -p bitfun-core --features agent-runtime --jobs 4 — execution_engine
    loop-detection tests pass (successful_loop x2, zero_max_rounds,
    failed_tool_round_signature x2).
  • cargo check -p bitfun-core --features agent-runtime — no new warnings (the base
    fork_session_for_plugin dead-code warning remains).
  • git diff --check clean.
  • AI-assisted: yes (generated with review; commands above recorded).

Closes #238

Commit list:

  • 68314eee6 fix(execution): detect successful repeated tool loops — successful
    convergence signal + poll-tool whitelist + LoopRecovery reminder.
  • 646e3ea74 fix(execution): finalize on successful tool loop cap
    deterministic backstop via finalization_reason.
  • 400bc0355 fix(execution): drop dead init in successful loop counter
    removes the unused_assignments warning; rustfmt reflow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions