Skip to content

akim136/jason

Repository files navigation

🛰️ Jason

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.

Jason dashboard

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.

Why

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.

The $0 stack

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.

Architecture

  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.

Security model

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 carry prompt_text / completion_text, but the default path strips them before anything is buffered. jason_raw has 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-retention jason_samples dataset. It is off by default and physically cannot write to jason_raw.
  • Least-privilege service account. jason-sync gets dataEditor on the warehouse datasets and jobUser to run jobs, nothing else. No dataViewer; 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.

Quickstart

See it now (no infrastructure)

pnpm install
pnpm demo            # writes dist/demo.html from synthetic data
open dist/demo.html  # the full dashboard, rendered offline

Run the real pipeline

  1. 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
  2. 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
  3. Configure the workers. Fill the <PLACEHOLDER> ids in each workers/*/wrangler.toml, then set secrets with wrangler secret put (service account key, hash salt, sync token, dashboard token). Deploy with wrangler deploy.
  4. Onboard an agent. Copy workers/jason-sync/src/syncers/example-agent.ts, point it at your agent's D1 tables, and add it to the SYNCERS array. In the agent itself, call @jason/client.log() once per LLM call.
  5. Schedule the sync. Set the JASON_SYNC_URL and JASON_SYNC_TOKEN repo secrets; the workflow in .github/workflows/sync.yml triggers POST /sync nightly.

Repo layout

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`

Development

pnpm install
pnpm -r build      # build packages (types first, then client)
pnpm -r typecheck
pnpm -r test
pnpm demo          # render the offline dashboard

License

MIT © 2026 Alex Kim

About

A multi-agent LLM observability warehouse on BigQuery + Cloudflare Workers.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors