The citation graph as substrate
what you'll learn · Why writing the same citation pattern through every layer is the thing that lets agents, sandboxes, and live trades share one audit story.
Once every decision is a row that names what it cites, four otherwise-separate features become one shape — and the platform you can build on top of that shape is meaningfully different from one you can't.
A research platform usually ends up with three or four separate "who-touched-what" stories. The lake has lineage. The model registry has version pins. The order book has audit trails. The notebook has a list of run-ids. Each of these solves its layer’s problem; none of them speak to each other. Asking "what thesis backed the trade I just looked at?" means joining four log files by hand.
This post is about what happens when you commit to one shape instead.
The shape
Every row in the system that records a decision carries a single column:
cited_artifacts: list[typed-prefix-slug]
The values are slugs prefixed by what kind of thing they reference:
note:walk-forward-without-leakage— a research note in/writing/.event:event_release/FOMC-2026-06— a scheduled event.agent:claude-opus-4-7/01951b40-...— a specific agent run.adr:0044— a design decision.
The prefix tells the citation resolver where to look; the slug is stable; the whole string is queryable. That’s the shape. Everything below is consequences.
Four citation surfaces, one column
The interesting part is what the same column carries on different surfaces:
| Surface | What it cites |
|---|---|
| Live order’s provenance row | The strategy, the features, the research notes the strategy embodies. |
| Agent’s leaderboard proposal | Same notes; plus the agent’s own self-reference (agent:<model>/<run_id>). |
| Sandbox strategy declaration | The notes the in-browser demo embodies. |
Writing note’s cites: frontmatter |
The other notes this thesis builds on. |
Four surfaces with substantively different lifecycles — order provenance is per-trade, agent proposals are per-research-run, sandbox declarations are per-page, writing cites are per-note — share one column shape. Which means they share one query.
Want to know everything that cites walk-forward-without-leakage?
One scan. Live trades, agent runs, sandbox demos, and other notes
all come back in the same shape.
Why the unified shape matters
Three things become possible that would otherwise be tedious.
1. The audit reads both directions
Read forward from a trade: order → provenance → strategy → cited notes → notes those build on. Read backward from a note: who cites this? Every sandbox + agent run + live trade that ever embodied the thesis. Both directions, one column, one resolver.
This is the citable book discipline I argued for elsewhere: "every position is a citation". You don’t get the audit by adding a layer; you get it by making sure every layer uses the same primitive.
2. AI agents inherit the audit story for free
An AI research agent’s proposal lands as a row with the same shape
as a human-authored proposal — just with kind='agent' and an
agent:<model>/<run_id> self-reference on the citations list.
Nothing else changes downstream. The promotion gate still sees one
shape. The attribution-by-thesis query still sees one shape. The
operator dashboard still sees one shape.
The agent doesn’t need a special integration. It needs a typed identifier and the same column.
3. Hallucinated citations fail at the boundary
Every typed-prefix slug must resolve. Note slugs resolve against the
writing manifest; agent self-references resolve against the model
registry; event slugs resolve against the event silver schema. A row
whose cited_artifacts contains an unresolved slug never lands —
the gate refuses at write time.
This is the structural defence I keep coming back to: the hallucination doesn’t get caught by a downstream check; it can’t even be expressed by the data model.
What’s load-bearing vs decorative
The shape is small. What makes it work is what gets refused, not what gets stored.
- The citation column is decorative if nothing reads it.
- The typed-prefix scheme is decorative if any caller can mint a novel prefix.
- The build-time validators are the load-bearing layer. They decide which strings can live in the column.
When I describe the citation graph to someone, the conversation
keeps wanting to be about the column. It’s actually about the
refusal contracts. ADR-0015 refuses unknown note slugs. ADR-0044
refuses agent runs that fail the consistency check. The build script
refuses sandbox declarations that cite drafts. The writing-graph
table on /writing/ would be a polite lie if any of these refusals
silently degraded.
What this isn’t
It isn’t a new abstraction. The column existed before the agents, before the sandboxes, before the writing-cites. It’s been the provenance column on every order since the platform’s first commit.
It also isn’t a graph database. There’s no specialised storage, no specialised query language, no specialised cache. Every layer keeps its rows in whatever shape suits it (parquet, JSON frontmatter, TypeScript module) — they just agree on the column when they write the decision row.
The substrate makes the differentiator
The 2026 research-platform market is full of frameworks where AI agents can propose strategies, sandboxes let users explore data, and quants write notes. What’s rare is a platform where all three sit on the same audit substrate.
That’s the differentiator: the citation graph isn’t a feature you turn on. It’s the condition every feature shares, the column that makes the conversation between four otherwise-separate surfaces a single audit story. The agent’s proposals, the sandbox’s demos, the quant’s writing, and the trader’s live book all join on one column. The audit story is one query because the substrate predates the features.
Build the substrate first. The features that compose are the ones worth shipping.