Skip to content

16. Unlimited Loop And Guardrails ​

This chapter explains why a fixed maximum round count is the wrong boundary for a production-style agent, and how the runtime still guards against repeated tool loops after removing that cap.

After reading this chapter, you should understand:

  • why maximum tool rounds: 5 can fail valid tasks
  • why an unlimited loop still needs stop conditions
  • how repeated call signatures detect loops
  • why visible output commits before a fatal stop

Background ​

During real use, the agent hit:

text
Agent exceeded maximum tool rounds: 5.

That was the wrong failure. Five rounds can be a normal coding-agent task.

The project removed the fixed round cap, but still needed a guard against hallucinated infinite tool loops.

Design Choice ​

Remove the global round cap.

Add a narrow repeated-tool-call guard.

The loop now stops when:

  • a completed sampling round has no tool calls
  • the end user aborts
  • a fatal runtime error occurs
  • the same tool call with the same output repeats too many times

Repeated Call Signature ​

The guard does not use a hash.

It compares:

text
tool name
normalized JSON arguments
model-visible tool output

Arguments are normalized by parsing argumentsJson and stable-stringifying objects with sorted keys.

These are treated as the same:

json
{"path":"a.ts","limit":20}
{"limit":20,"path":"a.ts"}

Input/Output Matching ​

The guard matches tool requests to tool executions by toolCallId, not by array position.

That means parallel tool execution does not break matching.

Output Commit Before Fatal Stop ​

If the repeated-call guard fires, the runtime first appends the current function_call_output to history, then stops.

This preserves the invariant:

text
function_call -> function_call_output

That invariant matters for future resume/replay.

Self-Inspection ​

The current read tool can read the file that defines it:

text
lib/agent-builtins.ts

This is not reflection. It is normal file access through the current project path policy. It means the agent can inspect runtime source code, which is a useful diagnostic capability.

Tests ​

tests/agent-sampling-loop.test.ts verifies:

  • more than five tool rounds can complete
  • repeated identical read calls stop with REPEATED_TOOL_CALL
  • tool output is still committed before fatal stop

Current Status ​

This layer removes the artificial round cap and replaces it with runtime guardrails: abort, fatal errors, no-tool completion, and the repeated-call guard together decide when the loop ends.

Common Misunderstandings ​

Misunderstanding 1: Unlimited Means No Protection ​

Unlimited only removes the artificial round cap. The runtime still has abort, fatal errors, no-tool completion, and repeated-call guards.

Misunderstanding 2: Repetition Can Be Detected By Tool Name Alone ​

It cannot. The signature needs tool name, input, and output. Otherwise the same tool operating on different files would be misclassified.

Misunderstanding 3: The Last Tool Output Can Be Dropped When The Guard Fires ​

It should not be dropped. The model and debug surfaces need to see the last tool result before the runtime reports fatal stop.

Chapter Summary ​

This chapter moves the agent loop beyond a teaching limit: tasks are no longer cut off by a fixed round count, while semantic stop conditions and repeated-call guards still control runaway loops.

Chapter Checkpoint ​

The repeated-call guard has one case that can be run by name, no key needed:

bash
npx tsx --test --test-name-pattern "repeated" tests/agent-sampling-loop.test.ts

Measured output:

text
✔ stops repeated identical tool-call loops without a global round limit (10.282708ms)
ℹ tests 1
ℹ pass 1
ℹ fail 0

The case name is itself this chapter's design statement: there is no global round cap (allows more than five tool rounds before the final response in the same file proves runs can go past five rounds), but a repeated call with the same name, arguments, and output stops with REPEATED_TOOL_CALL, and the final tool output is still committed to history before the fatal stop.