Skip to main content
Everything hiloop captures — a model call, a tool invocation, an MCP message, a line of stdout, an OpenTelemetry span — becomes one event in a single, deliberately narrow schema. The schema doesn’t try to model the shape of your agent harness. It captures at a layer that doesn’t care about that shape and leaves reconstruction to query time. That’s not a limitation we’re apologizing for; it’s the point. Harnesses churn, and a schema that bakes in one harness’s assumptions rots with it.

What an event carries

Organization and project are assigned from the authenticated request context at ingest time. They are not trusted from individual event payloads. Two design choices make the model fast and honest:
  • Hybrid logical clocks. Events carry a wall-clock time and a logical counter, so hiloop can order them causally even when they come from different machines whose clocks don’t agree.
  • Content-addressed payloads. Large bodies — a full prompt, a completion, an HTTP response — don’t live inline. They’re stored once in a content-addressed blob store and referenced by digest, so the event stream stays small and identical payloads are stored once.

Signals

Every event belongs to one signal — the family of thing it describes: The first five describe your workload and come from the shared capture layer: explicitly through hiloop run on a host, or through a sandbox’s managed entrypoint, exec, SSH, cooperative HTTP, and OTLP boundaries. metric is a platform observation about the workload (resource samples recorded from outside the sandbox), and egress and runtime are platform metadata — resource identities and state vocabulary, never workload content. annotations are whatever you write, whenever you write them. The full per-signal reference — event names, attributes, correlation keys — is the signal reference.

Queryable columns

The query engine exposes a fixed column allowlist. Unknown columns are rejected instead of being interpreted dynamically. Common identity and time columns: Payload and promoted network columns:

Attribute values

Attributes are intentionally scalar. In every JSON surface an attribute value is a bare scalar — a string, a number, or a boolean — with one rule for integers: every 64-bit integer value (nanosecond timestamps and integer attributes alike) is encoded as a decimal string, because values above 2^53 silently lose precision as JSON numbers. No nested objects, no arrays. Anything large or structured goes to the content-addressed payload store and is referenced, not embedded. This keeps the columns you query typed and predictable.

Why one table

Because every signal lands in the same schema, keyed by the same run lineage, a question like “which experiments made an expensive model call after the third tool failure” is a filter over one relation — not a join across six bespoke telemetry systems. And because annotations are just events too, marking a run (“this branch is the baseline”) is the same write path as everything else. Next: how you query it.