events table.
The engine reads across recent events held in memory and historical events on object storage, and
presents them as one queryable surface. You don’t manage that split; you ask a question and get
rows back.
One table, plain SQL
Every signal — a model call, a tool invocation, a line of stdout, an OpenTelemetry span, an annotation — lands in the same denormalizedevents table. There are
no joins to get right and no bespoke query language to learn. You SELECT over the
canonical event columns and the engine returns rows.
run_id, lineage_path, signal,
name, ts_wall_ns, http_status_code, attributes_json, payload_digest, …). An unknown
column is rejected before the query runs, with an error that names the offending identifier and
lists what’s available. Model-shaped fields (model, token counts, messages) are not columns — they
derive at query time from the captured attributes and payload bodies.
The query gateway keeps SQL safe
You send raw SQL, but you never reach another organization’s data or escape into the database. Every query runs through a per-organization gateway that:- exposes only the
eventstable and your own SQL data views — noinformation_schema, and only an allowlist of scalar functions; - automatically constrains every scan of
eventsto your own organization, derived from the request’s verified identity — you can’t widen it, so the SQL you write is organization-agnostic; - rejects DDL, DML, and multi-statement SQL — only a single
SELECTruns; - caps each query with an injected row limit, a memory ceiling, and a hard timeout.
SELECT can say) and contained (it can only ever read your
own rows).
Ad-hoc queries
Send aSELECT to POST /v1/telemetry/sql:
null columns omitted, and 64-bit integer
columns (counts, ts_wall_ns) encoded as decimal strings so values above 2^53 never lose
precision:
hiloop query, and the SDKs call it from your code. See the
querying guide for runnable CLI and SDK examples.
Querying custom attributes
Arbitrary attributes an agent emits land inattributes_json. Reach into them with the
hiloop_json_get(attributes_json, '$.path.to.key') function — it walks a dot-separated path (the
leading $. is optional) and returns the addressed value as text:
Derive, don’t materialize
Everything model-shaped derives from the raw capture at query time. Payload bodies resolve from the content-addressed store withpayload_text, streamed responses reassemble with
hiloop_sse_reassemble, and provider JSON parses with the JSON-path functions — so a
data view like otel_genai_calls turns raw exchanges into
OpenTelemetry-GenAI-shaped rows with nothing precomputed at ingest. The standard token curves a
dashboard needs are a GROUP BY over that view — plain SQL, no special endpoint.
The public query surface is constrained SQL. Use the generated API reference for
exact schemas.