Causal Lineage and Session Replay with ZizkaDB ZizkaDB, an open-source operational database for LLM agents, introduces causal lineage and session replay features to address behavioral debugging gaps in traditional tracing tools. The database stores agent decisions as a directed acyclic graph with parent links, enabling a why() function that traces the causal chain behind any output. Self-hosting is available via a quickstart script, and the Python SDK allows explicit event logging with parent_id references. If you've shipped an LLM agent to production, you know the failure mode: a customer says the bot gave a wrong answer, you open your logs, and you see a wall of spans that tell you what happened but not why. The prompt changed three deploys ago. The agent skipped a tool call. A retrieval step pulled a stale document. Nothing in a flat trace tells you the causal chain that led to the bad output. ZizkaDB is an open-source operational database built specifically for this problem. Instead of storing spans like a tracing tool, it stores agent decisions as a graph, where each event points to the event that caused it, plus session-level replay and drift detection against a baseline. This post walks through the two features that make it different from a generic tracing setup: causal lineage why and session replay, with working code. Why not just use a tracer? Distributed tracing tools Langfuse, LangSmith, Phoenix give you a span tree: this call started, this call ended, here's the latency. That's useful for performance debugging. It's much weaker for behavioral debugging, where the question isn't how long did this take but what earlier decision caused this one. ZizkaDB models that explicitly by making every logged event optionally declare its parent id, turning a session into a directed acyclic graph of decisions instead of a list of timestamps. Setup Self-hosting is one script: bash git clone https://github.com/Zizka-ai/ZizkaDB https://github.com/Zizka-ai/ZizkaDB cd ZizkaDB bash scripts/quickstart.sh This pulls the pre-built images, starts the API on localhost:8000, and opens a dashboard at localhost:3001 with no signup required for local dev. If you'd rather skip the clone entirely: bash curl -fsSL https://raw.githubusercontent.com/Zizka-ai/ZizkaDB/main/scripts/quickstart-remote.sh https://raw.githubusercontent.com/Zizka-ai/ZizkaDB/main/scripts/quickstart-remote.sh | bash Install the Python SDK: bash pip install "zizkadb-sdk =0.2.7" The SDK is stateless by design: you pass agent, session id, and event id explicitly on every call rather than relying on hidden global state. That matters once you're running multiple agents or worker processes against the same store. Logging events with parent links Here's the core primitive. Every call to db.log returns an event id, and you pass that as parent id on whatever event it caused: python import asyncio from zizkadb import ZizkaDB async def main : async with ZizkaDB host=" http://localhost:8000" http://localhost:8000%22 as db: user msg = await db.log agent="support-bot", session id="session-4821", event="user message", data={"text": "How long do refunds take?"}, retrieval = await db.log agent="support-bot", session id="session-4821", event="tool call", data={"tool": "search policy docs", "query": "refund window"}, parent id=user msg.event id, response = await db.log agent="support-bot", session id="session-4821", event="assistant response", data={"text": "Refunds take 30 days."}, parent id=retrieval.event id, asyncio.run main Three events, two causal edges: the tool call was caused by the user message, and the response was caused by the tool call. That chain is the whole point. It's what lets you ask why the agent said this and get an actual answer instead of a timestamp-sorted guess. Causal lineage: why Given any event id, why walks the parent chain backward and returns the decision path that produced it: python result = await db.why response.event id result.print assistant response "Refunds take 30 days." ↑ caused by tool call search policy docs "refund window" → outdated faq chunk.md ↑ caused by user message "How long do refunds take?" This is the difference between a span tree and a lineage graph in practice: instead of scanning a trace for the surrounding calls and inferring causation yourself, you get the causal chain directly. In the incident this is modeled on, why on the bad response is what surfaces that search policy docs returned an outdated FAQ chunk instead of the current policy doc: the actual root cause, not just a tool being called. Session replay why traces one decision. Session replay reconstructs the entire session: every message, tool call, and response in order, with the state the agent had at each point. python session = await db.replay agent="support-bot", session id="session-4821" for event in session.events: print f"{event.timestamp} {event.event} {event.data}" 14:01:58 session start {} 14:02:09 user message {"text": "How long do refunds take?"} 14:02:11 tool call {"tool": "search policy docs", "result": "outdated faq chunk.md"} 14:02:12 assistant response {"text": "Refunds take 30 days."} The dashboard renders this same data as a timeline you can step through, which is where it's genuinely faster than grepping logs: you see exactly what the agent knew, including which documents it retrieved and which tool results it had, at the moment it generated the wrong answer. That's time travel over logged state rather than replaying UI interactions the way session-replay tools for web apps do; here the format is per-event input/output data. Catching it before a customer does: drift baselines Lineage and replay are for root-causing an incident you already know about. baseline is for catching the regression before that. Once you have enough sessions logged, you snapshot known-good behavior and compare new sessions against it: python baseline = await db.baseline agent="support-bot", label="pre-prompt-v2" drift = await db.check drift agent="support-bot", against="pre-prompt-v2" if drift.flagged: for change in drift.changes: print f"Drift on {change.topic}: {change.summary}" In the ZizkaDB docs' worked example, this is exactly what flags the refund-policy regression: check drift reports that refund answers changed shape after the prompt v2 deploy, pointing you at why for the specific session before a customer files a ticket. REST, if you're not in Python or TS Everything above also has a plain REST API, useful if your agent runtime isn't Python or TypeScript: bash curl -s -H "Authorization: Bearer zizkadb dev local" \ -H "Content-Type: application/json" \ -d '{ "agent": "support-bot", "session id": "session-4821", "event": "tool call", "data": {"tool": "search policy docs"}, "parent id": "evt 9f2a" }' \ http://localhost:8000/v1/events http://localhost:8000/v1/events Swagger docs are served at http://localhost:8000/swagger http://localhost:8000/swagger on self-hosted instances. There's also first-party support for LangChain ZizkaDBCallbackHandler , CrewAI ZizkaDBCrewLogger , and an MCP server for Cursor/Claude Desktop if you want lineage and replay available as tools inside your editor rather than only in the dashboard. Where this fits If you already have a tracer for latency and cost, you probably don't need to rip it out. What ZizkaDB is solving is a narrower, sharper problem: when an agent's behavior is wrong, not just slow, why and session replay get you from customer complaint to root cause without reading logs. The parent id graph is the whole mechanism, and it's simple enough to bolt onto an existing agent in an afternoon: three extra db.log calls in the example above is most of the integration work. Repo: ZizkaDB //github.com/Zizka-ai/ZizkaDB AGPL, self-host free . Managed cloud with a hosted dashboard is at db.zizka.ai if you'd rather not run Docker.