spans, runs, records and batches) holding the full execution history of your plays, tools and agents.
Queries are read-only and automatically scoped to your workspace: you never write a workspace_uuid filter, and you can’t reach another workspace’s data.
Where you can run a query
- CLI
- API
- Alert
This is ClickHouse SQL
The dialect is ClickHouse, not PostgreSQL, so the idioms differ from most warehouses:Rules and limits
Only a singleSELECT runs. INSERT, UPDATE, DELETE, DDL and multiple statements separated by ; are all rejected before execution, as is any table other than the four below. Reference them unqualified (spans, not orchestration.spans) and name your own CTEs freely.
All four tables are
ReplacingMergeTree, and queries run with FINAL: you
always see the latest version of each row, with no deduplication of your own
needed.spans
One row per node execution, the table to reach for when you’re measuring how individual actions behave. Runs that were idle or skipped never produce spans.
runs
One row per run, the whole execution of one record through one workflow. Unlike spans, this includes runs that ended up idle or skipped.
The executions nested column
runs and records both carry the run’s node executions as a nested column, which reads as parallel arrays: executions.status, executions.credits_used_count, and so on. Its fields mirror the spans columns of the same name, minus the node_/execution_ prefixes: node_uuid, node_slug, node_kind, node_action_slug, node_connector_uuid, node_integration_slug, node_tool_uuid, node_agent_uuid, node_release_uuid, node_batch_uuid, node_run_uuid, node_message_uuid, node_child_index, node_wait_until_finished, next_node_uuid, status, error_message, title, icon_url, credits_used_count, started_at, updated_at, finished_at.
Array functions work on them directly, so you can ask how many steps a run took, what it cost, whether any step failed:
records
The same shape as runs, keyed by the record rather than the run, and excluding idle and skipped runs. Use it to answer “what happened to this record”, where runs answers “what did this execution do”.
It differs from runs in four places: id and title replace record_id and record_title, run_uuid replaces uuid, parent_run_uuid replaces parent_uuid, and status can’t be idle or skipped. There is no deleted_at. Everything else, including executions, is identical.
batches
One row per batch, a play or tool triggered over a set of records.
data.kind names the trigger (segment, change, filter, recordIds, records, file, runs, group, schedule, form or watchedRecords), and the rest of the tuple carries the trigger’s payload: model_uuid, segment_uuid, change_uuid, change_kinds, ids, limit, webhook_url, the stringified_* filter and sort, the parent_* and group_* links, and the record counts total_records_count, added_records_count, updated_records_count, removed_records_count and unchanged_records_count. Read a field with dot access: data.kind, data.segment_uuid.
Example queries
Error rate over the last hour, as a percentage:nullif on the divisor keeps an hour without spans as a NULL rather than the
0 / 0 a bare count(*) would give. As an alert scope that reads as an empty
window, so a quiet hour never reports a rate.
The slowest integrations yesterday:
Next steps
Alert on a query
Turn any of these queries into a scheduled threshold check that fires
actions when it breaches.
Monitor from the CLI
List runs, batches, spans and traces without writing SQL.

