Anatomy of one run.
An email lands. About seven seconds and one model call later it's a labeled message with an audit row. Job-app email runs the graph for another twenty seconds into Notion.
01One run, timed
medians from the audit ledger, stored as JSONBidempotency check
127 ms
gmail fetch
191 ms
classify (sonnet)
5 990 ms
apply label
579 ms
parse email
265 ms
sublabel (opus)
3 160 ms
apply sublabel
522 ms
notion match
12 751 ms
extract (opus)
4 013 ms
notion write
1 245 ms
010 s20 s≈30 s
■ triager ■ job-apps graphstage medians, 805 + 14 production runs · label ≈7 s · full job-app pipeline ≈29 s
02Run artifacts
triager_runs · one row per run{
"message_id": "18f3…c2a", // PK, dedups replays
"primary_label": "Job Apps", "flagged": true,
"model": "claude-sonnet-4-6",
"input_tokens": 871, "cache_read_tokens": 4293, "output_tokens": 232,
"cost_usd": 0.0074,
"stage_timings_ms": { "fetch": 191, "classify": 5990, … }
}services/ triager · job_apps · news_brief · morning_brief · spend_sync
shared/ db · gmail · auth · anthropic client · settings
site/ public site · reads neon directly, no API layer
tests/ 602 tests · run with zero secrets
migrations/ alembic
prompts/ ✂ redacted in the mirror
shared/ db · gmail · auth · anthropic client · settings
site/ public site · reads neon directly, no API layer
tests/ 602 tests · run with zero secrets
migrations/ alembic
prompts/ ✂ redacted in the mirror
03Stack
boring infrastructure, on purposeBackend
Python 3.12FastAPISQLAlchemy 2.0AlembicLangGraphFrontend
Next.js 16React 19Tailwind v4Data & models
Neon PostgresJSONB timingsSonnet 4.6Opus 4.7prompt cachingOps
Modal apps + cronsLangSmith tracingPub/Sub push1Password secrets04By the numbers
spend-sync reconciles these nightly2,225emails classified · live
115briefs published · live
$0.010per email, prompt-cached
602tests · zero secrets
make test. 602 green.
The whole suite runs with zero credentials.