Back to How It Works

The Knowledge Graph

🕸️ Typed edges that carry their own evidence, traversal that is allowed to say “I don’t know”, and a prerequisite DAG that repairs its own cycles — plus an honest chapter on where the knowledge-graph claim runs out.

This site calls itself a knowledge graph. That phrase is cheap — a list of links with an emoji is not a knowledge graph, and plenty of things marketed as one are really a similarity blob with a force-directed layout on top. So here is the honest accounting: what the graph actually is, what reasons over it, what is genuinely in it today, and where the claim runs out.

What makes a graph a knowledge graph

Three things, and the third is the one most projects skip.

Nodes with types. Not "pages" — things of a particular kind. Edges with types. Not "related to" — a specific relation that means something different from the other relations. And edges with provenance: who asserted this, how confident were they, what is the evidence, and when did it stop being true.

Without typed edges you cannot ask a structural question. "What must I learn before this?" is only answerable if prerequisite is a different thing from related. Without provenance you cannot ever retract — and a knowledge base that can only accumulate is one that slowly fills with rot.

The schema

Six node types, declared once as an enum in src/db/schema.ts:

topic · skill · competency · technique · theory · tool

And six edge types, defined alongside them and mirrored exactly in the Python tier at roadmap-kg/kg/graph_store.py, so the two halves of the system cannot drift:

prerequisite · related · part_of · builds_on · contrasts_with · applies_to

That mirroring is deliberate. TypeScript and Drizzle own every write to the database; the Python agents may only propose mutations. The schema is the contract between them, so it is written down twice on purpose and asserted in tests.

Every edge remembers where it came from

An edge here is not just a pair of IDs. It can carry a confidence score, the evidence that justified it, who or what asserted it (created_by), and a bi-temporal pair — valid_at, the time the claim was true of the world, and recorded_at, the time the system came to believe it. Those are different clocks, and conflating them is how knowledge bases start lying to you.

Each edge also carries a status: proposed, committed, quarantined, or invalidated. Note what is missing from that list — deleted. Edges are soft-invalidated by stamping invalid_at and never removed, which means the graph can be replayed as it stood at any past moment (point_in_time() in kg/kg_memory.py). A wrong edge is a fact about what the system once believed, and that is worth keeping.

What is actually in it

A schema that supports provenance is not the same thing as a graph that has it, and this page would be worth very little if it blurred the two. So, counted live against the production database:

Concepts2,323
Concepts with at least one edge246
Edges356 — related 163, part_of 133, prerequisite 60
Edges carrying provenance60

The 60 prerequisite edges are the real ones. Each carries a confidence, a status, both timestamps, and an evidence span quoted from the curriculum that justified it — for example, the edge from Tokenization to Context Window Management is backed by the sentence "understanding tokenization mechanics is prerequisite to managing token budgets." They form the dependency DAG the rest of this page depends on.

The other 296 edges — every related and part_of — were written by an older seeding path with an empty metadata blob. No confidence, no evidence, no status. They are edges in the graph-theoretic sense and nothing more. And most of the 2,323 concepts are not concepts at all: they are section headings swept in from an interview-prep generator ("Amazon Web Services: What Interviewers Actually Ask"), which is why only 246 of them are connected to anything. The graph is a small, sharp core inside a large cloud of dust.

Reasoning is a traversal, not a vibe

When the graph is asked a question, kg/graphrag_agent.py does a breadth-first frontier expansion from the entry concepts — up to four rounds, capped at twelve nodes per frontier so it cannot blow up. It collects the subgraph it walked, renders it into the prompt as literal edges:

A --prerequisite--> B
B --builds_on--> C

and only then asks a model to synthesize an answer from that structure. The retrieval is deterministic graph code. The language model's job is narrow: turn a subgraph into a sentence.

And it is allowed to decline. If confidence does not clear 0.80, the agent abstains and returns no answer rather than a plausible one. An honest "I don't know" is a feature; the alternative is a system that is never wrong because it is never checkable.

The lesson page queries the graph while you read it

For a long time every graph-shaped thing on this site was a baked artifact — computed offline, frozen into JSON at build, shipped as a picture of a graph rather than a graph. The lesson page's "Recommended prerequisite" was not even that. It was the previous lesson in the same category: a curriculum ordering wearing the word prerequisite.

It is now a live query. When you load a lesson, the server walks the concept graph in D1 and asks what that lesson's concept actually depends on. The walk is a recursive SQL query, so a multi-hop traversal costs one round trip:

sql
WITH RECURSIVE walk(id, depth) AS (
  SELECT ?1, 0
  UNION
  SELECT e.source_id, walk.depth + 1
  FROM concept_edges e JOIN walk ON e.target_id = walk.id
  WHERE e.edge_type = 'prerequisite'
    AND COALESCE(json_extract(e.metadata, '$.provenance.status'), '') <> 'invalidated'
    AND walk.depth < ?2
)

Note the invalidated filter: that is the read side of the soft-delete contract. An edge the self-healing agent retracted is still in the table, and is still invisible to this walk.

Ask it what Advanced RAG depends on and it does not just name the obvious neighbours. It reaches Retrieval Strategies, Chunking, Reranking and RAG Evaluation at one hop; Embeddings and Evaluation Fundamentals at two; and Tokenization at three — because Advanced RAG needs vector databases, which need embeddings, which need tokenization. No human wrote that chain down. It is a consequence of the edges, and every link in it can show you the sentence that justified it.

The graph repairs itself

A prerequisite graph with a cycle is broken by definition — if A must precede B must precede C must precede A, there is no way to start learning. So kg/selfheal_agent.py runs a three-colour depth-first search over the active prerequisite subgraph looking for exactly that. When it finds a ring it does not throw the ring away; it finds the weakest edge in it — the one with the lowest confidence — and invalidates only that. The cycle breaks, the strong claims survive, and the retraction is recorded rather than silently applied.

This is also the first moment the confidence numbers have to be worth something. "Cut the weakest edge" is only a sane repair if the weights mean anything — which is the argument for making every edge justify itself at the moment it is written, not later.

The graph grows itself

New edges come from two-hop triadic closure (kg/kg_eval_discovery.py): if A connects to M and M connects to B, but A and B have no edge, that absence is a candidate. Most such candidates are noise, so each one is put to an LLM judge with a debate gate before it is even proposed — and a proposal still is not a commitment, because proposed is its own status.

All of it runs unattended. kg/autonomy.py:tick() sequences construct → heal → discover → consolidate and loops until nothing changes, on an hourly cron. consolidate() is the counterweight to growth: edge salience decays over time, so the graph forgets what stops mattering.

Vectors are not the graph

This site also runs a Qdrant vector store, and it would be easy to point at it and say "knowledge graph." It isn't one. Qdrant holds embeddings and text — it is the substrate for semantic search, for "explain this," for flashcards and audio cues. It stores no typed edges and performs no traversal. Vector search finds things that are similar; a graph encodes how things are related. Those are not the same question, and a cosine score will never tell you what you must learn first.

The two do meet in one place: kg/graph_view.py flattens each node's neighbours into its text ("Connected to: …") before embedding it, so retrieval gets a little structural context. That is graph-as-RAG-context. It is not a graph query engine, and calling it one would be the exact sleight of hand this chapter exists to refuse.

Three views you can click

  • The roadmap graph — 109 lessons, 257 typed edges, hand-rendered SVG with pan, zoom, and an upstream/downstream highlight cone.
  • The network graph — 352 nodes and 1,558 edges across lessons, source docs, papers, principles and terms, laid out with d3-force on a canvas.
  • The mind map — 3,747 nodes, drill-down by branch.

These three are still the baked kind: computed offline and shipped as static JSON, because the app runs on a Cloudflare Worker, which cannot read files from disk at request time. That constraint is about files. It never applied to the database — which is exactly why the lesson page's prerequisite walk could become a live query while these stayed pictures.

What this isn't (yet)

The uncomfortable half, stated plainly, because a page that only lists strengths is marketing.

Provenance is the exception, not the rule. 60 edges of 356 carry it. The related and part_of majority carry nothing, so any claim about this graph being fully auditable is, for now, a claim about one sixth of it.

Most of the graph is dust. 2,077 of 2,323 concepts are connected to nothing. They are generated interview-prep headings, not curriculum concepts, and they should be namespaced out of the graph rather than left to inflate its node count.

Only one of the six edge types is doing real work. There are no builds_on, contrasts_with or applies_to edges in production at all. The schema describes a richer graph than the data delivers.

The autonomous machinery is mostly unexercised. Self-heal looks for cycles in a 60-edge DAG that currently has none; discovery proposes edges into a graph whose density is low enough that triadic closure has little to work with. The code is real and tested, but it has not yet met a graph big enough to prove itself.

And the shipped graph views are still similarity-derived. In the network graph, 795 of 1,558 edges come from cosine similarity, against 618 explicit and 145 LLM-typed. A cosine edge is a guess that two things are alike — genuinely weaker evidence than a hand-declared prerequisite, and pretending otherwise would be the very thing this guide accuses other projects of.

The prerequisite DAG is the part that is real, and the lesson page now reads it live. The rest is a schema waiting for its data — which is a more honest place to be than the reverse.