Skip to main content

Diagnostics export

Console can produce a snapshot of the data Ocular has scored on your box, packaged for analysis by NOPE. It's the channel you use when you want us to look at signal correlations, calibration drift, traffic mix, or anything else that needs the model-output side of the data you're carrying.

This page is the documentation for the Diagnostics Export panel inside Console (Settings → "Open →" button, or direct URL http://<console-host>:3950/diagnostics).


What's in the export

NDJSON — one self-describing JSON object per line. Consumable by jq, Pandas, DuckDB, anything that can stream a line-oriented file.

Section What it holds
meta Single header line with schema version, export time, time window, retention window, row counts.
console_config Migration markers + retention setting. Lets NOPE tell what Console's migration state was when the export was taken.
watchlist Watchlist definitions — name, scope, conditions, enabled, time window. Webhook URLs redacted.
watchlist_match Materialized match rows with scope, user / session / turn, crisis score, snapshot of matched conditions.
session One per scored conversation. Includes the full /classify response verbatim (verdict, per-axis risks, signals, trajectory, meta.inference_ms) plus Console-side bookkeeping (scored_at, ingested_at, ingest_count, trajectory_points, truncated).
turn Per-turn signal + score dicts from the trajectory. The densest model-output store.
code_occurrence Denormalized (session_id, code, score) triples, one row per top signal per session. Exactly what the dashboard uses for cross-session correlation; exposed here as a flat table so you don't have to re-extract it from each session's ocular_response.signals.
user Per-user aggregates — max_crisis_score, session_count, persistent_codes (opaque signal ID → count), escalation trend.
audit Event log: session scored, watchlist matched, webhook sent / failed. Includes status code + latency for webhook events. Webhook URLs redacted.

Customer-provided IDs (session_id, user_id, agent_id) are emitted exactly as you sent them to /classify. We need them to correlate with whatever you describe to us, but we never receive anything that lets us read the conversations those IDs refer to.


What's deliberately not in the export

These never leave Console:

  • Conversation text — the content field in session_turns is excluded. We see verdicts and per-turn signal scores, never the messages themselves.
  • Webhook receiver URLs — operator infrastructure, redacted from both watchlist and audit sections.
  • Webhook payload bodies — the queue of webhook deliveries (webhook_outbox) is not exported at all.
  • Anything outside Console — session content your app stores elsewhere, provider data fetched via PROVIDER_URL, your downstream logs. Only what Console itself has persisted is in the file.

Time window

By default the export covers the whole retention window (RETENTION_DAYS, default 7). You can narrow it to the last 1 / 7 / 30 days from the UI, or via ?since_days=N on the HTTP endpoint. Windows wider than your retention setting are hidden — they'd just add zero rows since that data is already deleted.

The filter applies to event-scoped tables (sessions, turns, code occurrences, watchlist matches, audit log). Aggregates that don't have a time axis — user rows, watchlist definitions, console_config — are always included in full. A time-filtered max_crisis_score would be misleading.

The meta line records the window applied so downstream parsers can tell a partial from a complete export.


How to trigger

From Console (Settings → Diagnostics)

  1. Open http://<console-host>:3950/settings.
  2. Scroll to Diagnostics Export, click Open →.
  3. Pick a time window; the estimate (rows + bytes) updates live.
  4. Click Download NDJSON.

Your browser streams the file to disk. Cancelling mid-download stops the server-side cursor cleanly — no locked rows, no half-written temp state.

Via curl

For multi-GB exports, curl handles flaky connections better than a browser tab:

# Pre-flight estimate (JSON):
curl -fsS http://<console-host>:3950/api/diagnostics/export?estimate=true | jq .

# Full streamed download, gzip on:
curl -fsS --compressed \
  -o ocular-diagnostics.ndjson \
  'http://<console-host>:3950/api/diagnostics/export'

# Only the last 30 days:
curl -fsS --compressed \
  -o ocular-diagnostics-last30d.ndjson \
  'http://<console-host>:3950/api/diagnostics/export?since_days=30'

The response is Content-Type: application/x-ndjson and gzip-compressed when the client sends Accept-Encoding: gzip (--compressed in curl); typical ratio is 10–20× since NDJSON repeats field names on every line.


Expected size

Rough scale, for a Console with default retention (7 days):

Deployment shape Approximate export size
Smoke / pilot (few hundred sessions) under 5 MB compressed
Small production (tens of thousands of sessions) 50–500 MB compressed
Medium production (hundreds of thousands of sessions) 1–10 GB compressed

The Diagnostics Export panel shows a live estimate (rows + approximate uncompressed bytes) before you commit to downloading. Rule of thumb: the compressed download is ~10× smaller than the estimate.

If you've set RETENTION_MAX_GB, the export never exceeds that on-disk cap. See deployment.md § "Retention" for how that interacts with time retention.


Privacy + compliance

The export is designed to let us help you with model behaviour questions without needing your users' conversations. Nothing in the file is human-readable content; everything is either identifiers you gave us, opaque signal IDs of the form signal_NNNN, or numeric scores.

If your policy requires hashing customer-provided IDs before they leave your network, do it at ingest time — Console stores and exports whatever you pass to /classify. We can't transform them at export time without losing correlation with your own logs.


Sending it to NOPE

Any secure channel you already use — encrypted email attachment, a bucket you share with us, SFTP, whatever. We'll acknowledge receipt, summarise what we find, and usually reply with either a calibration fix, a doc clarification, or a follow-up question.

If you have a dedicated NOPE contact (named support engineer, customer- success lead), send it to them. Otherwise your onboarding contact is the right destination.