An assistant looks up a room, finds that it is unavailable, and searches for another. Where did the second search come from? That decision is the useful place to start understanding agents.
Before you begin: Complete Chains, agents, and tools. You should understand a function call and a saved conversation.
Find the decision in the loop
In a fixed workflow, your program determines the steps. In an agent loop, a model can propose the next step using the information available so far. The surrounding program still decides which proposals are allowed and executes the tools. A model that prints “reservation confirmed” has not reserved anything.
Think of a run as a sequence of observations and decisions. The user supplies a goal. The model sees instructions and available tool descriptions. It proposes a call. Your program checks the arguments and permission, executes the function, and returns its result. The model can then answer, ask for clarification, or propose another call. The original ReAct research explored this combination of reasoning and interaction; it did not establish that any unrestricted loop becomes reliable.
Follow one request
Our fictional request is “Find a room for 12 people after 2 pm.” The available tools are list_rooms and read_availability. Neither tool can create a booking.
| Step | New evidence | Reasonable next step |
|---|---|---|
| List rooms | A seats 8; B seats 16 | Discard A for this request |
| Read B | Free from 3 pm | Report B at 3 pm |
| User asks to book | No booking tool exists | Explain the limit |
The model is useful for interpreting the request and selecting a relevant lookup. Capacity checks can remain ordinary code. Making every comparison another model call would add cost and another opportunity for error.
Now change one observation: B's availability service times out. “No room is free” is no longer supported. A failed lookup and a successful empty result are different states. Preserve that distinction in tool output and in the answer.
Define progress before adding autonomy
A state is the information the application keeps about the run: request, evidence, pending calls, results, and status. A policy is the rule for choosing or allowing a next action. Some policy lives in instructions; hard limits belong in code.
For this task, progress means learning something new about a suitable room. Repeating the same failed call without a changed condition is not progress. Set a deadline and a bounded retry rule. Count model turns, tool calls, and elapsed time separately. One model response can contain more than one tool call.
An illustrative control policy, not executable Python, is:
Validate the request.
While time and call budgets remain:
Ask for an answer or a permitted lookup.
Reject invalid or repeated calls without new evidence.
Record the lookup result, including failures.
Stop when an answer is supported or clarification is needed.
Return the answer, its evidence, and the run status.
Decide how this run should end
The model calls read_availability("B") three times and receives the same timeout. It then proposes book_room("B"). What should the application do, and what should the user see?
Inspect the decision
Reject the unavailable booking tool and stop the repeated lookup under the retry policy. Report that B fits the size requirement but availability could not be confirmed. Do not imply a booking happened. The useful record includes the attempted lookup and failure status, not a fabricated successful outcome.
Sketch a second run where the user never supplied the number of people. A good first step is a question, because room capacity cannot be evaluated yet. This is an agent doing less work to reach a better result.
Next, put this boundary into a framework and inspect which parts it makes easier to express.
Sources
ReAct paper introduces interleaved reasoning and action. OpenAI Agents SDK running guide documents one concrete runner loop and its limits.