Lineage and history
“Where does this number come from?”, “who loaded this?”, “when did this order change?” Ontyx records the answers as data arrives, and gives them back when you ask. Everyday answers don’t carry this history; you ask for it when you want it.
What’s recorded
Every data version records, when it lands:
- How it came in: sent in a conversation, a file uploaded through a link, pushed by a script, or built by Ontyx from other datasets.
- Who loaded it: the member whose assistant sent it or who finished the upload, by the name and email their profile had at that moment, or the push key by the name it was created with. The key itself is never stored.
- When it landed, and the date its data is as of.
- How it was added: replaced, appended or upserted.
- The version it was built on, so the versions form an unbroken chain.
- The structure version it was written under.
- For a dataset built from other datasets, the data version of each one it was built from.
This is written once and never changed. Renaming a key or a person later doesn’t rewrite who loaded an earlier version. A load that changes nothing (every row already there exactly the same) makes no version, so nothing is recorded for it.
Every approved metric version records what it reads at the moment it’s approved: the dataset, the columns, the relationship it reaches other datasets through, and the structure version of each.
Every saved view records the version of each metric in force when it was saved, and who saved it and when.
Every structure version records who proposed it, who approved or rejected it and when, what it changed, and the reason given for a rejection.
Loads from before Ontyx recorded who loaded them still name the member or the key where the load’s reference says who it was. The answer marks those as worked out from the reference, and says “unknown” only when nothing says who.
Asking where data came from
Ask your assistant in your own words: “Where does the orders data come from?”, “Who loaded this, and when?”, “What is the revenue number counted from?”. It calls lineage for one dataset, one metric or one saved view.
For a dataset, the answer gives the version in force (or an earlier one you name) with everything recorded above, then the loads before it, newest first (5 unless you ask for more, up to 50), and the datasets its rows point at with their current versions. It ends with a summary sentence:
"orders" is at data version 2, as of October 31, loaded in a conversation by Dana Reyes on October 31 at 19:20 UTC. It updated and added rows on top of data version 1. The load before it is listed in history.For a metric, the answer gives the version and its sentence, whether it’s retired and why, what it reads as recorded when it was approved (each dataset, its columns, and whether that dataset’s structure has changed since), then each of those datasets traced the same way:
"Revenue" (metric version 1) reads orders.amount, orders.customer, orders.ordered_on, orders.region and orders.status. "orders" is at data version 2, as of October 31, loaded in a conversation by Dana Reyes on October 31 at 19:20 UTC.A load pushed by a script reads “pushed by key “nightly-export"", and a dataset built from others reads “built from “orders” (data version 2)”. Times are in UTC.
For a saved view (“What does the monthly report depend on?”), the answer gives each metric the view uses, at the version in use, with the version the view was saved with, what it reads and whether it’s behind; then every dataset behind those metrics, once each, with its data version, what it points at and what it’s built from. It ends with a summary, such as:
The view "monthly" uses "Average permit cost" (metric "avg_cost", version 1) and "Permits" (metric "permits_count", version 2; the view was saved with version 1). Their numbers come from "permits" (data version 2) and "weather" (data version 1). "permits" points at "weather". At the last check, 1 row pointed at a row that isn't in "weather". Trace a metric with lineage metric: "avg_cost" (or a dataset with dataset) for where its data came from, who loaded it and when.A name given as the wrong kind (a view’s name asked as a metric, say) is answered with what the name actually is.
On the dashboard
The dataset page, the metric page and each saved view’s card show the same connections as a graph: what the item depends on to its left, what depends on it to its right, with each arrow running from an item to what uses it, and links that are behind marked. Pointing at a link says how the two are connected. See Dashboard.
Versions
Every answer that returns data names the data version and the structure version it read (see Asking questions). Because versions never change once written, an earlier one can be asked again:
- A question (
dataset_query) or a metric (metric_query) takes adata_versionto answer from an earlier load: “what did revenue for September say last week?” - A metric also takes a
metric_version, to ask an earlier approved version of it. - A dataset’s structure history (
dataset_get_schema) lists the last 20 structure versions with who decided them.
On the dashboard, each dataset’s History lists its loads and structure versions, and each metric’s page lists its versions.
One row’s history
On a dataset where something identifies each row, you can follow one row through its versions: “How did order A-1006 change, and who changed it?” Your assistant calls dataset_row_history with the row’s identifier, such as { "order_id": "A-1006" }.
The answer covers the latest 20 data versions, oldest first. For each one: the row’s values, who loaded that version and how, when, and whether the row was unchanged, changed (which columns, with the value before and after), added or removed. It says when there are earlier versions it didn’t show.
The row of "orders" where order_id is "A-1006", in the 2 data versions shown (data version 1 to data version 2, oldest first): it's in data version 1; changed in data version 2 (status). Data version 2 is the data in force, as of 2026-10-31T19:20:29.325Z, and the row is in it.with, for data version 2:
{
"data_version": 2,
"source": "inline",
"loaded_by": { "kind": "user", "id": "user_…", "name": "Dana Reyes", "email": "dana@example.com" },
"load_mode": "upsert",
"change": "changed",
"changed_columns": ["status"],
"changes": [{ "column": "status", "from": "open", "to": "shipped" }]
}A dataset with nothing that identifies its rows can’t be followed this way: there’s no way to tell one row from another across loads. Declaring what identifies each row (your assistant can test a choice first) makes it possible.