Anatomy of one run.
An email lands; ~3.5 seconds and two LLM calls later it's a labeled message, an updated Notion row, and an audit record.
01 / FIG. 2Timing diagram — one run
stored as JSONB on the audit rowwebhook + jwt
128 ms
history fetch
412 ms
classify (llm)
887 ms
apply label
204 ms
dispatch
58 ms
langgraph (llm)
1 581 ms
notion update
257 ms
audit write
94 ms
01 s2 s3 s≈3.5 s
■ triager ■ job-apps ■ postgrestwo LLM calls · everything else is plumbing · end-to-end ≈3.5 s
02What every run leaves behind
triager_runs · one row per run{
"message_id": "18f3…c2a", // PK — replays dedup
"label": "Job Apps", "flagged": true,
"model": "claude-sonnet-4-6",
"tokens": { "in": 1842, "cached": 1496, "out": 31 },
"cost_usd": 0.0041,
"stages_ms": { "fetch": 412, "classify": 887, … },
"outcome": "dispatched"
}services/ triager · job_apps · news_brief · morning_brief · spend_sync
shared/ db · gmail · auth · anthropic client · settings
apps/web/ dashboard — reads neon directly, no API layer
tests/ 569 tests — run with zero secrets
migrations/ alembic
prompts/ ✂ redacted in the mirror
shared/ db · gmail · auth · anthropic client · settings
apps/web/ dashboard — reads neon directly, no API layer
tests/ 569 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 v4TremorData & models
Neon PostgresJSONB timingsSonnet 4.6Opus 4.7prompt cachingOps
Modal apps + cronsLangSmith tracingPub/Sub push1Password secrets04By the numbers
spend-sync reconciles these nightly1,190emails classified · live
31briefs published · live
$0.0041per email, prompt-cached
569tests · zero secrets
Clone it. make test. 569 green.
The suite runs with no credentials at all — that's the point.