Passing data with RunContextWrapper
RunContextWrapper is a wrapper the SDK hands to your tools that carries the object you passed to the run: a tool declares ctx: RunContextWrapper[Customer] and reads ctx.context.
Last updated: 28 Sep, 2026 · openai-agents 0.22.3
The model should never be trusted to supply who the caller is. You know the customer before the run starts, so you pass it in as context and let the tool read it, instead of hoping the model fills a tool argument right.
A tool that takes RunContextWrapper
Make the first parameter of the tool ctx: RunContextWrapper[T]. The SDK fills it in for you, and ctx.context is the object you gave to the run. This first parameter is not part of the tool's schema, so the model never sees it or fills it.
@function_tool
def my_order(ctx: RunContextWrapper[Customer]) -> str: # first param is context
return ctx.context.order_id # the caller data, not a model argumentThe context object
Any object works. A dataclass is a small, clear one. It holds what you know about the caller before the run.
from dataclasses import dataclass
@dataclass
class Customer:
name: str # who is calling
order_id: str # their order, known before the runA tool that reads the caller
The tool takes no order id from the model. It reads the id from the context, so it always answers about the caller, not about whatever the model typed.
@function_tool
def my_order(ctx: RunContextWrapper[Customer]) -> str:
"Look up the calling customer's own order."
c = ctx.context # the object you passed to the run
return f"{c.name}'s order {c.order_id}: shipped on 3 March."Passing context into the run
Pass the object as context= on the run. It reaches the tool through ctx.context. Here ToolModel is a stand-in that calls the tool once, then returns the tool's output as the reply; the full program shows it.
agent = Agent(name="Desk", instructions="Help with the caller's order.",
tools=[my_order], model=ToolModel())
# context=... reaches every tool through ctx.context
Runner.run_sync(agent, "where is my order?", context=Customer("Ada", "A17"))Answering two callers from context
The same agent and the same question run twice, with a different Customer each time. The reply changes because the tool read the context, not a model argument.
from dataclasses import dataclass
from agents import Agent, Runner, function_tool, RunContextWrapper, set_tracing_disabled
from agents.models.interface import Model
from agents.items import ModelResponse
from agents.usage import Usage
from openai.types.responses import (
ResponseOutputMessage, ResponseOutputText, ResponseFunctionToolCall,
)
set_tracing_disabled(True)
@dataclass
class Customer:
name: str
order_id: str
@function_tool
def my_order(ctx: RunContextWrapper[Customer]) -> str:
"Look up the calling customer's own order."
c = ctx.context
return f"{c.name}'s order {c.order_id}: shipped on 3 March."
def _message(text):
return ResponseOutputMessage(
id="msg", role="assistant", type="message", status="completed",
content=[ResponseOutputText(text=text, type="output_text", annotations=[])],
)
def _tool_call(name, arguments):
return ResponseFunctionToolCall(
id="fc", call_id="call_1", name=name, arguments=arguments, type="function_call",
)
class ToolModel(Model):
async def get_response(self, system_instructions, input, model_settings, tools,
output_schema, handoffs, tracing, **k):
if isinstance(input, list):
for it in reversed(input):
d = it if isinstance(it, dict) else it.__dict__
if d.get("type") == "function_call_output":
return ModelResponse(output=[_message(d.get("output", ""))],
usage=Usage(), response_id=None)
return ModelResponse(output=[_tool_call("my_order", "{}")],
usage=Usage(), response_id=None)
async def stream_response(self, *a, **k):
raise NotImplementedError
agent = Agent(name="Desk", instructions="Help with the caller's order.",
tools=[my_order], model=ToolModel())
print(Runner.run_sync(agent, "where is my order?",
context=Customer("Ada", "A17")).final_output)
print(Runner.run_sync(agent, "where is my order?",
context=Customer("Sam", "B92")).final_output)Why the two replies differ
- The id came from context: the tool never took an
order_idargument, so the model could not change it. - Ada saw A17, Sam saw B92: only
context=differed between the two runs. - ctx is not in the schema: the model was asked to call
my_orderwith no arguments, and it did.
Context data vs a tool argument
| Tool argument | Context (ctx.context) | |
|---|---|---|
| Who supplies it | the model | your code, before the run |
| In the tool schema | yes | no |
| Can the model change it | yes | no |
| Good for | what the model decides | who the caller is, secrets, handles |
When to reach for context
- Identity: the current user or customer, so a tool acts as them.
- Handles: a database connection or an API client a tool needs but the model should not see.
ctx anywhere but the first parameter, or leaves off the type, the SDK cannot fill it and the call fails.Related
- Previous: Structured output with output_type
- Next: Instructions from a function
- Reference: Context: local context
- Add an
email: strfield toCustomerand return it from the tool. - Run a third time with
Customer("Lee", "C33")and read the reply. - Remove
ctxfrom the tool signature and read the error the run gives.
Slow is fine. Stopping is the only problem.