capybara

module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: MIT

README

capybara logo

capybara

A terminal trace debugger for AI agents.
It records what an agent did and shows you where it went wrong.

release build status go version license

capybara demo

Contents

Install

go install github.com/tonquoc0407/capybara/cmd/capybara@latest

Or grab a binary from the releases page. One file, no CGo, no runtime dependencies.

Getting a trace in

Run it with no arguments. It opens the TUI, listens for OTLP on 127.0.0.1:4318 and 127.0.0.1:4317, and tails ~/.claude/projects when that directory exists:

capybara

Traces land in capybara.db in the working directory. Point somewhere else with -db, and drop prompt and tool bodies with -no-content. With an empty database, the middle pane shows what it's listening on and how to send it something — there's nothing to look up.

If something else already holds 4317 or 4318 — a collector, Jaeger, another tracing UI — capybara keeps the transport that did bind, reports which one it lost, and carries on. -otlp 127.0.0.1:4319 moves the HTTP listener somewhere free.

There are three ways in, and one database can hold all of them.

OTLP

Point any instrumented app at it:

OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318

Four attribute conventions are read — OpenTelemetry's own gen_ai.*, OpenInference (Arize/Phoenix), OpenLLMetry (traceloop), and the Vercel AI SDK's legacy ai.* — so whichever instrumentor you already run is enough.

demo/frameworks/ has two runnable agents that end in the same failure — a LangGraph one traced by OpenLLMetry and a plain OpenAI tool loop traced by OpenInference. Both point at a local stub of the OpenAI API, so they need no key.

Below, the LangGraph one runs while the TUI is open: the quote tool raises, LangGraph hands the failure back to the model, and the model answers with a price anyway.

a real LangGraph agent traced into capybara

Nothing above is written for capybara. Below is somebody else's project — a chess analytics agent answering questions over a DuckDB warehouse of 9.4M Lichess games — traced without a line of its code being touched, and priced from the token counts Gemini reported.

a third-party LangChain agent traced without modification

A session file

capybara watch claude

Tails Claude Code's own logs. No instrumentation, no restart — it reads what's already on disk.

A file you have

capybara import trace.jsonl

Takes span-per-line JSONL, or agent-replay JSON when the name ends in .json.

From your own agent

Install the Python SDK:

pip install capybara-sdk
import capybara

capybara.init()  # export to a local capybara on 127.0.0.1:4318


@capybara.trace(tool="lookup_price")
def lookup_price(sku: str) -> dict:
    return {"price": 42.0, "currency": "USD"}

init() reuses a TracerProvider you already configured, so any instrumentor you already run keeps working — capybara only adds an exporter to it.

Reading a trace

The tree marks x for a failed span and ! for one carrying a finding. The run column shows when each run started, what it cost, and what was recorded against it.

Key Action Key Action
j / k move w cost waterfall
enter expand d diff two runs
/ search c context view
tab change pane b blame the output
f filter by kind r re-run from a span
a raw attributes t export a pytest case
e edit a tool output ? full help
q quit

The waterfall sorts spans by cost, so the turn that spent the money is the first line rather than something to scroll for. The context view shows what filled each turn's window — system text, tool output, history — and marks the turns where it dropped, which is where a compaction ate something.

What it looks for

Findings are recorded, never enforced — capybara doesn't fail your build. Seven kinds, all passive.

improvised

The one worth knowing about. A tool call failed, and the next model turn answered as if it hadn't:

tool get_stock_price x                    3.0s
  missing field: as_of (+3)
llm messages.create !                    17.0s
  improvised after get_stock_price failure

The evidence sits beside the answer, so the invented number is on screen next to the call that never returned it. A turn that retries the tool, or that says in any of several languages that it failed, isn't flagged.

drift

Fires when a tool's output stops matching the shape it's been returning — a field disappears, or changes type. capybara learns that shape from what it observes, one call at a time, and adopts the new one at the change point. A tool that sometimes prints text and sometimes prints JSON has both accepted: one line of JSON out of a shell command isn't a contract change.

malformed and empty_payload

Cover output that won't parse, or that arrived empty — and only for tools whose output has always been structured.

tool_error

Marks a call that succeeded as far as the trace is concerned but whose payload says otherwise — an error field with something in it, MCP's isError, ok: false, an HTTP status in the 4xx or 5xx range, or text that opens with Error:, fatal: or a traceback. Frameworks differ here: a tool that raises is marked failed by the instrumentor and needs nothing extra, while a tool that returns an error value instead looks like a clean result.

Only the top-level keys or the first line are read — searching the whole body for the word "error" would flag every document that discusses one. On 1,964 real tool calls this fires 6 times, all of them genuine.

loop

Marks the same call repeated back to back with the same arguments. A Read over ten files is a plan; the same Read ten times is a loop. Calls whose arguments were never recorded aren't compared against each other, because nothing is known about them.

cost_spike

Marks a turn burning several times the run's own rolling baseline.

If your tool has a declared schema, give it to the SDK with capybara.schema("lookup_price", Price). The declared shape wins over the learned one, so the first violating call is a finding instead of a new version.

Other commands

Command Description
capybara watch claude tail a session source without the TUI
capybara diff <run_a> <run_b> align spans, mark the first divergence
capybara blame <run> walk the final output back to its tainted source
capybara replay <run> re-run a recording, optionally with an edited tool result
capybara export <run> write a pytest case for the failure
capybara export <run> --golden write a CI fixture
capybara export <run> --html write a self-contained page
capybara check <golden> <run> compare against a golden, non-zero on divergence
capybara serve read-only web view

replay serves the recorded model responses and tool outputs back to the agent process, so nothing touches the network. Edit one tool result first and only the turns after it go live — that's how you ask what the agent would have done with the answer it should have got. A call that isn't in the recording stops the replay rather than running live.

serve and export --html render the same read-only page, one from the database and one with a single run inlined. Recorded bodies are written as text, never as markup.

Config

~/.config/capybara/config.toml:

theme = "bara"

bara is the default warm dark. mono drops the accent to grey; paper is for a light terminal. Red and amber mean the same thing in all three.

Model rates live in a table built into the binary, covering the current Claude, OpenAI, and Gemini families. Extend or override it with ~/.config/capybara/pricing.json, which is merged over the built-in one. A model with no entry stays unpriced rather than being guessed at, and a rate that varies by context length or by date is recorded at its standard tier — the table has no conditions in it.

Architecture

One Go binary holds the receiver, the store, the analysis pass, the TUI, and the web view. Storage is SQLite through modernc.org/sqlite, so there's no CGo and no database to run.

Spans arrive over OTLP or from a file and are normalised into one shape: a run, a tree of spans each with a kind (agent, llm, tool, retrieval), and the recorded content hanging off them. Everything downstream reads that shape, which is why a Claude Code session and an OpenTelemetry stream end up in the same views. Attributes that don't map are kept raw and shown under a.

Analysis is a background pass over spans not yet analysed, woken whenever the store is written. It's incremental and restart-safe: a re-imported span is analysed again, and findings carry a unique key so the second pass records nothing new. Cost is computed per LLM span from the recorded token counts, cache reads included where the source reports them.

The Python SDK is optional. It only produces OTLP spans, plus a declared schema per tool when you give it one, and the replay entry point the binary calls back into.

License

MIT

Directories

Path Synopsis
cmd
capybara command
Command capybara is a terminal trace debugger for AI agents.
Command capybara is a terminal trace debugger for AI agents.
internal
analyze
Package analyze watches ingested spans: schema learning, contract drift, improvise detection.
Package analyze watches ingested spans: schema learning, contract drift, improvise detection.
export
Package export turns a recorded run into regression artifacts: a pytest case that replays it and a golden fixture for CI.
Package export turns a recorded run into regression artifacts: a pytest case that replays it and a golden fixture for CI.
ingest/claude
Package claude tails Claude Code session logs under ~/.claude/projects.
Package claude tails Claude Code session logs under ~/.claude/projects.
ingest/intake
Package intake imports trace files; for now generic span-per-line jsonl.
Package intake imports trace files; for now generic span-per-line jsonl.
ingest/otlp
Package otlp receives OTLP traces on localhost and maps gen_ai semantic conventions onto capybara's internal span model.
Package otlp receives OTLP traces on localhost and maps gen_ai semantic conventions onto capybara's internal span model.
replay
Package replay re-executes a recorded run against its own recording: cached model responses, recorded tool outputs, and an edited value at the span the user chose.
Package replay re-executes a recorded run against its own recording: cached model responses, recorded tool outputs, and an edited value at the span the user chose.
store
Package store owns the sqlite database: schema, migrations, writes, queries.
Package store owns the sqlite database: schema, migrations, writes, queries.
theme
Package theme holds the TUI color themes: flat lipgloss style structs.
Package theme holds the TUI color themes: flat lipgloss style structs.
tui
Package tui is the full-screen terminal interface: three panes, live updates.
Package tui is the full-screen terminal interface: three panes, live updates.
web
Package web renders capybara's read-only view: the same page whether it is served from a database or exported with one run inlined.
Package web renders capybara's read-only view: the same page whether it is served from a database or exported with one run inlined.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL