DEV Community

Machine coding Master
Machine coding Master

Posted on

Stop Infinite ReAct Loops: Deterministic Cycle Detection in Spring AI with Java 21 Record Patterns

Stop Infinite ReAct Loops: Deterministic Cycle Detection in Spring AI with Java 21 Record Patterns

Runaway ReAct agent loops are the single fastest way to blow through a enterprise LLM budget in a single deployment. If you rely on soft system prompt instructions or simple max-message limits to stop infinite agent execution, your production setup is a liability.

Why Most Developers Get This Wrong

  • Relying on hard iteration caps: Setting max-iterations=10 in your orchestrator just defers the bill spike without solving deterministic tool loops.
  • Naive string matching: Checking raw LLM text output misses cycles when tool arguments contain dynamic values like timestamps or ephemeral tracing IDs.
  • Prompt-level guardrails: Pleading with models ("do not execute the same tool twice") fails probabilistically under heavy context windows.

The Right Way

Catch execution cycles at the runtime level by pairing a custom Spring AI CallAroundAdvisor with Java 21 record patterns across a rolling sliding window.

  • Intercept at the Advisor boundary: Intercept AdvisedRequest payloads before Spring AI dispatches state back to your model provider.
  • Deconstruct tool calls with Record Patterns: Extract tool signatures and arguments cleanly using Java 21 pattern matching (case ToolCall(String name, Map args)).
  • Sliding Window Fingerprinting: Hash sanitized tool signatures over a 5-step rolling window to instantly catch $A \rightarrow B \rightarrow A$ cyclic tool execution.
  • Short-circuit execution: Throw a typed runtime exception immediately to break the loop before incurring another API token charge.

I built javalld.com while prepping for senior roles — complete LLD problems with execution traces, not just theory.

Show Me The Code

public record ToolCall(String name, Map<String, Object> args) {}

public class CycleDetectionAdvisor implements CallAroundAdvisor {
    private final SlidingWindowCache<Integer> window = new SlidingWindowCache<>(5);

    @Override
    public AdvisedResponse around(AdvisedRequest req, CallAroundAdvisorChain chain) {
        if (req.attributes().get("last_tool") instanceof ToolCall(String name, var args)) {
            int signatureHash = Objects.hash(name, sanitize(args));
            if (window.containsAndAdd(signatureHash)) {
                throw new AgentCycleDetectedException("Loop detected for tool: " + name);
            }
        }
        return chain.nextAround(req);
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Enforce guardrails in code, not prompts: LLMs are probabilistic, but cycle detection in your backend pipeline must be strictly deterministic.
  • Leverage Java 21 Record Patterns: Cleanly destruct agent state without messy reflection or verbose instance checks.
  • Fail fast at the framework edge: Catch tool looping inside Spring AI's CallAroundAdvisor before firing unnecessary LLM token calls.

Top comments (1)

Collapse
 
alexshev profile image
Alex Shev

Deterministic loop detection is the right framing. A ReAct loop is not only a bad answer; it is a control-system failure where the agent keeps proving it cannot change state. I like that this treats the loop as something observable in the trace instead of something you hope the model notices.