Airlock can optionally write a compact operational record for each logical request into FathomDB. This page distinguishes the shipped operator contract from the longer-term data-model direction.
Enable the FathomDB engine with:
AIRLOCK_ENABLE_FATHOMDB=1To record logical requests into that engine, also enable the recorder-fed logger:
AIRLOCK_ENABLE_FATHOM_LOGGER=1Airlock opens airlock-fathom.db lazily under AIRLOCK_STATE_DIR. The engine
is PID-bound and thread-safe inside one proxy process; do not run multiple
live processes against the same database path. The former FathomDB 0.3.x
logs/airlock.db path is not migrated or opened by Airlock.
The optional store supports bounded operational search/analysis and audited per-client erasure. Erasure is performed by the running proxy, is loopback only, and covers FathomDB only — it does not delete JSONL records. See Operations → FathomDB and Admin API.
The sections below describe the intended data model: what is stored as first-class fields, what belongs in nested payload JSON, what is computed at write time, and what is expected to be derived later from projections or summary tables. They are not a promise that every listed field or control is enabled in every deployment.
Airlock's Fathom model has four storage shapes:
- Event record — one
RequestLognode per logical Airlock request - Payload JSON — optional nested request/response detail attached to that event
- Per-event derived fields — values computed once at write time
- Summaries / projections — aggregates derived from many events
The main design rule is simple:
- Keep stable operational query keys as first-class fields
- Keep bulky or nested data in one payload JSON object
- Compute deterministic per-request values once
- Keep rollups separate from raw event records
Every logical request may produce one RequestLog node in FathomDB.
These fields are intended to remain first-class fields on the event record:
timestampsuccesserror_flagmodelairlock_providerrequest_idcall_idprompt_tokenscompletion_tokenstotal_tokenscostduration_msfailure_categorycall_typemcp_tool_namemcp_server_nameairlock_clienterror_typeuserteam
Why these stay first-class:
- They are the main operational filter and grouping dimensions
- They support dashboards, billing views, advisor queries, and troubleshooting
- They should not require JSON-path extraction for common queries
Airlock also supports a nested payload JSON object for richer request detail. This is where variable-shape, bulky, or more privacy-sensitive fields belong.
The intended payload members are:
messagesresponse_textheadersresponseguardrail_metadatagemini_metadatamcp_metadatarouting_metadatamcp_arguments
This replaces the idea of many parallel JSON-string fields such as
messages_json, headers_json, and mcp_arguments_json.
Instead of many separate JSON blobs, Airlock should prefer one nested payload:
{
"messages": [...],
"response_text": "ok",
"headers": {"x-request-id": "..."},
"response": {...},
"guardrail_metadata": {...},
"gemini_metadata": {...},
"mcp_metadata": {...},
"routing_metadata": {...},
"mcp_arguments": {...}
}Payload capture is intended to be adjustable per field.
Operationally, this means:
- a global
offmode is supported - individual payload members can be enabled or disabled independently
- the TUI can expose these toggles as checkboxes
- configuration can represent the selected payload fields as CSV
The goal is to let operators choose a smaller privacy footprint by default, while still enabling richer drill-down when needed.
Some fields are not raw request inputs, but they are still request-local and deterministic enough to compute once and store on the event record.
Current example:
cost
Airlock's current direction is:
- maintain a provider/model pricing table
- refresh it on an interval, likely weekly
- keep the newest pricing recordset in memory
- compute event-level cost at write time using the request timestamp and the pricing snapshot in effect for that request
This gives fast request writes and stable per-record billing data without requiring repeated recomputation in the TUI.
Multi-request rollups should not be stored on every RequestLog event.
Examples:
- MTD / YTD totals
- per-client rollups
- error aggregates
Near-term plan:
- compute these from raw events or projections when queried
Roadmap:
- add dedicated aggregate tables or summary nodes if read frequency or data volume makes on-demand summing too slow
This keeps the raw event record simple and prevents mixing facts with pre-aggregated views.
All stored and computed times are in UTC.
That applies to:
- event timestamps
- pricing lookups for per-record cost
- rollup windows and summary calculations
Timezones are only applied at display time, if a client or UI needs them.
The current intended operator model is:
- Event field allowlist: configurable as CSV
- Payload field allowlist: configurable as CSV, with
offsupported - TUI controls: checkboxes to enable or disable fields without editing raw YAML
Example direction:
fathom:
event_fields: timestamp,success,error_flag,model,airlock_provider,request_id,call_id,prompt_tokens,completion_tokens,total_tokens,cost,duration_ms,failure_category,call_type,mcp_tool_name,mcp_server_name,airlock_client,error_type,user,team
payload_fields: offOr:
fathom:
payload_fields: messages,response_text,headers,response,guardrail_metadata,gemini_metadata,mcp_metadata,routing_metadata,mcp_argumentsThis page documents the user-facing model and intended controls. The exact config key names and TUI controls may evolve as implementation catches up.
Today, Airlock already writes a compact Fathom record for each logical request when Fathom logging is enabled, and it already uses Fathom for some billing and advisor read paths.
The richer model described here is the direction for making Fathom a stronger operational store:
- compact event fields for common queries
- optional nested payload JSON for rich inspection
- per-record derived cost
- projections now, aggregate tables later