We turned our website into a knowledge graph. Then it confessed.
A website becomes a knowledge graph when every entity it describes carries one identifier, every type comes from a shared vocabulary, and every link between them resolves. We rebuilt agentu.co.uk that way using the Vault-LD spec, giving each page a Markdown twin whose frontmatter is linked data. The graph immediately exposed defects the HTML never showed, including our founder existing as two separate entities.
We published the theory. Then we audited ourselves.
We published a piece arguing that AI agent memory should be linked data: keep the markdown, add a shared @context, and a folder of notes becomes a knowledge graph the rest of the web can join.
Then we read our own site the way an AI agent does.
We were not doing it.
This is the build log: what we found, what we shipped, where the specs pushed back, and what any company can copy. The theory post makes the argument. This one is the receipt.
What an AI agent actually got from us
Every key page on agentu.co.uk already served a Markdown version for AI agents - a clean, low-token twin advertised in each page's HTML. We were proud of it.
It also threw away every fact we had.
The HTML page declared an author, a publish date, a category, structured takeaways and a cited FAQ. The Markdown twin an AI agent fetched? A heading and prose. All island, no meaning - the precise failure our own post described.
Worse. Our category taxonomy - six carefully defined topic clusters - existed only as a JavaScript object inside the build. A controlled vocabulary nothing outside the build could read. We had invented a vocabulary by accident, exactly as we warned you that you probably had.
One human, two identities
The audit's real find was quieter and worse.
Our founder existed twice in our own structured data.
The About page declared https://agentu.co.uk/about#founder. The blog byline declared https://agentu.co.uk/#person-daniel-byrne. Same human. Two identifiers, two bios, two entities.
Answer engines deduplicate by identifier. Split the identifier and you split the signal: every article he wrote was crediting a different person from the one the About page vouches for. We had been shipping that for weeks, and no scanner flagged it, because both nodes were individually valid.
Look closely at those two strings, because the bug is hiding in plain sight. One is scoped to a page. The other is scoped to the site. That is the whole defect, and it generalises into a rule worth stealing:
A person's identifier belongs to the site, not to the page that happens to mention them.
/#person-daniel-byrne, never/about#founder.
Scope an identifier to a page and you have quietly promised the world that the founder on your About page and the author of your articles are two different things. Answer engines will believe you. They have no reason not to.
That is the case for linked data in one sentence: the defects that cost you citations are invisible until you treat your site as a graph.
What a website-as-knowledge-graph looks like
Every key page on agentu.co.uk is now readable two ways. Humans get the HTML. AI agents get a Markdown note with YAML-LD frontmatter. Here is the twin of the theory post, abridged:
--- id: "https://agentu.co.uk/insights/ai-search-aeo/vault-ld-agent-memory-linked-data/#article" type: "schema:BlogPosting" title: "Vault-LD: how agent memory joins the web's knowledge graph" author: "[[daniel-byrne|Daniel Byrne]]" publisher: "[[agentu|Agentu Ltd]]" category: "[[ai-search-aeo|AI Search & AEO]]" datePublished: 2026-07-11 ---
Three design choices carry all the weight.
- The notes carry no
@contextof their own. The vocabulary lives once, at /context.jsonld, and maps our field names to schema.org wherever a term already exists. That is Vault-LD's core rule, and it is why the notes stay terse enough to read. - The identifiers are shared, not parallel. A note's
idis the same IRI the page's JSON-LD already declares. The Markdown face and the HTML face are one graph described twice, not two descriptions that happen to agree. - The wiki links resolve.
[[daniel-byrne]]is not decoration; it points at a real note at /insights/authors/daniel-byrne.md, which carries the founder's single, reconciled identity. The author, the categories and the company are all notes an AI agent can follow.
The founder now exists once. The taxonomy is now six addressable terms with definitions any AI agent can fetch. And the whole vault is enumerable from one place: /index.md lists every concept on the site with its description, generated from the same data as the notes themselves.
You can fetch all of this right now. That is rather the point.
The identifiers, in full
Naming is the hard part, so here is every convention we settled on. Steal it. The only rule that really matters is that a thing gets one of these, forever, and both faces of the site use the same string.
| Entity | Identifier pattern | Example |
|---|---|---|
| The company | {site}/#org | https://agentu.co.uk/#org |
| A person | {site}/#person-{slug} | .../#person-daniel-byrne |
| A service | {site}/#service-{key} | .../#service-core |
| A page that already declared a node | {site}{path}#{nodetype} | .../about#aboutpage |
| A page that declared none | {site}{path} | .../solutions/ |
| An article | {canonical}#article | .../insights/seo/foo/#article |
| A taxonomy term | {site}/insights/{slug}/#term | .../insights/seo/#term |
Note the two rules for pages. If the page already declared an entity in its JSON-LD, reuse that identifier rather than minting a second one. If it declared nothing, the page's own URL is its identifier. Either way you invent nothing, which is exactly the discipline that keeps a founder from becoming two people.
Where the specs pushed back
We would not trust a case study with no friction in it, so here is ours.
Our filenames broke identity minting
Vault-LD mints a note's identifier from its file name alone - and our source files are all called index.mdoc, one per article folder, because that is how our CMS colocates images. Filename minting would have collided every post onto one identifier. The spec's escape hatch is an explicit absolute id, which overrides minting entirely. Every note now carries one. The lesson generalises: the vault is an export, not your filing system. Conformance lives on the generated face, and your source tree can stay however your tools need it.
We were squatting on a reserved name
Our twins are conformant with Open Knowledge Format 0.1, Google Cloud's markdown-plus-frontmatter spec: every note carries a non-empty type, and we honour the two reserved filenames the format sets aside for structure rather than content.
That second part is what we got wrong first. OKF reserves index.md and log.md at any level of the directory tree for navigation and change history, not concepts, and says they must carry no frontmatter. Ours was the homepage pitch - full frontmatter, zero navigation. The prose moved to /agentu.md, now the Organization note every publisher and worksFor link resolves against, and /index.md became what the format says it should be: a plain index of every concept on the site, generated fresh on every build.
We type notes with a schema.org code, not a wiki link
Vault-LD's conformance checklist prefers type: "[[BlogPosting]]", a link to a class note. We emit type: "schema:BlogPosting" instead, because linking would mean publishing a page for every schema.org class purely to be a link target, and a dangling link is the one thing a conforming tool must flag. The spec permits the code form on values that reference a foreign vocabulary. We documented the deviation where we made it. Early specs reward that honesty; silent deviations are how interoperability dies.
The build now refuses to lie
The quiet failure mode of any knowledge graph is rot. Rename an author, retire a category, and a link that resolved yesterday dangles today - while the site looks perfectly fine to a human.
So the deploy gate reads the vault the way an AI agent would. Every note must carry a type and one absolute identifier. Every wiki link must resolve to a note that exists. No two notes may claim the same identifier. Any failure fails the build, and a build that fails does not ship.
Testing that gate found a second, older bug within the hour.
Cloudflare joins the headers of every rule that matches a URL. It does not let the most specific rule win, which is what we had assumed, and what most people assume. Our rule for the blog HTML (/insights/*, revalidate immediately) therefore also matched /insights/<cat>/<slug>.md. Every article twin had been going out like this:
Cache-Control: public, max-age=0, must-revalidate, public, max-age=3600
Two contradictory cache policies in a single header, shipping since the day the twins launched. The fix is one character: scope the HTML rule with a trailing slash, /insights/*/. Blog URLs all end in a slash; twins never do.
Nothing looked wrong. No validator on earth checks that. Going looking is the only audit that works - and looking means reading what your server actually returns (curl -I), not what you believe you configured.
Then test the gate itself. Break a wiki link on purpose, rebuild, and watch it exit 1. A gate you have never watched fail is not a gate. It is a hope.
What it cost, and what it did not
About a day, end to end. No database. No new vendor. No change to our security policy - a markdown file and a context file touch nothing that executes. Every step is reversible: delete the context and the twins degrade back to plain markdown, which is where we started.
Set that against what the same day bought: the entity defect fixed, a taxonomy made public, a machine-readable face for the whole site, and a build that guards all of it. The 40% of agentic AI projects Gartner expects to fail by 2027 will not fail for lack of ambition (Gartner, 2025). They will fail on foundations nobody audited. This is what auditing the foundation looks like, and it fits in a day.
Copy this
- Fetch your own machine face. Whatever your site serves an AI agent - a Markdown twin, an llms.txt, a JSON-LD block - read it cold. Does it carry your facts, or just your prose?
- Count your entities. Pick the person or product you most need answer engines to trust, and find every identifier your site gives it. More than one is a leak.
- Publish your vocabulary once. A single context file, reusing schema.org wherever a term exists. Your types stop being strings that happen to match.
- Make the graph fail loudly. Whatever your build is, teach it that a dangling identifier is an error. Rot you cannot see is rot you will not fix.
The graph is the deep end. If you want the whole stack in order - crawlability, structured data, speed, then this - start with how to make your website readable to AI agents, which is the full build with this post as its last chapter.
Or start where we start: the free agent-readiness diagnostic reads your site the way an AI agent does, in about ten seconds. If you want the audit done for you, talk to Agentu.
Theory is a claim. A resolvable identifier is proof.
Frequently asked questions
How do you turn a website into a knowledge graph?
Give every entity you describe exactly one identifier, take your types from a shared vocabulary rather than inventing them, and make every link between entities resolve to something real. In practice that means serving each page in two faces: the HTML humans read, and a Markdown twin whose frontmatter carries the same facts as linked data, using the same identifiers the page's JSON-LD already declares. Then validate the whole graph on every build. We did this with the Vault-LD specification.
What did implementing Vault-LD actually involve?
Four things. A shared @context file at /context.jsonld that maps our field names to schema.org. Linked-data frontmatter on the Markdown twin of every page, article, category and author. One stable identifier per entity, shared between the HTML's JSON-LD and the Markdown face. And a conformance check that fails the build if any of it breaks. No database, no platform, no migration.
Why do your Markdown pages carry frontmatter now?
Because before, they threw the facts away. Our twins served clean prose to AI agents but stripped the author, date, category and structure that the HTML page declared. Now the twin leads with a YAML-LD block carrying those facts, resolved through the shared @context - so an AI agent gets the same knowledge graph a search engine gets, in the format it reads best.
Why did you give every note an explicit id instead of minting from filenames?
Vault-LD mints identity from the file name alone, and our source files are all called index.mdoc - one per article folder. Filename minting would have collided every post onto a single identifier. The spec's escape hatch is an explicit absolute id, which overrides minting entirely, so every note carries the same identifier its page already declares in JSON-LD.
Why is /index.md not your homepage any more?
The Open Knowledge Format reserves index.md as a navigation file: no frontmatter, just links with descriptions. We had been squatting on that name with marketing copy. The homepage prose moved to /agentu.md, where it is the Organization note other notes link to, and /index.md became a real index of every concept on the site, generated from the same data as the notes themselves.
How do you stop the graph rotting as the site changes?
The build refuses to ship it broken. Every deploy runs a conformance check over the generated vault: every note must have a type, an absolute identifier and no private @context, every wiki link must resolve to a note that exists, and no two notes may share an identifier. Rename an author or retire a page carelessly and the build fails before the web ever sees it.
Can I do this on my own site?
The discipline transfers even where the tooling differs. Name your types once, reuse schema.org wherever a term exists, give every entity you care about exactly one identifier, and make your machine-readable pages carry the same facts as your human ones. If your site already emits JSON-LD, the twins are a generated face away.