Context in the OpenAI Agents SDK is a powerful mechanism for sharing state across tool calls, guardrails, handoffs, and lifecycle hooks. Think of Context as a "shared workspace" or "carrying case" that travels with the agent run, allowing you to pass custom data, configuration, and state to various components without relying on global variables or complex threading mechanisms.
Core Concepts
What is Context?
Context is a user-defined object that:
Carries custom data throughout an agent run
Is accessible from tools, guardrails, handoffs, and hooks
Is mutable - can be modified during execution
Is type-safe - uses generics for compile-time checking
Is isolated - each run has its own context instance
ToolContext extends RunContextWrapper with tool-specific metadata:
Python
@dataclassclassToolContext(Generic[TContext]):
# All RunContextWrapper fields...
tool_namespace: str | None"""Tool namespace if configured."""
tool_origin: ToolOrigin | None"""Origin of the tool."""
# Each run has its own context instance# So no cross-run interference# But if you share mutable objects across runs:
shared_data = {} # Not thread-safe!@dataclassclassSharedContext:
data: dict# This is shared!# Use with caution or use thread-safe structures
Context and Guardrails
Input Guardrails with Context
Python
from agents import input_guardrail
@input_guardraildefcheck_user_limits(
context: RunContextWrapper[UserContext],
agent: Agent,
input: str | list[TResponseInputItem],
) -> InputGuardrailFunctionOutput:
"""Check if user has reached limits."""
user_id = context.context.user_id
limits = get_user_limits(user_id)
if limits["remaining"] <= 0:
return InputGuardrailFunctionOutput(
output_info="User has reached limits",
tripwire_triggered=True,
)
return InputGuardrailFunctionOutput(
output_info="User has capacity",
tripwire_triggered=False,
)
Output Guardrails with Context
Python
@output_guardraildefcheck_output_size(
context: RunContextWrapper[UserContext],
agent: Agent,
agent_output: str,
) -> OutputGuardrailFunctionOutput:
"""Check output size against user limits."""
max_size = context.context.preferences.get("max_output_size", 1000)
iflen(agent_output) > max_size:
return OutputGuardrailFunctionOutput(
output_info=f"Output too large (max {max_size})",
tripwire_triggered=True,
)
return OutputGuardrailFunctionOutput(
output_info="Output size acceptable",
tripwire_triggered=False,
)
Context and Handoffs
Handoff Functions with Context
Python
defon_handoff(
context: RunContextWrapper[UserContext],
input: HandoffInputData,
) -> None:
"""Called during handoff."""
user_id = context.context.user_id
print(f"Handing off for user: {user_id}")
handoff(
specialist,
on_handoff=on_handoff,
)
Handoff Input Filter with Context
Python
deffilter_handoff_input(
data: HandoffInputData,
) -> HandoffInputData:
"""Filter handoff input."""# Access context via run_context
user_id = data.run_context.context.user_id
# Filter based on userif user_id == "special":
return data # Full contextelse:
return data.clone(new_items=data.new_items[-5:]) # Limited context
Context and Lifecycle Hooks
Run Hooks with Context
Python
from agents import RunHooks
classMyRunHooks(RunHooks[UserContext]):
asyncdefon_agent_start(
self,
context: AgentHookContext[UserContext],
agent: Agent,
) -> None:
"""Called when agent starts."""
user_id = context.context.user_id
print(f"Agent {agent.name} starting for user {user_id}")
asyncdefon_agent_end(
self,
context: AgentHookContext[UserContext],
agent: Agent,
output: Any,
) -> None:
"""Called when agent ends."""
user_id = context.context.user_id
print(f"Agent {agent.name} ended for user {user_id}")
hooks = MyRunHooks()
result = await Runner.run(agent, input, hooks=hooks)
Agent Hooks with Context
Python
from agents import AgentHooks
classMyAgentHooks(AgentHooks[UserContext]):
asyncdefon_start(
self,
context: AgentHookContext[UserContext],
agent: Agent,
) -> None:
"""Called when this agent starts."""
user_id = context.context.user_id
print(f"My agent starting for user {user_id}")
agent = Agent(
name="my_agent",
instructions="...",
hooks=MyAgentHooks(),
)
defget_model_for_user(context: RunContextWrapper[UserContext]) -> str:
"""Select model based on user tier."""
tier = context.context.preferences.get("tier", "free")
if tier == "premium":
return"gpt-4o"elif tier == "enterprise":
return"gpt-4-turbo"return"gpt-4o-mini"
Context and RunState
Context in RunState
RunState includes context:
Python
result = await Runner.run(agent, input, context=my_context)
state = result.to_state()
# Access context
saved_context = state.context
# Good - focused context@dataclassclassUserContext:
user_id: str
preferences: dict# Avoid - bloated context@dataclassclassEverythingContext:
user_id: str
preferences: dict
database_connection: Any# Don't put heavy resources here
cache: dict
logger: Any
3. Use Type Hints
Always use type hints:
Python
# Good@dataclassclassMyContext:
user_id: str
data: dict[str, Any]
# Avoid@dataclassclassMyContext:
user_id # No type hint
data
4. Document Context Fields
Document context fields:
Python
# Good@dataclassclassMyContext:
"""Context for user operations."""
user_id: str"""Unique identifier for the user."""
preferences: dict"""User preferences (theme, language, etc.)."""# Avoid@dataclassclassMyContext:
user_id: str# What is this?
preferences: dict# What keys?
5. Avoid Circular Dependencies
Avoid circular references in context:
Python
# Good - no circular references@dataclassclassContextA:
data: str@dataclassclassContextB:
context_a: ContextA
# Avoid - circular reference@dataclassclassContextA:
context_b: ContextB
@dataclassclassContextB:
context_a: ContextA