Somebody on the compliance side has just asked whether the LangChain agent has an audit trail, and the engineer’s honest first thought is “yes — it’s all in LangSmith”. Every chain run, every tool call, inputs and outputs, latency, cost, a trace tree you can click through. What else would an audit trail be?
The answer is that a trace and an audit trail are different artefacts built for different readers, and confusing them is the most common way a team ends up with two years of beautiful traces and nothing an auditor will accept. This is the difference, precisely, and the good news at the end: if you already trace, you have done most of the work.
What a trace is for
A trace answers why did the chain do that? for an engineer, usually the same day. It is optimised for that: full payloads, because you need to read the prompt; a tree, because you need to see the retrieval that fed the generation; fast search; and cheap, short-lived storage, because traces are enormous and most are never opened. LangSmith, Langfuse and Phoenix are all good at this. We use two of them.
What an audit trail is for
An audit trail answers prove to me that this happened, in this order, and that nobody changed the record afterwards— for a reader who does not trust you, months later. That reader is an auditor, a regulator, a customer’s security reviewer, or opposing counsel. It needs four properties a trace does not have, and it is missing them not because the observability tools are bad but because nobody asked them to be evidence.
| The question | A LangSmith / Langfuse trace | An audit trail |
|---|---|---|
| Who reads it | An engineer, debugging | An auditor, a regulator, a security reviewer |
| How long it lives | Days to weeks by default; self-hosters decide | As long as the obligation — EU AI Act Art. 19 says six months minimum; financial records run to years |
| Can it be edited | Yes. It is a row in a database, and nothing in the trace would show an edit | Each entry carries a hash of the previous one; any edit breaks every link after it |
| Who approved the action | The service account that called the API | A named person, from the authenticated session, recorded before execution |
| Can it stop anything | No — a trace is written after the call returns | Yes — policy runs before the action, and the refusal is recorded beside it |
| Can an outsider verify it | They read what you export and take your word | They run a verifier offline with public keys, no account with anyone |
The six-month reference in the table comes from Article 19 of the EU AI Act; sector-specific obligations can require longer retention.
The four properties, one at a time
Retention. The easy half, and the one self-hosting Langfuse genuinely solves — if you set it. But a stored trace is still a mutable trace, so retention alone gets you more of a record that was never built to be evidence.
Tamper evidence.Not access control. “Only admins can edit” says who could have; a hash chain with an independently timestamped head says nobody did, and lets a stranger check.
A named human. A trace records the caller. For anything material — money, data about a person, a decision — the auditor wants the person who authorised it, and wants that identity to have come from the session, not from a field the agent populated.
Pre-execution. A trace is a record of the past. A policy check has to run before the tool call, or it is not a control; it is a report of a control you did not have.
The good news: same spans
If you emit OpenTelemetry — and LangSmith, Langfuse and Phoenix all can, via OpenInference or their own exporters — the instrumentation is done. An audit system of record reads those spans downstream: model calls become recorded actions, tool executions become tool calls with a policy verdict, payloads become fingerprints (so the prompt itself never has to leave your systems). You keep the trace viewer for debugging. You add the chain for evidence.
# You already have this from langsmith import traceable # Add this — it reads the same spans import auditant auditant.init(api_key="ak_…", agent_id="refund-agent") @traceable # LangSmith: why did it do that? @auditant.record(policy="refunds") # Auditant: prove that it did, and who approved def process_refund(order_id: str, amount: float): ...
Two decorators on the same function, two artefacts for two readers. The observability tool is not replaced, and we would rather say so than pretend a trace viewer with long retention is the same thing.
Do you need both?
- No regulator, no auditor, no enterprise security questionnaire: you need observability, not this. Keep LangSmith and stop reading.
- Agents that move money, decide about people, or touch regulated data: you need both, and the second one is the one that is currently missing.
- Either way, write down what each plane retains today. The number is usually smaller than the team believes.
The longer version of this argument is at /llm-observability; the field-by-field spec of what the record has to hold is at /ai-agent-audit-trail.
See it on a real chain
The evidence path described above is running on our own account. Every ad-account change made by the agents behind Admiral, our paid-ads product, is recorded on the chain admiral/prod — actions on the chain, seals countersigned, and the sealed-through sequence are read live at /trust, not written into the page.