The agent that kept calling its own tool for 40 minutes: finish_reason, stop conditions, and the loop guard
Problem
An order-lookup agent burned through its context and $4 in one session:
// log: model called search_orders → got [] → called search_orders again (identical args)
// × 26 turns, then: openai.BadRequestError: max_tokens + tool calls exceeded contextThe model never said anything to the user. It just kept calling the same tool with the same arguments until the context window filled.
Root cause
Tool loops are not a model bug; they are a missing loop contract. Three holes, all present:
1. Empty results fed back raw: {"result": []} gives the model nothing to reason about, so it retries — with the identical query, because nothing suggests a different one. 2. No stop condition: the loop ran until the API rejected it. Nothing counted turns, nothing detected repetition, nothing escalated to the user. 3. Errors as strings: a tool exception returned "Error: timeout", which the model treats as another input to retry rather than a state to report.
function searchOrders(args: SearchArgs) {
const rows = db.searchOrders(args);
return rows.length
… 3 more lines in the fix🔒 the fix — including 3 code blocks — is members-only. $1/mo unlocks everything.