Promptry is a library with a CLI, a FastAPI dashboard, a GitHub Action, and an MCP server stapled to the same local store. Your code calls track() and track_invocation() — or just swaps its OpenAI import for promptry.openai and the ledger fills itself. Everything else — evals, cost drill-down, the live prompt CMS, budgets, call traces — reads and writes the same SQLite file. No queues, no daemons, no cloud.
Your code reaches the SDK two ways — the promptry.openai drop-in or an explicit track() — and the SDK writes two ledgers to SQLite: versioned prompt templates and per-call invocations. Everything else reads the same file. Point [storage] at Postgres and the picture is identical, just shared.
The entire core product is a thin layer over this schema, applied through numbered migrations on a schema_version table. Open the database with sqlite3 and every read the dashboard makes is reproducible on the command line. Opt-in enterprise mode layers a few more tables on top (users, an append-only audit log); the local-first default stays exactly these nine.
One row per unique (name, hash). Dedup + auto-incremented version per name.
Named labels like prod, staging, dev. Promotion moves an env tag to one version, so render_prompt(env=…) serves exactly that one.
One row per suite execution. Drift and comparison queries read from here.
Every assertion, every run. Semantic, judge, JSON, regex, grounding, tool-use, conversation.
Thumbs up/down from users. Closes the loop from production back to the eval suite.
Versioned test data. Pin a suite to a dataset version for reproducible runs.
The per-call ledger written by track_invocation() — or automatically by the promptry.openai drop-in. One row per LLM call, no dedup. Tokens, cost, latency, model and an optional trace_id live in metadata; request/response text is captured only when you opt in. Call traces are a GROUP BY trace_id over this table — no separate trace store.
End-user ratings ingested via POST /api/feedback, correlated to the exact invocation by request_id. Joins back to a call's trace.
Spend caps. Current-period spend is summed from the invocations ledger on read; breaches surface in the dashboard and fan out to Slack, PagerDuty, a webhook, or email when a channel is configured.
End-to-end on a typical pipeline. Promptry's own overhead is dwarfed by the LLM calls themselves, and it never sits in the request's critical path.
Every architectural choice traces back to the same constraint: no service to run, no vendor to trust.