TraceBi  ·  the report-as-code framework

Reports as code —
to keep agents in line.

AI writes the analysis now — fast, and confidently wrong when it’s wrong. TraceBi turns every report into code: an agent authors it, a human approves it, guardrails refuse the bad numbers, and a receipt proves the rest reproduce.

Python · DuckDB reports as code agent-native · human-controlled guardrails + receipts MIT

The problem

A dashboard can’t keep
an agent in line.

An agent generates reports faster than anyone can check them, and a confident wrong number reads exactly like a right one. A BI tool can’t govern that — you can’t diff a dashboard in a pull request, review its measure logic, or make it refuse a bad aggregation. The metric hides inside the GUI; the “source of truth” is a screenshot.

TraceBi makes the report code instead: the connectors, the star schema, the measures, and the figures are all Python and JSON in your repo — the one place an agent’s work can be reviewed, versioned, tested, and governed like any other pull request.

Everything that makes a number is in the repo, reviewable, and governed.

Built for agents · controlled by humans

Two authors, one contract.

TraceBi is built for the analyst that is now an agent — and for the human who stays in control of what it ships. Both author the same code against the same contracts; neither gets a shortcut around review or the receipt. This is a first-class design goal, not a plugin.

the agent builds

It works from a clean structure, never a blank canvas. tracebi init scaffolds the three-folder project; tracebi context hands the agent the exact vocabulary that exists — the facts, dimensions, measure kinds, and operators — so it never guesses; and a report is structured slots to fill against a closed, declarative grammar. There is a right shape for the work, and the framework shows the agent where it is.

LESSONS · SFT

Does it right

Good practice is baked into the environment, not a prompt — a curated knowledge base it reads, cited by the errors — and it drives the whole loop over the MCP gateway.

GUARDRAILS · RLHF

Can’t ship the wrong one

The silent-wrong patterns are refused by construction, so even a weaker model can’t push a confidently-wrong number through.

VERIFY · REWARD

Checks its own work

tracebi verify mechanically re-runs an agent’s overnight reports and localizes any drift to its source.

the human controls

REVIEW

Read it in a pull request

The agent’s output is a diff — a measure on the model, a figure binding — a person reads in one screen and approves, edits, or rejects. You hold the merge button.

EDIT

Change it by hand, anytime

It’s plain Python and JSON. Rename a measure, retune a band, rewrite a section — no black box, no round-trip through the agent to fix a typo.

CONTAIN

It writes only its own report

The gateway has no write access to your warehouse — the only thing the agent produces is its rendered artifact and receipt. It can check everything and rewrite nothing you depend on.

The spine · three phases, two freeze points

Messy data to a live report, decoupled.

The slow, unconstrained analysis and the fast, iterated reporting never block each other — because the model between them is a frozen contract. Editing a report never re-runs the pandas; the page re-renders in milliseconds.

① TRANSFORM

Do the real work

Ordinary, unconstrained pandas — pull the queries, clean, dedupe, parse. Then sink clean star-schema tables into a file-backed warehouse.

transforms/ → warehouse.duckdb
freeze: the tables
② MODEL

Name the contract

A declarative star schema over the warehouse — grain, keys, measures — in a few dozen lines a reviewer reads in one screen.

models/ → the semantic contract
freeze: the model
③ REPORT

Bind the figures

A page where every KPI, chart and table cell is a live query against the model — no pandas in the loop.

reports/ → a self-contained artifact

Each phase is a folder with its own cadence. The tracebi CLI scaffolds them, runs each one, and serves the result — no BI server to stand up.

See it as a user · the whole loop in five commands

What you actually type.

From an empty folder to a shareable, self-verifying report — the real commands, and what each one gives you back. Step through it.

bash
$ tracebi init altsvault
✓ created altsvault/
├─ transforms/ # ① your pandas
├─ models/ # ② the star schema
├─ reports/ # ③ the dashboards
├─ inputs/ # raw pulls
└─ AGENTS.md · sample_dashboard # a working example
$ cd altsvault
one command

Scaffold the project

A three-folder project — one per phase — plus a working sample dashboard and an AGENTS.md that teaches an agent the whole grammar. It’s a repo from minute one: git init and commit.

The semantic layer · where the depth is

Measures declared once.
Referenced by name, everywhere.

The heart of the framework is a real semantic layer: a metric is defined in one place on the model — reviewed in a pull request, versioned in git — and every report calls it by name. No more re-deriving “revenue” five different ways in five different scripts.

And the hard patterns that used to force a hand-written script are first-class measures. A whole concentration table is three lines:

hand-rolled in a script
# somewhere in report.py
df = q("select * from holdings")
df = df.sort_values("fv", ascending=False)
df["rank"]  = range(1, len(df)+1)
df["share"] = df.fv / df.fv.sum()
# re-implemented per report,
# reviewed by nobody

Metric logic scattered across scripts — the definition drifts, and the next report reinvents it.

declared on the model
model.add_measure("fv_rank",  rank="fair_value")
model.add_measure("fv_share", share="fair_value")
model.add_measure("fv_cum",   running="fv_share")
# one definition, referenced by name
# from every report and every query

Reviewed once, versioned in git, called by name — the same number everywhere it appears.

the grammar

ratio · share · rank · runningRatios of totals (never a mean of ratios), % of total, concentration & Pareto tables.
median · stddev · p90 / p95 / p99The distribution and its tail — the worst marks a mean and even a median hide.
period_endSemi-additive AUM / NAV — summed across funds, latest snapshot over time. Never double-counted.
offset · growthPeriod-over-period — YoY, QoQ, MoM — as a measure, not a self-join.
to_dateYTD / QTD / MTD cumulatives that reset on the period boundary.
rank + partition_by · havingTop-3-issuers-per-sector — a governed query, not a spreadsheet pivot.
time grains · value bins · or / and filtersGroup by month, by balance band; filter on this OR that across columns.

A closed, declarative grammar — no free-form SQL to review, and every measure is data you can diff, validate before it runs, and generate.

Guardrails that teach

The wrong number is made
unavailable — at the keystroke.

The framework’s cardinal rule is that it never returns a confident, silently-wrong total. So the model refuses the classic mistakes the moment you declare them, and the refusal names the fix — a guardrail is a lesson that fires when it matters, that no author (human or agent) can skip.

mean(mark) → 8.49 ✕

Mean of a ratio

A per-row ratio averaged overweights small rows. Refused — even when the column name hides it — with a pointer to a ratio of totals. Truth was 1.05.

sum(aum) over time ✕

Stock double-count

Summing a balance across snapshots triple-counts the same money. Refused, with a pointer to a semi-additive period_end.

join on a non-unique key ✕

Fan-out inflation

A join that silently multiplies every additive measure is refused by default — a total never inflates by accident.

The output

A report is one file.
And every number in it checks out.

The framework renders a report to a single self-contained HTML artifact — charts, data, and styling all inlined, no server, no CDN. It opens anywhere and emails cleanly. And because every figure was a live query, each one ships stamped with the query and a fingerprint of its bytes, so tracebi verify --file can confirm — offline — that nothing was edited after the fact.

Total fair value · portfolio_model
$628,412,900 reproduces
query    fact_holdings · measures=[fair_value] · agg=sum
engine   duckdb 1.5.5 · rows=1,204
sha-256  a3f1…9b7c · e8d2…4f10

A live demo of the mechanic — the real verifier hashes the embedded bytes. It proves a number reproduces and the file is intact; it doesn’t claim the number is “correct.”

Batteries included

A framework, not a library
of loose parts.

Everything the workflow needs ships in the box — and it all runs from your machine, offline, no account.

CONNECTORS

Read from anywhere

CSV, SQL, DuckDB, BigQuery, Snowflake, in-memory — one BaseConnector interface, and lineage on every load.

PIPELINES

Schedule the refresh

Register medallion layers, run them upstream-first, and keep a run history — or hand the sink to the pipeline you already have.

RENDERERS

Ship the report

Self-contained HTML with live charts, or an Excel workbook — from the same model, same measures.

LINEAGE

Immutable by construction

Every operation returns a new dataset with a node appended — the audit chain can’t silently break.

CLI + WEB

Scaffold, dev, serve

tracebi init · dev · report · verify · serve — plus a FastAPI + React app over the same registry.

MIT

Yours to run

Code-first and open. No BI server to license, no data leaving your environment.