Langfuse + BotWire: Observability Meets Persistent Memory
Free · Open source (MIT) · Works with LangChain, CrewAI, AutoGen · No signup
You're building agents with Langfuse traces, but your agent loses context between conversations. Memory gets wiped on restarts. State disappears when processes die. You need persistent memory that survives alongside your observability traces, and BotWire Memory solves this exact problem with zero configuration.
The Memory Gap in Agent Observability
Langfuse excels at tracing LLM calls, tracking costs, and debugging agent behavior. But traces only capture what happened — they don't preserve what your agent learned or remembered. When your agent restarts, it's back to square one.
Consider a customer support agent that learns user preferences during a conversation. With Langfuse, you can trace every LLM interaction, see token costs, and debug failures. But if the agent crashes or redeploys, it forgets the customer entirely. The traces remain in Langfuse, but the contextual memory is gone.
This creates a gap: you have perfect observability of stateless interactions, but no persistence for stateful agent behavior. Your agent becomes like a goldfish — perfect memory of the last 30 seconds (the current conversation), but no long-term memory across sessions.
The Fix: Persistent Memory Alongside Traces
BotWire Memory adds persistent key-value storage that survives restarts, redeploys, and process crashes. It works alongside Langfuse without interfering with your existing traces.
pip install botwire
from botwire import Memory
import langfuse
# Initialize both systems
langfuse_client = langfuse.Langfuse()
agent_memory = Memory("customer-agent")
def handle_customer_query(user_id: str, query: str):
# Start Langfuse trace as usual
trace = langfuse_client.trace(name="customer_query")
# Load persistent context
user_prefs = agent_memory.get(f"user:{user_id}:preferences") or {}
conversation_history = agent_memory.get(f"user:{user_id}:context") or []
# Your LLM call with Langfuse observability
response = llm.generate(
query=query,
context={"preferences": user_prefs, "history": conversation_history[-5:]}
)
# Log to Langfuse
trace.generation(name="response", input=query, output=response)
# Persist learnings to BotWire
if "preference" in response.lower():
user_prefs.update(extract_preferences(response))
agent_memory.set(f"user:{user_id}:preferences", user_prefs)
conversation_history.append({"query": query, "response": response})
agent_memory.set(f"user:{user_id}:context", conversation_history[-10:])
return response
How It Works: Memory That Survives Everything
The code above creates a dual-layer system: Langfuse captures the observability, BotWire preserves the state. Your agent memory persists across restarts because BotWire stores data remotely, not in local variables.
Memory operations are simple key-value pairs. Keys should be namespaced (user:123:preferences, session:abc:context) to avoid collisions. Values can be any JSON-serializable data — dictionaries, lists, strings, numbers.
# Memory operations beyond basic get/set
memory = Memory("my-agent")
# List all keys in namespace
all_keys = memory.list_keys()
# Set with TTL (expires after 1 hour)
memory.set("temp:session", data, ttl=3600)
# Delete specific key
memory.delete("old:context")
# Atomic updates for counters
current_count = memory.get("api_calls") or 0
memory.set("api_calls", current_count + 1)
The memory persists across different processes and machines. If you're running distributed agents, they all share the same namespace. This enables patterns like agent handoffs, where one agent stores context and another agent picks it up later.
Cross-process sharing works because BotWire is an HTTP service, not an in-memory cache. Multiple agent instances can read and write the same keys simultaneously. For high-concurrency scenarios, implement optimistic locking by checking timestamps before updates.
LangChain Integration for Chat History
If you're using LangChain with Langfuse, BotWire provides a drop-in chat history adapter that persists conversation memory:
from langchain_openai import ChatOpenAI
from botwire import BotWireChatHistory
from langfuse.callback import CallbackHandler
# Persistent chat history
chat_history = BotWireChatHistory(session_id="user-123")
# LangChain with Langfuse tracing + BotWire memory
llm = ChatOpenAI(callbacks=[CallbackHandler()])
# Chat history survives restarts
llm.invoke("What did we discuss yesterday?", chat_history=chat_history.messages)
The BotWireChatHistory acts like LangChain's built-in memory classes, but data persists in BotWire instead of disappearing when your process exits. Langfuse still captures all the LLM interactions for observability.
When NOT to Use BotWire
BotWire isn't the right tool for every use case. Avoid it when you need:
• Vector search or embeddings — BotWire is key-value storage, not a vector database like Pinecone or Weaviate • Sub-millisecond latency — HTTP round-trips add ~50-200ms, fine for LLM agents but not real-time systems • High-throughput writes — Free tier caps at 1000 writes/day per namespace, though reads are unlimited
FAQ
Why not just use Redis or a database? You can, but then you need hosting, connection management, and credentials. BotWire works instantly with zero setup — no signups, no API keys, no infrastructure.
Is this actually free? Yes, 1000 writes per day per namespace, forever. 50MB storage per namespace. Unlimited reads. We're not trying to monetize hobbyist agent builders.
What about data privacy? BotWire is open source (MIT license) and self-hostable. For production workloads with sensitive data, run your own instance. It's just FastAPI + SQLite.
Get Started Now
BotWire Memory bridges the gap between Langfuse observability and persistent agent state. Your traces remain in Langfuse, your memory survives in BotWire.
pip install botwire
Full documentation and self-hosting guide at botwire.dev.