---
id: "https://agentu.co.uk/insights/ai-search-aeo/turn-your-website-into-a-knowledge-graph/#article"
type: "schema:BlogPosting"
title: "We turned our website into a knowledge graph. Then it confessed."
description: "We rebuilt agentu.co.uk as a knowledge graph: linked-data notes with shared identifiers. Within the hour it exposed defects our HTML had hidden for weeks."
resource: "https://agentu.co.uk/insights/ai-search-aeo/turn-your-website-into-a-knowledge-graph/"
answer: "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."
author: "[[daniel-byrne|Daniel Byrne]]"
publisher: "[[agentu|Agentu Ltd]]"
category: "[[ai-search-aeo|AI Search & AEO]]"
datePublished: 2026-07-11
dateModified: 2026-07-11
tags:
  - "Linked data"
  - "Agentic web"
  - "Knowledge graphs"
  - "Case study"
keywords:
  - "Website knowledge graph"
  - "Linked data website"
  - "Entity SEO"
  - "Structured data audit"
  - "Entity consistency"
  - "Vault-LD"
  - "YAML-LD"
  - "Open Knowledge Format"
  - "OKF"
  - "JSON-LD"
  - "Markdown for AI agents"
takeaways:
  - "A website becomes a knowledge graph when its entities share identifiers with the rest of the web. Ours now do: every key page carries a Markdown twin whose linked-data frontmatter resolves through one shared vocabulary at /context.jsonld."
  - "The graph confessed within the hour. Our founder existed as two separate entities in our own structured data, and answer engines deduplicate by identifier - so we were splitting the exact trust signal we tell clients to concentrate."
  - "Our topic taxonomy existed only as a JavaScript object inside the build. A controlled vocabulary nothing outside the build can read is not a vocabulary."
  - "Google's Open Knowledge Format reserves index.md for navigation, so our homepage twin moved to /agentu.md and /index.md became a genuine index of every concept on the site."
  - "A conformance check now runs on every build: a dangling link, a missing type or a colliding identifier fails the deploy. A broken graph cannot ship."
  - "Total cost: about a day. No database, no migration, no change to our security policy, and every step is reversible."
---

# 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.

*AI Search & AEO · Daniel Byrne · 2026-07-11*

## Key takeaways

- A website becomes a knowledge graph when its entities share identifiers with the rest of the web. Ours now do: every key page carries a Markdown twin whose linked-data frontmatter resolves through one shared vocabulary at /context.jsonld.
- The graph confessed within the hour. Our founder existed as two separate entities in our own structured data, and answer engines deduplicate by identifier - so we were splitting the exact trust signal we tell clients to concentrate.
- Our topic taxonomy existed only as a JavaScript object inside the build. A controlled vocabulary nothing outside the build can read is not a vocabulary.
- Google's Open Knowledge Format reserves index.md for navigation, so our homepage twin moved to /agentu.md and /index.md became a genuine index of every concept on the site.
- A conformance check now runs on every build: a dangling link, a missing type or a colliding identifier fails the deploy. A broken graph cannot ship.
- Total cost: about a day. No database, no migration, no change to our security policy, and every step is reversible.

## We published the theory. Then we audited ourselves.

We published a piece arguing that
[AI agent memory should be linked data](/insights/ai-search-aeo/vault-ld-agent-memory-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:

```yaml
---
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 `@context` of their own.** The vocabulary lives once, at
[/context.jsonld](/context.jsonld), and maps our field names to
[schema.org](https://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 `id` is 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](/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](/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](https://github.com/GoogleCloudPlatform/knowledge-catalog)
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](/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](https://www.gartner.com/en/newsroom/press-releases/2025-06-25-gartner-predicts-over-40-percent-of-agentic-ai-projects-will-be-canceled-by-end-of-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

1. **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?
2. **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.
3. **Publish your vocabulary once.** A single context file, reusing schema.org
wherever a term exists. Your types stop being strings that happen to match.
4. **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](/insights/ai-search-aeo/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](/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](/contact/?interest=insights).

> 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.

---

Source: https://agentu.co.uk/insights/ai-search-aeo/turn-your-website-into-a-knowledge-graph/
Agentu Ltd - Website Growth Agency that AI Agents Recommend. https://agentu.co.uk
