Context Variables and Agent Memory
On this page
Context Variables and Agent Memory
An agent without memory forgets everything between runs: it asks for the customer’s name in every message and starts from scratch each turn. Two complementary mechanisms fix this. Context variables inject shared state into the agent and its tools, while conversational memory maintains the thread of a multi-turn conversation.
Context Variables: Shared State
The RunContextWrapper is a typed object that circulates between the agent, its tools, and its guardrails. Concretely, you define a dataclass describing the session—identity, cart, preferences—and every tool receives it as the first parameter. In the example below, ajouter_au_panier writes to this object and voir_panier reads it back: both tools share the same cart without any data passing through the prompt.
from dataclasses import dataclass, field
from agents import Agent, Runner, RunContextWrapper, function_tool
@dataclass
class SessionUtilisateur:
user_id: str
nom: str
email: str
panier: list[dict] = field(default_factory=list)
preferences: dict = field(default_factory=dict)
@function_tool
def ajouter_au_panier(
ctx: RunContextWrapper[SessionUtilisateur],
produit: str,
quantite: int = 1,
) -> str:
"""Adds a product to the user's cart."""
ctx.context.panier.append({"produit": produit, "quantite": quantite})
total = len(ctx.context.panier)
return f"{produit} (x{quantite}) added to cart. Total: {total} item(s)."
@function_tool
def voir_panier(ctx: RunContextWrapper[SessionUtilisateur]) -> str:
"""Displays the cart contents."""
if not ctx.context.panier:
return "Your cart is empty."
lignes = [f"- {item['produit']} x{item['quantite']}" for item in ctx.context.panier]
return f"Cart for {ctx.context.nom}:\n" + "\n".join(lignes)
agent = Agent(
name="Shop Agent",
instructions="You help customers with their shopping.",
tools=[ajouter_au_panier, voir_panier],
model="gpt-5.6-terra",
)
session = SessionUtilisateur(
user_id="usr_789",
nom="Marie Dupont",
email="[email protected]",
)
# Context is shared across all tools
result = Runner.run_sync(agent, "Add a laptop and two mice to my cart", context=session)
print(result.final_output)
print(f"Cart after execution: {session.panier}")
The context is mutable: tools can modify it, and changes persist after execution. This is what makes the last line interesting—after run_sync, your application’s session object actually contains the two items, ready to be sent to your order system.
Dynamic Instructions
This state isn’t just for tools: it can also shape the agent’s behavior. Instructions can be a function that receives the context and builds the system prompt on the fly, including the customer’s name and preferred language.
def instructions_dynamiques(ctx: RunContextWrapper[SessionUtilisateur]) -> str:
prefs = ctx.context.preferences
langue = prefs.get("langue", "français")
return f"""You are a sales assistant.
The customer's name is {ctx.context.nom}.
Their email is {ctx.context.email}.
They have {len(ctx.context.panier)} item(s) in their cart.
Communicate in {langue}."""
agent = Agent(
name="Personalized Agent",
instructions=instructions_dynamiques,
tools=[ajouter_au_panier, voir_panier],
model="gpt-5.6-terra",
)
Instructions are evaluated on every turn of the agent loop, enabling real-time personalization: if a tool just added an item, the next turn immediately accounts for it in the system prompt.
Multi-Turn Conversational Memory
Context carries data, not the dialogue itself. To maintain a conversation over multiple exchanges, pass the history. The principle comes down to three steps: you add the user’s message, you run the agent on the full list, then you replace your history with the one returned by the result—including messages, tool calls, and responses.
from agents import Agent, Runner
agent = Agent(
name="Assistant",
instructions="You are an assistant that remembers the conversation.",
model="gpt-5.6-terra",
)
historique = []
async def converser(message_utilisateur: str) -> str:
# Add the user's message to the history
historique.append({"role": "user", "content": message_utilisateur})
# Run the agent with the full history
result = await Runner.run(agent, historique)
# Save new exchanges for the next turn
historique.clear()
historique.extend(result.to_input_list())
return result.final_output
# Simulate a conversation
async def demo():
print(await converser("My name is Thomas."))
print(await converser("What is my first name?")) # The agent remembers
print(await converser("Recommend a good restaurant in Paris."))
This memory lives in RAM. It disappears when you redeploy your service, and it grows indefinitely if the conversation lasts: two limits to keep in mind before going to production.
Pattern: Persistent Memory with a Database
For memory that survives restarts, store the history in a database. The approach presented here goes one step further: instead of keeping everything, the agent decides itself what deserves to be retained, via a pair of save and retrieve tools. The customer says “I’m allergic to peanuts,” the agent calls sauvegarder_note, and the information remains available three weeks later.
import json
from agents import Agent, Runner, function_tool
@function_tool
async def sauvegarder_note(ctx: RunContextWrapper, cle: str, valeur: str) -> str:
"""Saves information to remember later."""
# In production: write to database
notes = ctx.context.notes
notes[cle] = valeur
return f"Note saved: {cle} = {valeur}"
@function_tool
async def recuperer_note(ctx: RunContextWrapper, cle: str) -> str:
"""Retrieves previously saved information."""
notes = ctx.context.notes
valeur = notes.get(cle)
if valeur:
return f"{cle} = {valeur}"
return f"No note found for '{cle}'"
@dataclass
class ContexteMemoire:
user_id: str
notes: dict = field(default_factory=dict)
agent = Agent(
name="Assistant with Memory",
instructions="""You are an assistant with persistent memory.
Use sauvegarder_note to retain important information
and recuperer_note to retrieve it.""",
tools=[sauvegarder_note, recuperer_note],
model="gpt-5.6-terra",
)
Context vs. Instructions: When to Use What
These four mechanisms partially overlap, and the classic mistake is to route everything through instructions. The table below summarizes the mapping to remember.
| Need | Solution |
|---|---|
| User data (name, role) | Context variables |
| Agent behavior | Instructions (static or dynamic) |
| Conversation history | result.to_input_list() |
| Long-term memory | Tools + database |
| Runtime configuration | Context variables |
Key Takeaways
RunContextWrappercarries typed state accessible by all tools- Context is mutable: tools can modify it during execution
- Dynamic instructions (functions) adapt to context on every turn
- Conversational history is maintained with
result.to_input_list() - For persistent memory, combine save tools with a database