Skip to main content
Capturing telemetry is half the job. The other half is getting answers out of it. hiloop runs a query engine over the event model so you can interrogate a thousand parallel experiments the way you’d interrogate a database — because that’s exactly what you do: you write SQL against a single 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 denormalized events 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.
The addressable columns are exactly the canonical event schema (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 events table and your own SQL data views — no information_schema, and only an allowlist of scalar functions;
  • automatically constrains every scan of events to 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 SELECT runs;
  • caps each query with an injected row limit, a memory ceiling, and a hard timeout.
So a query is both expressive (anything SELECT can say) and contained (it can only ever read your own rows).

Ad-hoc queries

Send a SELECT to POST /v1/telemetry/sql:
The response is a list of plain JSON object rows — null columns omitted, and 64-bit integer columns (counts, ts_wall_ns) encoded as decimal strings so values above 2^53 never lose precision:
The CLI wraps this with 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 in attributes_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 with payload_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.