Deterministic · judge-free
Your agent said it worked. The trace says otherwise.
tracelint is a structural linter for tool-calling agents. It reads the trace you already collect and proves the defects a fluent final answer hides — with a CI exit code, and no model in the loop.
A sample agent run. Hover any finding to see what tracelint caught.
get_order failed with a 500 — but its order id A100 was passed straight into the side-effecting refund_order. The refund ran on data from a call that had already errored.
Why it exists
The bugs that never reach the answer.
A fluent final message passes your evals and your LLM judge — while the run underneath already went wrong. These defects are structural, and decidable straight from the trace:
deploy tool returns a 500, and the agent reports success and moves on. The error is right there in the span.{"status":"declined"} over a 200. Nothing technically errored, so exception handling never fires.send_email runs twice with identical args. It fires twice; the answer looks perfect.The tool changes — a charge, a database write, a file delete, a deploy — but the structural bug is the same, and it's decidable straight from the trace.
How it works
It runs after the run, on the trace — never on a model.
Many agent failures aren't a matter of taste; they're decidable from what the agent actually did. tracelint decides them, the same way every time.
Read the trace
Point it at the spans you already emit — Phoenix, Langfuse, LangSmith, OpenAI, OTel GenAI — normalized into one canonical schema.
Apply the rules
Deterministic checks replay the trace: schema violations, ignored errors, reused failed values, loops, duplicate side effects — each with the exact steps as evidence.
Return an exit code
A structurally-provable defect exits non-zero and fails the build. Everything else is disclosed, never silently passed.
The checks
What it catches
Deterministic checks across schema, error handling, provenance, control flow, and side effects. Every finding is filed under one of three tiers — and only the first one fails your build:
Provable from the trace itself. Fails CI — exit 2, the build stops.
A certain fact — a tool errored, a side effect repeated. Reported, but doesn't fail CI.
A heuristic, shown with its evidence for you to judge. Never fails CI on its own.
failure_when fired on a 200.Works on the traces you have
Bring your own telemetry.
tracelint reads the exports from the tools you already run. One canonical schema underneath, so every rule reaches every source.
Arize Phoenix openinference
Lint the OpenInference spans Phoenix collects — reads get_spans_dataframe() directly.
for t in load_source("spans.json", "openinference"):
print(render_report(lint_trace(t, default_rules(), reg)))
Langfuse langfuse
Fetch a trace with the SDK and hand it straight to tracelint — object or raw JSON.
trace = langfuse.api.trace.get("id")
print(render_report(lint_langfuse_trace(trace, registry=reg)))
OpenLLMetry / Traceloop otel
The event-list reader understands the OTel GenAI semantic convention out of the box.
tracelint check spans.json --format otel --tools tools.json
OpenAI & LangSmith openai · langsmith
Chat-completion message lists (incl. ShareGPT) and nested LangSmith run trees.
tracelint check run.json --format langsmith --tools tools.json
Custom format? Map it to the canonical Trace schema — messages, tool calls, and paired results — in a few lines, and every rule applies. The tool contract (side_effecting, failure_when, per-field x-value-origin) lives in your tools.json, declared once and never guessed from a name.
In your pipeline
Gate the build on it.
Add it in a few lines. A hard defect fails the run — the same way a failing test does.
# .github/workflows/ci.yml - uses: AshwinUgale/tracelint@v0.5.0 with: path: spans.json format: openinference tools: tools.json # …or the CLI, anywhere $ tracelint check spans.json \ --format langfuse --tools tools.json