aigov!=aio11y, part 3: Building Track 3
- Daniel Rolles

- Jul 5
- 7 min read
Part two ended with Track 3 as the only one of three that answers the audit question — and with the admission that, today, nobody is shipping it. It also ended with something more concrete than a wish: two open GitHub issues, in two projects, both filed to start closing the gap.
So: what would shipping Track 3 actually take?
The agent as Job
Start with where the instrumentation lives, because it is not where people assume. It does not live in the agent's reasoning loop — the agent doesn't know, and doesn't need to know, that it is producing lineage. It lives at the boundary: the MCP server, or whatever gateway fronts it, at the point where a tool call resolves into an actual read against a data source.
That is a familiar pattern, not a new one. A Spark listener fires OpenLineage events without the engineer who wrote the job ever touching lineage code. A dbt adapter does the same for a model build. The instrumentation sits at the seam between "code that does the work" and "system that knows what the work touched" — and it fires regardless of what wrote the code.
For an MCP tool call, that seam produces a RunEvent. The agent invocation — this call, this session, this instance — takes the Job role. The call itself is the Run. The tables read, and the columns where they're derivable, are Input datasets. Whatever the call produces — rows returned into a prompt, a result the agent writes elsewhere — is Output.
The warehouse doesn't know it's talking to an agent. Lineage shouldn't need it to.
None of that requires a new entity type. Job, Run, Dataset is the same triple that already fires for a scheduled pipeline. What's different is only what sits on the other end of the Job — and that is where the real modelling problem starts.
Two kinds of lineage: declared and derived
Part two named the distinction: declaration-based lineage records a relationship that already existed before the event does; interaction-derived lineage creates the relationship by happening. Here is what that means for the schema, not just the argument.
A Job's name and namespace are normally facts about the world that predate any run — `orders_daily_etl` in `warehouse.jobs` is true whether or not it ran today. An agent invocation has no such pre-existing fact. It comes into being at the moment of the call, and the next call — same agent, same tool, different session — may touch entirely different tables in an entirely different way.
Facets are how OpenLineage already absorbs that kind of variation without a schema rewrite. Job facets, run facets and dataset facets are namespaced, versioned extensions bolted onto the core triple — the mechanism was built precisely so new kinds of job could show up without breaking old consumers. The runtime actor's identity — which agent, which model and version, which session, which conversation — belongs on a facet on the Run, not forced into the Job's pre-declared identity. The Job still needs a name; give it one derived from the tool and a stable hash. The facet is where the actor actually lives.
The open `type` field from the jobless lineage work does adjacent work at the event level: it lets a run event declare what kind of lineage it's carrying, rather than assuming every event describes a traditional scheduled job. Extending that field with an interaction-derived value gives consuming systems an explicit signal, not an inference: don't expect this Job name to recur, don't alert when it doesn't run again tomorrow, don't try to fit it onto a DAG. It tells the catalogue the truth — that it is looking at something structurally different from a pipeline, not a pipeline that happens to run irregularly.
Declaration-based lineage records a fact that was already true. Interaction-derived lineage creates the fact by happening — and the schema should say so, not leave it to be guessed.
Span or run event
Once both events fire off the same call, the practical question is where any given piece of information belongs. The rule that holds up:
The span carries who and how:
which agent, which model and version, which session
the tool name, and a reference to the call arguments — not the payload itself
latency, token count, cost
success, failure, retries, loop count
The run event carries what and where:
which datasets — tables, views, columns where derivable
the transformation applied, to the extent it's known: a read, a join, an aggregation
the output produced, and what consumed it downstream
schema, and column-level lineage facets where they can be derived
Exactly one fact needs to cross from one system to the other: the trace ID of the span that triggered the run event, carried as a facet on the run. Everything else stays where it already belongs — which is the entire point. This is not a merged schema. It is two schemas, each doing the job it was built for, meeting at a single shared key.
One fact crosses the boundary: the trace ID. Everything else stays home.
The RFCs, in the open
This isn't a proposal in the abstract. It is two open issues, in two repositories, under two Linux Foundation projects.
OpenLineage issue #4484 proposes roughly the extension above: a runtime-actor facet, and an interaction-derived value on the jobless `type` field — scoped deliberately small, an addition to the existing spec rather than a new one. It is under discussion, not merged. The shape of the actor facet in particular is still being argued over, and it should be: what identifies an "agent" in a multi-tenant deployment, and whether model version belongs on the facet or in the job name, don't have one obviously correct answer yet.
MCP issue #2638 approaches the same gap from the other side: should the MCP specification say anything at all about lineage, or is this purely a concern for servers and gateways to solve on their own? Today it is a tracking issue, not a proposal — flagging that any MCP server touching a data source is sitting on a lineage-shaped gap, and pointing at the OpenLineage RFC as prior art rather than asking MCP to invent its own model from scratch.
Both projects now sit under the Linux Foundation — OpenLineage under LF AI & Data, MCP under the Agentic AI Foundation — which matters more than it sounds. Cross-community proposals like this usually die from having nowhere to live. This one has an institutional home on both sides of the boundary it's trying to instrument.
Why open standards make this cheaper than it looks
The honest worry, on hearing "extend two open standards and get two communities to agree," is that this is a multi-year effort. It isn't — and the reason is what already exists on both sides before a single line of new code gets written.
OpenLineage has already solved the ecosystem problem. Purview, Dataplex, DataZone, DataHub, Atlan — most of the catalogue and governance platforms an enterprise is already running — ingest OpenLineage events today, unmodified. Ship one correctly modelled emitter at the MCP boundary, and it lands in whichever of those an organisation already has. Track 3 does not need bespoke integrations into five governance platforms. It needs one emitter, aimed at a format five platforms already read.
MCP has already solved the boundary problem. The interception point Track 3 needs — somewhere between the agent's tool call and the actual data source — is infrastructure MCP gateways and servers already provide for auth, rate-limiting and logging. Firing a lineage event alongside those existing hooks is additive. It is not a new architecture layered on top of an old one.
Compare that to the Track 2 alternative from part two: enriching spans with dataset semantics meant fighting retention mismatches, payload-sensitivity guidance, and a consumer ecosystem that doesn't speak OTLP at all. Track 3's cost is smaller for a specific reason — it isn't inventing a home for the data. It's plugging into two homes that already exist, at the one point where both already meet.
The standards did the hard part already. This is instrumentation, not invention.
What it looks like, concretely
If you're building this today, the shape of it is roughly:
an interceptor at the MCP boundary — server-side or gateway-side — that fires on any tool call touching a data source
a RunEvent per call: the tool/agent pairing as Job, the invocation as Run, the tables touched as Dataset
a runtime-actor facet on the Run carrying agent, model, version and session
the interaction-derived value set on the jobless `type` field
the OTel trace ID of the triggering span, carried as a single fact on the run event
nothing else changes — the catalogue you already run ingests it exactly as it would any other OpenLineage event
That's it. No new catalogue, no new query language, no new team.
Same mistake, cheaper this time
The point of this series was never that governance is wrong. It's that governance without an evidence layer is a policy and a hope — and that we already ran that experiment once, on data, and watched it take a decade to fail slowly enough for anyone to notice.
Agentic AI does not get a decade. But it does get something data observability didn't have in 2013: two mature open standards, an existing consumer ecosystem, and — as of two GitHub issues — a live, cross-community conversation about the one gap between them. That is the entire difference between "write policy and hope" and "instrument the boundary and know."
aigov!=aio11y
They are linked. But they are distinct. Now go build the evidence layer.

About the author: Daniel Rolles is CEO and Founder of BearingNode, and one of the key authors of BearingNode's Data and Information Observability Framework. He has spent over 30 years in data, analytics and AI, and is the author of the OpenLineage RFC on lineage for runtime actor-initiated data interactions referenced in this series.



