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.
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_usageStart the backend:
.venv/bin/python backend/manage.py runserver 127.0.0.1:8000In another terminal, start the dashboard:
cd frontend
npm install
npm run devOpen 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.
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_usageEquivalent 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.
The JSONL parser understands:
session_metafor non-content identifiers, source, provider, and timestamp;turn_contextfor turn ID and model;task_started,task_complete, andturn_abortedfor lifecycle, duration, completion, and TTFT;token_count.info.last_token_usagefor token categories;token_count.rate_limitsfor 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.
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.
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.
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.
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-runBuild and typecheck the frontend:
cd frontend
npm run typecheck
npm run buildThe 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.
This slice deliberately has no Docker, authentication, deployment configuration, background scheduler, or external runtime service. Re-run ingest_usage whenever fresh data is needed.