Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Codex Observatory

Codex Observatory is a local-only dashboard for understanding Codex and Hermes usage without copying conversation content into an analytics system. The first working slice includes a Django/SQLite ingestion and JSON API plus a React/TypeScript/Vite dashboard.

Quick start

Python 3.12+ and Node 20+ are recommended.

python3 -m venv .venv
.venv/bin/pip install -r backend/requirements.txt
.venv/bin/python backend/manage.py migrate
.venv/bin/python backend/manage.py ingest_usage

Start the backend:

.venv/bin/python backend/manage.py runserver 127.0.0.1:8000

In another terminal, start the dashboard:

cd frontend
npm install
npm run dev

Open http://127.0.0.1:5173. Vite proxies /api to the local Django server. The frontend makes no third-party runtime requests and includes no remote font, analytics, pricing, or telemetry calls.

Ingestion

The command reads these defaults:

  • ~/.codex/sessions/**/*.jsonl
  • ~/.codex/state_5.sqlite
  • ~/.hermes/state.db

Override them with environment variables:

CODEX_SESSIONS_GLOB='/alternate/codex/sessions/**/*.jsonl' \
CODEX_STATE_DB='/alternate/codex/state_5.sqlite' \
HERMES_STATE_DB='/alternate/hermes/state.db' \
.venv/bin/python backend/manage.py ingest_usage

Equivalent CLI options are --sessions-glob, --codex-state, and --hermes-state. The analytics database defaults to backend/analytics.sqlite3; set OBSERVATORY_DB to move it. Set OBSERVATORY_PLUS_MONTHLY_USD to change the $20 Plus default.

JSONL files are opened for reading. Source SQLite databases are connected through SQLite's mode=ro URI. Observatory never modifies either source database. File metadata plus WAL/SHM sidecar metadata skip unchanged inputs; stable session-scoped metric fingerprints and database constraints make reprocessing idempotent. When a changed JSONL rollout is re-read, its derived children are reconciled before re-materialization, so copied/renamed files, inserted lines, and edited events cannot leave duplicate or stale metric rows. Missing or unsupported source databases are recorded as health errors instead of being reported as healthy with stale data.

Allowlisted source fields

The JSONL parser understands:

  • session_meta for non-content identifiers, source, provider, and timestamp;
  • turn_context for turn ID and model;
  • task_started, task_complete, and turn_aborted for lifecycle, duration, completion, and TTFT;
  • token_count.info.last_token_usage for token categories;
  • token_count.rate_limits for exact quota windows, resets, plan, and credit balance;
  • completion events for exec, MCP, image, web, and patch tool durations.

The Codex state reader selects only thread ID, source, provider, model, and timestamps. The Hermes reader selects only session/model usage totals and timestamps. Queries do not select titles, previews, messages, system prompts, commands, results, or tool output.

Privacy boundary

The analytics database stores identifiers, model/source labels, timestamps, durations, token counters, completion state, and rate-limit snapshots. It does not store prompt text, assistant responses, reasoning text, message text, commands, tool inputs, stdout/stderr, tool output, titles, previews, CWDs, repository metadata, or source paths. Source paths are represented by one-way SHA-256 references in ingestion health records. Persisted and returned ingestion errors are bounded and redact paths and credential-like values.

The API status legend uses stale when a measured quota snapshot is older than two complete window lengths; stale values remain visible for historical context but are not presented as current.

The API is intentionally unauthenticated and binds to localhost in the documented command. Do not expose it to a network without adding an access-control layer.

Formulas and evidence status

Every dashboard number is labeled:

  • measured — copied from an allowlisted source field;
  • derived — exact arithmetic over measured fields;
  • estimated — uses an explicit price or subscription assumption;
  • unavailable — a required field or documented model price is absent.

Token formulas:

uncached input      = max(input tokens - cached input tokens, 0)
cache hit rate      = cached input tokens / input tokens
visible output      = max(output tokens - reasoning output tokens, 0)
total tokens        = input tokens + output tokens

Codex reports cached input as a subset of input and reasoning as a subset of output. They are never added again. Hermes records uncached input and cache reads separately, so ingestion first normalizes input = uncached input + cache reads; the stored invariant is then the same for every source.

Economics formulas:

Plus weekly equivalent = monthly fee × 12 / 365.2425 × 7
weekly quota value      = weekly equivalent × latest weekly used percent
credit consumption      = max(first measured balance - latest measured balance, 0)
API equivalent          = uncached input × input rate
                        + cached input × cached-input rate
                        + cache writes × cache-write rate
                        + output (including reasoning) × output rate

actual_cash_usd is the configured monthly Plus expense, not a claim that token usage caused a marginal charge. Quota value is an estimate that allocates the weekly-equivalent subscription fee using the latest measured weekly percentage. API equivalent is a counterfactual estimate, not a bill.

Rates are versioned in backend/usage/rates.py as openai-api-2026-08-22, in USD per million tokens. The table includes only models with a documented rate from the official OpenAI model comparison or model pages such as o4-mini. GPT-5.6 cache writes use the documented 1.25× uncached-input rate. If a model or required cache-write rate is missing, that model remains unpriced and coverage is reported; Observatory does not borrow a nearby model's price. The $20 Plus default follows the official Plus price.

Quota gauges use the latest measured snapshot whose window is exactly 300 minutes (5 hours) or 10,080 minutes (7 days). A snapshot older than two window lengths is marked stale in the API and UI; it is not presented as current usage. Gauges are not reconstructed from tokens, because Codex quota weights are not available in these sources.

API

All endpoints are local JSON GET endpoints without a trailing slash:

  • /api/summary — KPIs, token totals, latest gauges, and economics;
  • /api/models — model comparison and price coverage;
  • /api/tokens — daily overlap-safe token mix;
  • /api/sessions — recent content-free session rows;
  • /api/limits — latest quota windows, history, and credit delta;
  • /api/health — analytics DB, ingestion freshness, errors, and privacy boundary.

Verification

Run the full backend suite and migration drift check:

.venv/bin/python backend/manage.py test usage.tests
.venv/bin/python backend/manage.py makemigrations --check --dry-run

Build and typecheck the frontend:

cd frontend
npm run typecheck
npm run build

The synthetic JSONL fixture under backend/usage/tests/fixtures/ is redacted and contains no prompt/response/tool-output text. Tests cover parsing, repeated-ingestion dedupe, overlap-safe token math, duration aggregation, 5-hour/weekly windows, economics, privacy-safe session output, and every API route.

Scope

This slice deliberately has no Docker, authentication, deployment configuration, background scheduler, or external runtime service. Re-run ingest_usage whenever fresh data is needed.

About

Local dashboard for Codex and Hermes usage analytics — Django/SQLite ingestion + React/Vite UI. No conversation content leaves your machine.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages