What happens between an agent asking for a tool and the user receiving an answer? The OpenAI Agents SDK makes that loop explicit enough to inspect without writing every message exchange yourself.
Before you begin: Complete the ADK example or understand Python functions, API keys, and the agent loop.
Give each part one job
An Agent describes instructions, a model, and available capabilities. A Runner executes the interaction. A tool connects the run to an application function. The SDK can manage the sequence, but a tool description does not enforce authorization or establish that returned text is true.
We will reuse the fictional workshop catalog. This time, focus on the runner: a model turn can produce a final answer, a tool request, or a handoff to another agent. A handoff changes which agent continues the conversation. Calling an agent as a tool can leave the original agent responsible for the final answer. These are different control flows, not different levels of intelligence.
Run one read-only experiment
This complete integration script follows the official SDK documentation reviewed on September 10, 2026. Install openai-agents in an isolated Python environment supported by its current package requirements. Set OPENAI_API_KEY and OPENAI_MODEL to an available model with tool support. External model execution is not claimed as tested here.
The current quickstart uses tool from agents.decorators. If you maintain an older installation, follow that installed version's API rather than mixing imports from tutorials written for different releases.
import asyncio
import os
from agents import Agent, Runner
from agents.decorators import tool
@tool
def read_capacity(workshop_id: str) -> dict:
"""Read fixed capacity from a fictional catalog; no availability data."""
if workshop_id != "W1":
return {"status": "not_found"}
return {"status": "found", "id": "W1", "capacity": 12}
async def main():
agent = Agent(
name="Catalog reader",
model=os.environ["OPENAI_MODEL"],
instructions=(
"Use the catalog tool for capacity. Cite the workshop ID. "
"Do not infer remaining seats from capacity."
),
tools=[read_capacity],
)
result = await Runner.run(
agent, "What is the capacity of W1?", max_turns=3
)
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
Three model turns are a limit for this experiment, not a recommended universal setting. Exceeding the limit raises an SDK error. It is not a count of every tool call or a total currency budget. A real service also needs a deadline, tool-specific timeouts, and an intelligible failure response.
Decide what the next request can remember
Run the script twice. The second process does not automatically inherit the first conversation. You must deliberately pass previous input items, configure a session, or choose an appropriate provider-managed continuation mechanism. Bind stored conversations to the authenticated user in your application.
Tracing records how a run unfolded. The SDK documents tracing as enabled by default and offers controls, including OPENAI_AGENTS_DISABLE_TRACING=1. Decide which data may enter traces before using real learner conversations. A trace is for diagnosis; it is not your product's durable lesson-progress record.
Predict the failure boundary
Change the question to “Book every remaining seat.” The only tool reads capacity. What outcome is acceptable? What evidence would reveal a bug?
Inspect the expected behavior
The answer should explain that neither availability nor booking is supported. A claim that a booking was completed is a failure even if the run ended normally. Check actual tool events and the response together. The absence of a write tool limits what the program can do, but it does not prevent the model from making an unsupported claim.
Now lower max_turns to one and ask the capacity question. A tool lookup may require a later model turn to turn its result into an answer. Handle that limit as an incomplete run. This experiment shows why budgets must match the intended path.
Next, examine a framework that organizes work into named tasks and roles, and ask whether that extra structure helps this same problem.
Sources
SDK quickstart, runner lifecycle, and tracing controls document the APIs and distinctions used here.