> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hiloop.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# The event model

> The narrow, signal-typed schema every piece of hiloop telemetry shares.

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](/concepts/query-engine). 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

| Field          | What it is                                                                                                                |
| -------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `run_id`       | The [run](/concepts/run-scoped-observability) the event belongs to.                                                       |
| `lineage_path` | The producing run's dotted position in its tree (e.g. `01H8A.01H8B`). Derived from the run; enables subtree prefix scans. |
| `signal`       | The event family (see below).                                                                                             |
| `name`         | The event name within the signal (e.g. a model name, a tool name).                                                        |
| `ts_wall_ns`   | Wall-clock time in nanoseconds.                                                                                           |
| `ts_logical`   | Logical counter used with wall time to preserve causal ordering.                                                          |
| `attributes`   | A map of typed scalar key/value pairs.                                                                                    |
| `payload_ref`  | Optional reference to a large payload stored out of line.                                                                 |
| `event_id`     | Stable event identity for idempotent ingest and deduplication.                                                            |

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:

| Signal       | What it records                                                                                         |
| ------------ | ------------------------------------------------------------------------------------------------------- |
| `llm`        | Model (LLM) exchanges — the request and response exactly as they crossed the wire, bodies by reference. |
| `net`        | Every other HTTP exchange captured at the network boundary — tool calls, MCP traffic, downloads.        |
| `log`        | The workload's stdout/stderr, and the agent's own OpenTelemetry logs.                                   |
| `exec`       | Command lifecycle — what ran, its exit code, its duration.                                              |
| `span`       | The agent's own OpenTelemetry spans, if it emits them.                                                  |
| `metric`     | Resource utilization samples, measured from outside the sandbox.                                        |
| `egress`     | Network egress policy decisions — allowed, blocked, warned.                                             |
| `runtime`    | What the platform did: sandbox state transitions, operation lifecycle, and queue latency.               |
| `annotation` | Structured knowledge you write against runs and events.                                                 |

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. `annotation`s are whatever you write, whenever you
write them. The full per-signal reference — event names, attributes, correlation keys — is
[the signal reference](/observability/event-model#signals).

## Queryable columns

The query engine exposes a fixed column allowlist. Unknown columns are rejected instead of being
interpreted dynamically.

Common identity and time columns:

| Column         | Kind    |
| -------------- | ------- |
| `event_id`     | Text    |
| `project_id`   | Text    |
| `run_id`       | Text    |
| `lineage_path` | Text    |
| `ts_wall_ns`   | Numeric |
| `ts_logical`   | Numeric |
| `signal`       | Text    |
| `name`         | Text    |

Payload and promoted network columns:

| Column               | Kind    |
| -------------------- | ------- |
| `attributes_json`    | Text    |
| `payload_digest`     | Text    |
| `payload_media_type` | Text    |
| `payload_size_bytes` | Numeric |
| `schema_version`     | Text    |
| `trace_id`           | Text    |
| `span_id`            | Text    |
| `parent_span_id`     | Text    |
| `http_method`        | Text    |
| `http_host`          | Text    |
| `http_target`        | Text    |
| `http_status_code`   | Numeric |
| `http_exchange_id`   | Text    |

## 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](/concepts/query-engine)
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](/observability/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](/concepts/query-engine).
