A $0 multi-agent LLM observability warehouse on BigQuery + Cloudflare Workers.
Jason gives a fleet of LLM agents one shared, queryable warehouse for cost, latency, quality, and incident telemetry, without running any always-on infrastructure and without ever storing a single prompt or completion. It runs entirely inside the free tiers of BigQuery and Cloudflare Workers. The only thing you pay for is your own LLM API calls.
The dashboard above is rendered from synthetic data with one command (
pnpm demo), so you can see the whole thing before wiring up any infrastructure.
Most LLM observability tools are SaaS products that bill per event and ask you to ship prompt and completion text to a third party. For a small fleet of production agents, that is both expensive and a privacy liability. Jason is the opposite: you own the warehouse, content never leaves your agents, and the running cost is zero.
It is built around one universal fact: every agent makes LLM calls. Log those calls in a common shape and you get cost economics, latency percentiles, error rates, and per-model spend for the entire fleet from a single table, no matter what each agent actually does.
| Layer | Service | Why it is free |
|---|---|---|
| Warehouse | BigQuery | 10 GiB storage + 1 TiB query/month free tier. Content-free rows are tiny. |
| Ingestion | BigQuery load jobs | Batch load jobs are free (unlike streaming inserts, which bill per byte). |
| Sync + transform | Cloudflare Workers | 100k requests/day free. One nightly run is rounding error. |
| Dashboard cache | Cloudflare KV | The dashboard reads pre-computed mart slices from KV: zero query-time BigQuery. |
| Scheduler | GitHub Actions | Free cron to trigger the nightly sync (sidesteps the 5-cron Workers free-plan limit). |
The query-time path never touches BigQuery, so you cannot accidentally blow the 1 TiB query quota by refreshing a dashboard. Marts are computed once per sync and cached to KV.
agents (Cloudflare Workers, D1) jason-sync (Worker, nightly)
┌───────────────────────────┐ ┌────────────────────────────────┐
│ @jason/client.log(call) │ per-agent D1 │ 1. read KV watermark │
│ • drop prompt/completion │ ───────────────▶ │ 2. pull new D1 rows │
│ • hash user/customer id │ llm_calls, │ 3. transform: drop content, │
│ • buffer + flush │ agent_runs │ hash ids, shape rows │
└───────────────────────────┘ │ 4. BigQuery LOAD JOB (free) │
│ 5. build marts (SQL in code) │
│ 6. cache mart slices → KV │
└───────────────┬────────────────┘
│
jason_raw ──▶ jason_staging ──▶ jason_marts │ KV
(load target) (views) (cost / quality / incidents) ▼
┌────────────────────────────────┐
GitHub Actions ──POST /sync──▶ jason-sync │ jason-dashboard (Worker) │
(nightly cron, bearer token) │ • token-gated │
│ • reads mart slices from KV │
│ • renders Chart.js, no BQ │
└────────────────────────────────┘
Data flows in one direction: agents write content-free rows to their own D1, jason-sync loads
them into jason_raw via free batch load jobs, a code-first transform builds the staging views
and marts, and the finished mart slices are cached to KV. The dashboard only ever reads KV.
See ARCHITECTURE.md for the design decisions behind each layer.
Privacy is enforced at the source, not by a downstream filter:
- No prompt or completion content, ever.
@jason/client.log()accepts an event that can carryprompt_text/completion_text, but the default path strips them before anything is buffered.jason_rawhas no content columns to write to. - Identifiers are hashed, not stored. User/customer ids are run through HMAC-SHA-256 with a secret salt (Web Crypto) before they leave the worker. The raw value is never persisted.
- Opt-in sampling is separate and redacted. If you explicitly want a redacted text sample,
logSample({ redact: true })runs PII redaction (email, phone, SSN, credit card, account numbers) and routes to a separate, short-retentionjason_samplesdataset. It is off by default and physically cannot write tojason_raw. - Least-privilege service account.
jason-syncgetsdataEditoron the warehouse datasets andjobUserto run jobs, nothing else. NodataViewer; reads happen through user accounts. - Token-gated dashboard. The dashboard worker requires a bearer token (or
?t=query param) and serves only pre-aggregated marts, never row-level data. - Load jobs, not streaming. Free batch loads also mean no per-row streaming buffer to leak.
pnpm install
pnpm demo # writes dist/demo.html from synthetic data
open dist/demo.html # the full dashboard, rendered offline- Create the warehouse. Create a GCP project, then:
bq query --use_legacy_sql=false < infra/bigquery/datasets.sql bq query --use_legacy_sql=false < infra/bigquery/raw_tables.sql # optional: load a few synthetic rows to see the transform end-to-end bq query --use_legacy_sql=false < infra/bigquery/seed.sql
- Provision the service account (least privilege, key written to
~/.secrets):cp .env.example .env # fill in GCP_PROJECT_ID bash infra/gcp/setup-gcp.sh - Configure the workers. Fill the
<PLACEHOLDER>ids in eachworkers/*/wrangler.toml, then set secrets withwrangler secret put(service account key, hash salt, sync token, dashboard token). Deploy withwrangler deploy. - Onboard an agent. Copy
workers/jason-sync/src/syncers/example-agent.ts, point it at your agent's D1 tables, and add it to theSYNCERSarray. In the agent itself, call@jason/client.log()once per LLM call. - Schedule the sync. Set the
JASON_SYNC_URLandJASON_SYNC_TOKENrepo secrets; the workflow in.github/workflows/sync.ymltriggersPOST /syncnightly.
packages/
jason-client/ Telemetry client: hashing, PII redaction, GCP auth, BigQuery load/query, buffering
jason-types/ Shared event/row contracts (LLMCallEvent, EvalRunEvent, AlertEvent)
workers/
jason-sync/ Nightly D1 → BigQuery sync + code-first transforms + KV mart cache
jason-dashboard/ Token-gated Chart.js dashboard served from KV (zero query-time BigQuery)
infra/
bigquery/ Datasets, raw schema, synthetic seed
gcp/ Least-privilege service-account setup
scripts/
synthetic.ts Deterministic fake mart slices powering `pnpm demo`
pnpm install
pnpm -r build # build packages (types first, then client)
pnpm -r typecheck
pnpm -r test
pnpm demo # render the offline dashboardMIT © 2026 Alex Kim
