See what your LangChain & LangGraph agents actually did. Local-first observability — drop in one line, watch every run live in your browser.
pip install "tracesage[langchain]"
tracesage demo # seeds a sample run and opens the live UI in your browserNo API key, no config, no code — tracesage demo opens a real trace so you can click
around the graph immediately. Then add one line to your own agent:
result = await graph.ainvoke(
{"input": payload},
config={"callbacks": [tracer.handler]}, # ← the only line you add
)
# tracesage prints a link: 🔍 http://localhost:7842/ui — open it to watch the run liveA failed tool call shows up as a red node on the exact step that broke — the tool, its input, and the error — instead of a stack trace buried in your logs.
- Why tracesage
- Install
- Quick start
- Screenshots
- Concepts: topology node kinds
- Features
- Examples
- Documentation
- Comparison
- Performance
- Status
- Contributing
- License
LangChain agents emit a rich callback stream — chain start/end, tool start/end, LLM start/end, retrieval, errors. tracesage captures all of it without changing your workflow logic, persists it locally (SQLite + gzipped blobs), and renders it in an interactive graph + timeline UI in real time.
- Zero infrastructure. No Docker. No Postgres. No external services.
pip install. The UI is fully self-contained (assets vendored, no CDN) and works offline. - Minimal-change integration. Add one callback to your existing
ainvoke— no rewrites. - Crash-safe by design. The handler never raises and the tracer never crashes your pipeline.
- Interactive graph view. Custom SVG graph (no framework), auto-laid-out. Hover, click, replay any run.
- MCP-aware. Tools loaded from MCP servers are attributed by source, so you can see which tools came from which server vs. which are hardcoded. See docs/mcp.md.
- OpenTelemetry export. Optionally ship every trace as OTel spans to your collector / Tempo / Jaeger / Datadog / Honeycomb — the bridge from the local dev view to your production stack (config-driven; see docs/configuration.md).
- Multi-app friendly. Run several apps at once: each auto-binds a free port (7842, 7843, …),
shows its
TRACESAGE_PROJECT_NAMEin the UI header, and keeps its own data dir — no clashes. - Pluggable storage. SQLite today; Postgres / remote-collector / object-store backends planned (see
production_roadmap.md). - MIT licensed. Free forever.
pip install "tracesage[langchain]"Requires Python 3.11+. Quote the extra ("tracesage[langchain]") so zsh —
the default macOS shell — doesn't glob the brackets and fail with no matches found. The [langchain] extra pulls langchain-core; that's the only mandatory
third-party dep beyond the standard FastAPI / aiosqlite / pydantic stack. If your
app uses LangGraph, also pip install langgraph (tracesage doesn't pull it).
tracesage is provider-agnostic — it traces LangChain's callback stream, so OpenAI / Anthropic / local models are all captured automatically; there's no provider setting in tracesage. Install whichever provider you use:
pip install langchain-openai # OPENAI_API_KEY=...
pip install langchain-anthropic # ANTHROPIC_API_KEY=...For MCP tool-source attribution (loads tools from MCP servers and tags them by
source), install the mcp extra:
pip install 'tracesage[mcp]'See it in 5 seconds — seed a sample trace and open the UI:
tracesage demoSync scripts / notebooks — wrap your code; every LangChain call is captured
automatically (no callbacks= wiring) and a clickable trace link is printed:
import tracesage
with tracesage.trace() as tl: # starts the UI + global capture
result = agent.invoke("your input") # 🔍 tracesage: http://127.0.0.1:7842/ui/#run=...
input("Trace ready — open the printed link, then Enter to exit.") # keep the UI up(The embedded UI stops when the with block / process exits; traces persist to
~/.tracesage, so you can also reopen them later with tracesage serve.)
Async apps — use the context manager (or await TraceSage.create() for full control):
import asyncio
from tracesage import TraceSage
async def main():
async with TraceSage.session(install=True) as tl: # install=True → global capture
await graph.ainvoke({"input": "your payload"}, config={"tags": ["my-system"]})
await tl.flush() # ensure events are persisted
print(tl.run_url("<run_id>")) # deep link to a run
asyncio.run(main())Prefer explicit wiring? Pass config={"callbacks": [tl.handler]} instead of install=True.
That's it. Open the URL tracesage prints on startup (🔍 tracesage: http://…/ui/#run=…)
and explore — it's http://localhost:7842/ui by default, but auto-port picks the next
free port (7843, …) if 7842 is taken, so trust the printed link (tracer.ui_url).
Once you have traces, debug them without leaving your terminal:
tracesage show <run_id> # render a run as a tree in the terminal
tracesage watch <run_id> # live-tail events as they stream
tracesage diff <run_a> <run_b> # compare two runs (tokens, tools, errors)
tracesage view trace.jsonl # open an exported trace in the UI directlyTest your agents — the tracesage_capture pytest fixture is auto-registered:
def test_agent_uses_search(tracesage_capture):
agent.invoke("find me a hotel")
tracesage_capture.assert_tool_called("search")
tracesage_capture.assert_no_errors()
assert tracesage_capture.total_tokens()[0] < 5000See docs/development.md for the full developer guide, and
examples/showcase/ for 30 before/after apps across popular use cases.
When you open the UI, every node in the topology graph is one of five event-based
kinds — plus a synthesized mcp node when you attribute tools to an MCP server.
Knowing what each means is the prerequisite to reading a trace:
| Kind | What it is | Examples you'll see |
|---|---|---|
agent |
A function you registered as a LangGraph node, that calls other components | agent:billing_agent, agent:fact_extractor, agent:supervisor |
tool |
A side-effect function (DB query, API call, calculation) decorated with @tool |
tool:lookup_account, tool:run_sql, tool:cite_sources |
llm |
A language-model call (chat or completion) | llm:FakeListChatModel, llm:ChatOpenAI, llm:ChatAnthropic |
retriever |
A BaseRetriever subclass — the "R" in RAG |
retriever:Chroma, retriever:FAISS, retriever:_FixedCorpusRetriever |
chain |
Plumbing — LCEL primitives, the LangGraph orchestrator, routing functions | chain:LangGraph, chain:RunnableSequence, chain:ChatPromptTemplate, chain:route_after_quality |
mcp |
An MCP server (synthesized) — groups the tools loaded from it | mcp:weather, mcp:math, mcp:github |
Quick mental model:
agentis your code that calls something. It does reasoning.tooldoes the actual side-effect work and returns a result.llmis what you count, cost, and cache.retrieveris its own dimension — "did we find the right docs?" is a different question from "did the LLM use them well?".chainis the wrapping machinery (theprompt | llm | parserpipe operator, the LangGraph state machine, routing functions). It's infrastructure, not business logic.mcpgroups tools by the MCP server they came from — provenance, so you can tell server-provided tools from your hardcoded ones (see docs/mcp.md).
Read the full reference at docs/concepts.md —
it covers how tracesage classifies events into kinds, why agents with no
descendants get demoted to chain, and walks through a research-pipeline
topology piece by piece.
- Run list with status badges (running / completed / failed), search, status filter, and a toast when a watched run finishes ("Run completed" / "Run failed: …")
- Topology vs. Run-trace — one graph pane toggles between the all-up system architecture (nodes in per-kind columns) and a single run laid out as a left → right call tree in call order. Picking a run opens its trace; the Topology button returns.
- Step-through replay — explicit Start / Pause / Resume plus Prev / Next manual stepping walk a run on the graph (1x / 2x / 5x); clicking a timeline step during replay pauses and jumps the cursor there
- Scoped topology — the graph + "Tools by source" default to the selected run (a toolbar selector switches to last-N-runs / all-time), so removed tools, agents, or MCP servers don't linger across app versions as you iterate
- MCP attribution —
mcp:server nodes with distinct per-server colors (tools tinted by source), agent→server and server→tool edges, and a draggable "Tools by source" panel; a large tool fan-out wraps into columns and the graph caps its zoom so nodes stay legible instead of shrinking to fit - Timeline with click-to-expand step cards — a
*_startshows its request, a*_endthe full request + response payloads, plus tokens, duration, and errors; MCP-backed tools are tagged with their server - Node inspector — click any node for its stats; LLM nodes show token usage (in / out, total across N calls) and each node's invocations are grouped one-per-call
- Header stats —
ev/s(1-min rolling event rate),running(in-progress runs),dropped(events lost to backpressure; red if non-zero), and a heartbeat-backed connection dot that reflects a dropped link instead of going stale - Dark / light themes, persisted in
localStorage - Keyboard shortcuts:
j/knext/prev run,/focus search,ttoggle theme,Esc,?
- Refuses to bind non-loopback addresses without
auth_token(hard fail-stop) - Bearer-token HTTP auth + WebSocket auth via
?token=or subprotocol - Constant-time token comparison
- Path-traversal blocked at runtime
- Per-run event cap (circuit breaker) and root-level sampling for high volumes
- Bounded internal state (no unbounded memory growth in long-running processes)
- Kill switch:
TRACESAGE_ENABLED=falsemakes it a complete no-op (no server, no DB/worker, no-op handler) — integrate once, disable per-environment. See docs/production.md.
tracesage's own UI/CLI is the local developer-loop view. To get agent traces into a
production observability stack, point otlp_endpoint at any OTLP/HTTP collector —
every trace is also emitted as OpenTelemetry spans (the local SQLite store stays too):
pip install "tracesage[otel]"
export TRACESAGE_OTLP_ENDPOINT=http://localhost:4318 # or set in TraceSageConfigfrom tracesage import TraceSageConfig
# pass to TraceSage.create() / .session() / tracesage.trace():
cfg = TraceSageConfig(otlp_endpoint="http://localhost:4318", otlp_service_name="my-agent")Where it helps: the spans land in whatever OTLP-compatible backend you already run —
Grafana Tempo, Jaeger, Datadog, Honeycomb, Arize/Phoenix, an OTel Collector — so agent
traces sit alongside the rest of your services (correlated with HTTP/DB spans, long-term
retention, alerting, multi-service/multi-process views) with no vendor lock-in. Mapping:
root_run_id→trace, run_id→span, parent_run_id→parent; tokens/errors/MCP server become
span attributes. Best-effort — if the collector is down, tracing continues and your app is
never affected.
Note — it's config-driven, not a UI toggle. There's no button in tracesage's UI to turn this on. You enable it via config/env before tracing starts, and the exported traces appear in your OTel backend's UI (Grafana/Jaeger/Datadog/…), not in tracesage's own UI. tracesage's UI always shows the local SQLite view. See docs/configuration.md.
The tracesage command ships with the package — a viewer + utilities over a
data directory. It never ingests events; ingestion happens only when your code calls
TraceSage.create(). The fastest way to see it working, with zero code:
tracesage demo # seed a sample trace, serve + open the UIInspect traces in the browser:
| Command | What it does |
|---|---|
tracesage serve -d ~/.tracesage [--open] |
Read-only viewer over an existing data dir (auto-picks the next free port; --auth-token to gate it) |
tracesage view trace.jsonl [--open] |
Open an exported JSONL trace directly in the UI |
Inspect traces in the terminal (no server):
| Command | What it does |
|---|---|
tracesage runs --status failed --limit 20 |
List root runs (filter by --status / --tag, --json for NDJSON) |
tracesage show <run_id> |
Render a run as a colour-coded indented call tree (MCP tools tagged mcp:<server>) |
tracesage watch <run_id> |
Live-tail a run's events as they're written (--once to print and exit) |
tracesage diff <run_a> <run_b> |
Compare two runs side by side — status, steps, tokens, tools, errors |
tracesage stats [--json] |
Summary stats — run counts by status, avg duration, token totals, DB size |
Move, retain, diagnose:
| Command | What it does |
|---|---|
tracesage export [RUN_ID] --all -o trace.jsonl |
Dump one run or --all to JSONL (-o - for stdout) |
tracesage import -i trace.jsonl -d DIR |
Read a JSONL export back into a data dir (backups / move between machines) |
tracesage gc --max-runs 10000 [--dry-run] |
Retention — delete oldest runs/blobs beyond a count or --max-blob-size-gb |
tracesage doctor -d ~/.tracesage |
Read-only diagnostics — schema version, run counts, orphan/missing-blob checks |
tracesage version |
Print the installed version |
Most commands take --data-dir, -d (default ~/.tracesage). See
docs/cli.md for the full per-command reference.
The examples/ directory has three tiers:
getting_started/— 3 standalone demos driven byFakeListChatModel(no API key): smart-search agent, research supervisor, RAG.mcp/— MCP attribution demos (needstracesage[mcp]), including two real-world before/after apps:trip_demo/— one agent over three bundled stdio MCP servers (flights / weather / hotels, 7 tools each) + a local tool; no external installs, just an LLM key. Best place to see multi-server topology + "Tools by source".gmail_youtube_demo/— a ReAct agent that reads a real Gmail inbox and summarises YouTube transcripts (YouTube needs no auth; Gmail is optional, via Google ADC).
showcase/— 30 real before/after apps across popular use cases (customer support, RAG, multi-agent, MCP, reasoning loops, finance/legal/insurance). Each ships a plainbefore.pyand anafter.pywith tracesage added, sodiffshows the exact integration.
# instant, no key:
python examples/getting_started/01_smart_search_agent.py # then open http://localhost:7842/ui
# the real-world gallery (needs an LLM key):
pip install -r examples/showcase/requirements.txt
export OPENAI_API_KEY=...
python examples/showcase/01_support_faq_router/after.pyThe showcase gallery has 30 before/after apps spanning LangChain + LangGraph: routing, parallel fan-out, the supervisor pattern, RAG variants, writer-critic loops, map-reduce, MCP, self-correction, and finance/legal/insurance verticals.
| Doc | What's in it |
|---|---|
| Quickstart | First trace in five minutes |
| Developer guide | Trace links, sync/notebook setup, CLI debugging, pytest fixture |
| Configuration | Every TRACESAGE_* env var explained |
| CLI reference | All tracesage subcommands |
| Production guide | Sampling, auth, retention, deployment |
| Troubleshooting | "Where are my runs?", install/port issues, FAQ |
| Comparison | tracesage vs LangSmith / LangFuse / Phoenix |
| Extending tracesage | Adding framework adapters and storage backends |
| Examples | 30 before/after apps with tracesage added |
When to use tracesage vs alternatives:
| tracesage | LangSmith | LangFuse | Phoenix | |
|---|---|---|---|---|
| Zero infra | ✓ | cloud / enterprise self-host | Docker + Postgres | ✓ |
| Pure pip install | ✓ | ✓ (cloud) | ✗ | ✓ |
| Live UI | ✓ | ✓ | ✓ | ✓ |
| MIT licensed | ✓ | proprietary | MIT | Elastic v2 |
| Eval framework | non-goal | ✓ | ✓ | ✓ |
| OpenTelemetry export | ✓ (0.2+) | partial | ✓ | ✓ |
See docs/comparison.md for the full breakdown.
Bench results (5,000 events, 100 distinct run_ids, 20% blob-eligible):
| Platform | Sustained | p99 write | Drops |
|---|---|---|---|
| Linux x86 + NVMe | 800–1200 ev/s | 80–150 ms | 0 |
| Windows NTFS | 60–100 ev/s | 1–2 s | 0 |
Windows NTFS is the bottleneck (gzip + fsync amplification). For very high
throughput on Windows, raise TRACESAGE_WORKER_BATCH_SIZE to 200 and
TRACESAGE_WORKER_BATCH_TIMEOUT to 0.5.
Beta. API may still shift before v1.0. The PyPI badge at the top shows the published
version; it's stamped by the release workflow when a version actually ships (so it
matches PyPI and never gets ahead of a release). Built for local development and
single-process tracing; centralized multi-process / remote-collector mode is
on the roadmap (see production_roadmap.md). Today the only
shipped adapter is LangChain / LangGraph — the core is framework-neutral and
CrewAI / AutoGen / LlamaIndex adapters are planned, not yet available.
See the changelog for release notes.
Issues and pull requests are welcome.
- Read
docs/contributing.mdbefore sending a PR - For non-trivial changes, open a discussion first
- Bugs and feature requests use the issue templates
- Security reports: see
SECURITY.md— please don't open public issues for vulnerabilities - All participation is governed by the Code of Conduct
MIT © tracesage contributors







