How to Add an Audit Trail to a LangChain Agent in 15 Minutes with ZizkaDB

· Medium ·

9 min read Original article ↗

Mir Arshad Ali

Press enter or click to view image in full size

zizkadb langchain sdk

This is the next how-to guide in the series. The earlier guides explained what each ZizkaDB function does and how to design for audits and oversight. This one is hands-on: take a LangChain agent, connect it to ZizkaDB, and get to the point where you can ask why the agent did something and see the chain. Every command below comes from the ZizkaDB connection guide, so you can copy it as is.

It is written for developers who already run a LangChain agent, or are about to, and want auditability from day one instead of after the first incident.

A note on scope: this guide uses the self-hosted open source stack on localhost. The managed cloud works with the same SDK, using a live API key instead of a host. Package versions below were current in the repository when I checked, so confirm them on PyPI before you pin anything.

Before you start: what you are building

The goal is a record where every step links to the step that caused it. ZizkaDB does this with an explicit parent_id on each event. A user message is the root, an LLM response points to that message, and a tool call points to the response. Walking back along those links is what why() does, and it is what separates an audit trail from a flat log.

By the end you will have:

A running ZizkaDB stack on your machine Your LangChain agent logging events through a callback handler Named tool calls, so the dashboard shows which tool ran A working why() query on a real agent step Step 1: Start the stack

You need Docker. The recommended path needs no repo clone, and the first image pull can take 5 to 10 minutes:

bash curl -fsSL https://raw.githubusercontent.com/Zizka-ai/ZizkaDB/main/scripts/quickstart-remote.sh | bash

If you would rather work from a clone:

bash git clone https://github.com/Zizka-ai/ZizkaDB.git && cd ZizkaDB bash scripts/quickstart.sh

When it is up, the API runs at http://localhost:8000 and the dashboard at http://localhost:3001/login. On localhost there is no signup and no API key, because a dev key is built in.

Step 2: Check that the stack works before touching your agent

Run the built-in demo first. If something fails here, it is a stack problem, not an agent problem:

bash pip install zizkadb-sdk zizkadb demo

You should see a small chain printed, with a tool call linked to an LLM response linked to a user message. If you get a connection refused error, the stack is not running, so repeat Step 1.

Step 3: Install the LangChain integration bash pip install “zizkadb-sdk>=0.3.0” “zizkadb-langchain>=0.1.3”

Optionally set defaults so you do not repeat them in code:

bash export ZIZKADB_HOST=http://localhost:8000 export ZIZKADB_AGENT=my-bot

Pick the agent name with care. The same name must be used in your code and in the dashboard, and it is how you will filter later.

Step 4: Attach the callback handler

This is the whole integration for a basic LangChain call:

python import asyncio from langchain_openai import ChatOpenAI from zizkadb import ZizkaDB from zizkadb_langchain import ZizkaDBCallbackHandler

async def main(): async with ZizkaDB(host=”http://localhost:8000") as db: handler = ZizkaDBCallbackHandler(db=db, agent=”my-bot”) llm = ChatOpenAI(model=”gpt-4o-mini”) await llm.ainvoke(“Hello”, config={“callbacks”: [handler]})

asyncio.run(main())

The handler goes in the callbacks list of the config you pass to LangChain. For a real agent, pass the same config to your agent executor or graph invocation, so every LLM and tool call inside the run goes through the handler.

If you are starting a new project, you can scaffold one instead:

bash zizkadb init my-agent — template langchain Step 5: Verify in the dashboard

Open the dashboard, go to Activity, and look for my-bot. It should appear within about 30 seconds. Click an event and open the Why? (causal) tab to see its chain.

The connection guide also offers two quick checks. In Settings, Send test event logs to a separate dashboard-connection-test agent, so it never pollutes your real data. And Settings with ?agent=my-bot has a Test agent action.

If nothing shows up, check these in order: the stack is running, the host URL matches, the agent name matches exactly, and the handler is actually in the callbacks of the call that runs.

Step 6: Ask why from the terminal

Pick an event ID from the dashboard and run:

bash zizkadb why <event_id>

Or from Python:

python (await db.why(event_id)).print()

You should see the chain walking back from the step you chose to the user message that started it. Try it on a tool call or an odd answer. This is the moment the integration proves its value, because you are now explaining a real decision from your own agent, from a recorded chain instead of from memory.

Step 7: Name your tools so the data is useful

An event type like tool_call says that a tool ran. Naming it says which one. The connection guide states that the LangChain, LiveKit, and MCP integrations already log tool names this way. For tools or API calls you log yourself, use these helpers:

python call = await db.log_tool(“support-bot”, “lookup_order”, args={“order_id”: “ORD-8842”}) await db.log_tool_result(“support-bot”, “lookup_order”, result=order, parent_id=call.event_id)

on failure:

await db.log_tool_result(“support-bot”, “lookup_order”, error=exc, parent_id=call.event_id)

await db.log_api(“support-bot”, “POST”, “https://api.stripe.com/v1/refunds", status=200, duration_ms=312)

Per the guide, this is what lets the dashboard, reports, and drift detection show per-tool call counts and error rates, and say things like refund calls up 12pp instead of only tool calls up. It also means log_api strips the query string, fragment, and credentials from URLs before logging, which matters for secrets. A 4xx or 5xx status counts toward that API’s error rate.

Pass parent_id whenever you log by hand. A missing parent link is the most common reason a why() chain looks incomplete.

Step 8: Cover parts the handler does not see

The callback handler sees what LangChain sees. Anything outside it, such as a database write, a call to an internal service, or a human approval, will not appear unless you log it. Use db.log() with an explicit parent_id for those:

python user = await db.log(agent=”my-bot”, event=”user_message”, data={“text”: “Why is my order late?”}) await db.log(agent=”my-bot”, event=”tool_call”, data={“tool”: “lookup_order”}, parent_id=user.event_id)

Check coverage with a simple test. Run one scripted conversation, open its session in the dashboard, and compare it with what you know the agent did. Any step you can name that is missing from the timeline is an instrumentation gap. Fix those before relying on the record for anything important.

Step 9: Keep secrets and personal data out of events

Logged content is stored. Before going further, decide what must never be logged: API keys, credentials, and unnecessary personal data. Redact before you log, since changing a stored event afterwards would break its integrity checksum. If you handle personal data, tag events consistently so erasure with db.forget() can find everything that belongs to one person, using a metadata filter.

For telemetry, the project collects an anonymous install ping from the MCP server, which you can opt out of with export ZIZKADB_TELEMETRY=false.

Step 10: Move toward production

Localhost uses an auto-injected dev key, which is not suitable for a server. The self-hosting steps in the guide for a VPS are:

In infra/.env, set ENV=production, DEPLOYMENT_MODE=self_hosted, and SELFHOST_ADMIN_TOKEN to a long random value. Run docker compose -f infra/docker-compose.yml up -d. Run bash infra/deploy-selfhost.sh. Open the dashboard, enter the admin token, and create API keys in Settings. Use those keys in your SDK and MCP configuration.

Then check the wiki pages on self-hosting and production deployment for backups, TLS, and upgrades, which this guide does not cover. If you would rather not run infrastructure, the managed cloud takes the same SDK with a live key.

What the business case looks like

These figures are modeled assumptions, not measured results. Replace them with your own.

Assume a team running one LangChain agent has 10 incidents a month at 100 euro per engineering hour. Without a causal record, each takes about 5 hours of reading scattered logs and guessing at sequence, costing 5,000 euro a month. With why() and replay on a connected trail, each takes about 1 hour, costing 1,000 euro a month. That saves roughly 4,000 euro a month.

The setup is a one-time cost. For a single LangChain agent, the handler attach plus a few hand-logged steps is plausibly a few hours, and the stack setup is under an hour apart from the image download. At 100 euro per hour, that is a few hundred euro, so the payback is measured in days if the incident volume is real.

The self-hosted edition is free under AGPL-3.0. The managed cloud is listed at 29 euro a month for Pro and 69 euro for Team, so choose based on whether you would rather pay for hosting or run it yourself.

The larger value arrives later, when a customer, an auditor, or your own risk team asks why the agent did something and you can show the chain instead of reconstructing it.

Common mistakes to avoid

Mismatched agent names. The name in code and in the dashboard must be identical, or events seem to vanish.

Forgetting the handler in the call that runs. The callback must be in the config of the invocation that executes the agent, not only on a spare instance.

Missing parent links on hand-logged events. Without parent_id, a why() chain stops early.

Logging secrets. Redact before logging, because stored events are checksummed.

Trusting an unchecked trail. Run one scripted conversation and compare the timeline to what really happened.

Using the dev setup in production. Replace the auto dev key with real keys and an admin token before exposing anything.

Where to go from here

Run the quickstart, attach the handler, and ask why on one real step. That is the whole integration, and it takes minutes. After that, add named tools and hand-logged steps for anything LangChain does not see, then work through the earlier guides for drift monitoring, point-in-time reconstruction, and audit preparation, since they all build on the same record.

If you tell me which agent framework and model provider you use, I can help adapt these steps and test the coverage of your trail.

Want to test ZizkaDB on your agent? Try our open source version here: https://github.com/ZIZKA-AI-SL/ZizkaDB

Interested in a design partnership? Fill in the form on our site or reach me directly at founder@zizka.ai

Sources: ZizkaDB connection guide (CONNECT.md) · ZizkaDB repository README