Tool note
Claude: Tool Use Loop Failure Modes
If a tool returns an empty list when it breaks and an empty list when there is truly nothing, the model will report nothing and sound certain.
The agentic loop is three steps. The model asks for a tool, you run it, you hand back the result, repeat until it stops. Almost everything that goes wrong in production lives in the third step, and none of it is about the model.
The model’s entire view of your system is the text you put in the tool result. What you leave out, it cannot know — and it will proceed anyway, because proceeding is what it is for.
Why this exists
The first failure mode is an error the model cannot see. Your handler catches the exception, logs it properly, and returns [] to the loop. The model sees a successful call with no data and tells the user there are no matching records. An outage has become a confident wrong answer, and the log that explains it is somewhere the model was never shown.
So the distinction that must survive into the result text is this: a tool that failed, a tool that found nothing, and a tool that returned part of the answer are three different states. Most implementations collapse all three into an empty response, and once collapsed, no prompt can recover the difference.
The second is a loop that does not terminate. Usually it is not an infinite loop in the classic sense — it is the model calling the same tool with the same arguments, getting the same unhelpful result, and trying again, because nothing told it that retrying is pointless.
How to use it
Give every tool one result envelope, and write it for the model to read. A status of ok, empty, error or partial; a plain-language note; the data; and on an error, whether a retry is worth trying and what else to do. Search failed: the records API returned 503. This is transient — retry once, then report the outage rather than saying no records exist. That sentence does more than a paragraph of system prompt.
Set three bounds, not two. Maximum iterations and maximum wall clock are the obvious two, and they only cap the damage. The third — maximum repeats of the same tool with identical arguments — catches the actual pathology, and it is the one almost nobody implements.
Cap tool output at the tool. A tool that returns two hundred rows fills the window, and the symptom looks like the model forgetting your instructions halfway through the run. It did not forget — the instructions got crowded out. Truncate at the source, say so in the result, and give it a way to ask for more.
A tool description is a prompt. When the wrong tool gets called, the fix is nearly always the description and the parameter names, not another rule in the system prompt.
Then break things deliberately. Revoke a credential. Point a tool at an empty data set. Return a timeout, then malformed JSON. Read what the model tells the user in each case. That test takes an afternoon and almost nobody runs it before going live.
Where it goes wrong
Retries cannot fix authorization. A loop that retries a 403 burns budget and ends in the same place. Mark permission errors as terminal in the envelope so the model stops and says so.
Writes do not belong in a retrying loop unless they are idempotent. If the loop can retry, every write needs an idempotency key, or your timeout-and-retry quietly creates the record twice. This is the failure that reaches customers.
A loop is the wrong shape for a fixed sequence. If three steps always run in the same order, that is a script. Making it agentic adds cost, latency and nondeterminism to solve a problem you did not have.
Some boundaries are human. When an action is irreversible and the check is cheap, put a person there and stop tuning. Which actions those are is a design decision, not a safety afterthought.
And you cannot debug any of this from the final answer. Log every call, its arguments, result size, elapsed time and stop reason, or the next incident will be unreproducible.
Copy and Clear become available when the worksheet is ready.
AGENTIC TOOL LOOP — RESULT CONTRACT, BOUNDS, AND A BREAK-IT TEST
PART 1 — THE RESULT ENVELOPE (every tool returns this shape)
status: ok | empty | partial | error
note: one or two sentences written for the model to read
data: the payload, or null
meta:
truncated: true | false
returned:
total_matching: | unknown
retry_worth_it: true | false
next_action: what to try instead, in words
Four statuses, four different sentences. Never collapse them.
ok "Found 3 matching records. Fields: ."
empty "The query ran and matched 0 records. The data source is
healthy — this is a true negative, not a failure."
partial "Returned 50 of 1,248 matches, sorted by . Ask for
page 2 if you need more. Do not treat 50 as the total."
error "Search failed: the records API returned 503. Transient —
retry once. If it fails again, report the outage. Do NOT
tell the user there are no records."
THE RULE: whatever your handler catches, the model must be able to
read about it in note. A stack trace in your log that never reaches
the result is an error the model cannot see.
PART 2 — BOUNDS (three, not two)
max_iterations: hard stop on tool calls per run
max_wall_clock: s hard stop on elapsed time
max_identical_calls: 2 same tool + same arguments
^ the one people skip; it catches the
real pathology, which is repetition
On each bound being hit, return a terminating message the model can
act on, not a silent kill:
"Stopping: was called with the same arguments 3 times and
returned the same result. Summarize what you know and state what
is still unknown."
Also set:
per_tool_timeout: s
max_result_bytes: enforced AT the tool
PART 3 — TERMINAL VS RETRYABLE (decide once, per tool)
TERMINAL — never retry, return error with retry_worth_it: false
RETRYABLE — retry with backoff, cap the attempts
PART 4 — WRITE TOOLS
create a second record, and says so in note.
an approval step.
PART 5 — TOOL DESCRIPTIONS ARE PROMPTS
For each tool, check:
If the wrong tool is being called, fix this section before adding
another rule to the system prompt.
PART 6 — BREAK IT ON PURPOSE (one afternoon, before go-live)
For each case: run it, then read what the model tells the USER.
Fail the case if the user-facing answer is wrong or overconfident.
PASS CRITERIA: the answer to the user distinguishes "I could not
find out" from "there is nothing there". If it does not, the fix
is in Part 1, not in the prompt.
PART 7 — LOG THIS OR YOU CANNOT DEBUG IT
run_id, turn_index
tool_name, arguments (redacted), idempotency_key
status, note, result_bytes, truncated, elapsed_ms
retry_count, bound_hit (none | iterations | clock | identical)
stop_reason, total_tool_calls, total_tool_bytes
The final answer is not enough to reconstruct a run from. The question
you will need to answer later is "what did the model see".