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
contentfield insession_turnsis excluded. We see verdicts and per-turn signal scores, never the messages themselves. - Webhook receiver URLs — operator infrastructure, redacted from both
watchlistandauditsections. - 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)
- Open
http://<console-host>:3950/settings. - Scroll to Diagnostics Export, click Open →.
- Pick a time window; the estimate (rows + bytes) updates live.
- 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.