Arize Phoenix + Agent Memory: Tracing Stateful Agents
Free · Open source (MIT) · Works with LangChain, CrewAI, AutoGen · No signup
Tracing agent behavior across sessions is broken in most setups. Your LLM agents forget everything between runs, making Arize Phoenix show disconnected traces instead of coherent user journeys. You need persistent memory that survives restarts and integrates with Phoenix's tracing infrastructure.
The Agent Memory Problem
Standard agent frameworks store conversational state in memory variables or session objects. When the process restarts, container redeploys, or you switch between development machines, that state vanishes. Your Phoenix traces show individual interactions, but miss the broader context of how agents evolve their understanding over time.
This breaks agent evaluation in Phoenix. You can't measure how well your agent learns from past interactions, maintains user preferences, or builds upon previous conversations. Each trace looks like a fresh start, even when users expect continuity.
Consider a support agent that should remember a user's previous issues, preferred communication style, or resolved tickets. Without persistent memory, Phoenix shows each interaction as isolated, making it impossible to evaluate the agent's contextual performance or identify patterns in multi-session user journeys.
The Fix: Persistent Memory with BotWire
Install BotWire to add persistent key-value memory that survives restarts and integrates seamlessly with Phoenix tracing:
pip install botwire
Here's a basic agent with persistent memory:
from botwire import Memory
import openai
# Create persistent memory namespace
agent_memory = Memory("support-agent")
def handle_user_request(user_id: str, message: str):
# Retrieve user context
user_context = agent_memory.get(f"user:{user_id}:context") or {}
previous_issues = agent_memory.get(f"user:{user_id}:issues") or []
# Build prompt with persistent context
context_prompt = f"User history: {previous_issues}\nCurrent context: {user_context}"
response = openai.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": context_prompt},
{"role": "user", "content": message}
]
)
# Update persistent memory
previous_issues.append({"message": message, "timestamp": "2024-01-15"})
agent_memory.set(f"user:{user_id}:issues", previous_issues)
return response.choices[0].message.content
How It Works
The Memory class provides a simple key-value interface backed by BotWire's HTTP API. Data persists across process restarts, making your Phoenix traces show true multi-session behavior.
Memory operations are straightforward:
memory.set(key, value)- stores any JSON-serializable datamemory.get(key)- retrieves data, returnsNoneif missingmemory.delete(key)- removes a keymemory.list_keys(prefix)- finds keys starting with prefix
For structured agent state, use namespaced keys:
from botwire import Memory
agent_memory = Memory("customer-service")
# Store user preferences
agent_memory.set("user:42:preferences", {
"communication_style": "formal",
"timezone": "EST",
"language": "en"
})
# Store conversation summaries
agent_memory.set("user:42:summary", "VIP customer, prefers email over phone")
# List all user data
user_keys = agent_memory.list_keys("user:42:")
print(user_keys) # ['user:42:preferences', 'user:42:summary']
Memory persists across machines and deployments. If you run the same namespace on different servers, they share the same memory space. This enables distributed agents that maintain consistent state.
The free tier provides 1000 writes per day per namespace with unlimited reads - sufficient for most development and small production workloads.
Phoenix Integration Pattern
When tracing agents with Phoenix, include memory operations in your spans to show how persistent state influences agent decisions:
from botwire import Memory
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
memory = Memory("traced-agent")
def process_with_memory(user_id: str, query: str):
with tracer.start_as_current_span("agent_process") as span:
# Trace memory retrieval
with tracer.start_as_current_span("memory_lookup") as memory_span:
user_state = memory.get(f"user:{user_id}")
memory_span.set_attribute("memory.keys_found", len(user_state or {}))
# Your agent logic with memory context
response = your_llm_call(query, context=user_state)
# Trace memory updates
with tracer.start_as_current_span("memory_update"):
updated_state = {**user_state, "last_query": query}
memory.set(f"user:{user_id}", updated_state)
span.set_attribute("agent.memory_namespace", "traced-agent")
return response
When NOT to Use BotWire
BotWire isn't the right choice for:
- Vector similarity search - it's key-value only, not a vector database
- High-throughput caching - Redis will outperform for >10k ops/second
- Sub-millisecond latency - HTTP overhead makes it slower than in-memory solutions
FAQ
Why not just use Redis? Redis requires setup, authentication, and infrastructure management. BotWire works immediately with no configuration - just import and use.
Is this actually free? Yes, 1000 writes/day per namespace forever. No credit card, no signup, no API keys. You only pay if you need higher limits.
What about data privacy? Self-host the open source version (MIT license) if you need full control. The hosted version at botwire.dev stores data encrypted at rest.
Get Started
Add persistent memory to your agents and see the full picture in Phoenix traces. Install BotWire and your agents will maintain state across sessions, giving you meaningful evaluation data.
pip install botwire
Check out the full API and self-hosting options at botwire.dev.