Insights2026年5月30日11 min read

The roadmap is the curriculum. Stop shipping a syllabus.

Every learning product is shaped like a playlist. The world isn't a playlist. What we learned shipping Orno, a graph-native learning platform — and why the breadcrumb beat the beautiful graph we spent three weeks on.

A directed graph of connected lesson nodes — most greyed and locked, three glowing as completed, one pulsing as the next-best step — overlaid on a hand-drawn module list crossed out.

Every learning product is a playlist. Lesson one, lesson two, lesson three. The world isn't a playlist. We built Orno because the syllabus is the lowest-resolution map of a real subject — and we wanted to ship the structure itself. This is what happened when we tried.

There's a sentence I have said in three different product reviews this year, to three different teams, about three different learning products. The sentence is: "This isn't a course. This is a list pretending to be a course."

Each of those teams was, at the moment I said it, building something genuinely good — an internal certification path, a paid bootcamp, an AI-driven self-study tool. They had spent real money on instructors, video production, an LMS that didn't suck, a brand. And in every case the underlying primitive was a list of lessons, sequenced top to bottom, with the user advancing through it like a YouTube playlist. The vocabulary they used to describe their own product gave the game away: chapter, module, week, next.

Anyone who has tried to learn anything dense — calculus, distributed systems, macroeconomics, real auction theory — knows that none of those subjects are lists. They are graphs. Concepts depend on other concepts. Branches fork and merge. A single page references three things you may or may not have already encountered, and which order you saw them in changes whether the page lands or doesn't.

The cost of pretending a graph is a list is invisible from the outside. From the inside it looks like dropoff. Learners stop using the product not because they couldn't have understood the material, but because the product gave them no map of where they were. They couldn't see what they'd already earned. They couldn't see what was a prerequisite they'd skipped. They were at chapter 12, the chapter list said so, and they had no idea what chapter 12 was actually built on. The interface flattered the subject by simplifying it; the simplification killed the learning.

Two side-by-side sketches: on the left, a vertical list of items numbered 1 to 12, a downward arrow labeled 'next'. On the right, a directed graph of nodes with multiple incoming and outgoing edges, no obvious single sequence.
Left: every learning product. Right: every actual subject.

What if the curriculum is the graph

Orno started as a small bet. What if we made the dependency graph the primary surface of the product? Not a flowchart on the marketing site — the actual interactive thing you navigate. Each node is a lesson; each edge is a prerequisite; completed nodes glow; locked nodes grey out; the next-best node pulses. You don't choose lesson seventeen because it's lesson seventeen. You choose it because the graph just unlocked it.

Authoring a graph instead of a syllabus changes what curriculum design even means. You're not deciding what comes first. You're declaring what depends on what. The arrangement falls out of the dependency declarations automatically — we use Dagre to lay the graph out, so the author never moves a node. Adding a new lesson is a node and a few edges. Reorganizing the curriculum is a refactor of the edges. The shape that emerges is structural, not sequential.

On the learner side, this changes what progress means. Instead of "4 of 12 modules complete," we show "42% of the reachable graph unlocked" — and surface the specific downstream concepts that just became reachable. Progress is now a property of the topology you've earned, not the videos you've watched.

On the AI mentor side, this changes what the mentor knows. The mentor on our build doesn't just see the current lesson. It sees the chain of dependencies the learner has already unlocked, and the immediate downstream nodes still ahead. When the learner asks "why does this work," the mentor's answer is grounded in the prior nodes the learner has touched, not in an abstract "this is a calculus question" sense. The mentor knows where you are because the topology tells it where you are.

I want to say all of that worked the first time we shipped it. It didn't. It worked the second time, with three weeks of rework in between.

Layer 1: The beautiful graph nobody used

The first version of Orno's learner surface was the graph as the home page. You logged in, you saw the graph, and the lesson was something you opened by clicking a node. We rendered the graph with XYFlow, laid it out with Dagre, gave completed nodes a soft glow, gave locked nodes a desaturated tone. It looked, to be honest, exquisite. I have screenshots.

Learners didn't use it.

They would open Orno. They would look at the graph. They would close the tab. The graph was technically the curriculum, the curriculum was the whole point, and nobody wanted to navigate it. The closest analog I can find to the feeling our early users described is opening a Figma file with a beautifully organized component library and immediately feeling guilty that you don't know which component to grab. The map was the territory, and the territory was overwhelming.

We were proud of the graph and angry at the users and absolutely missing the point.

Layer 2: The breadcrumb that beat the graph

We watched a learner use the prototype for an hour. She was working through a path the graph had identified as her best next sequence — three lessons, in order, each building on the last. The graph was visible the entire time. She did not look at it once. She used the back and next arrows at the bottom of each lesson. She wanted to know what was immediately behind her and immediately in front of her. The whole structural picture was, for her, noise.

So we changed the surface. The graph demoted to a side panel. The lesson page led with a contextual breadcrumb — the last three nodes she had unlocked, the current node, the next three nodes the topology suggested. A single horizontal row of seven dots and labels. That's it.

Engagement went up immediately.

I'll spare you the engagement-up self-congratulation. The interesting part is what we learned about the topology being load-bearing in the back end even when it's invisible in the front end. The breadcrumb only worked because the system underneath it knew the dependency graph perfectly. The next-three suggestions were not the next chronological items — they were the topologically-correct next steps from where the learner was, computed live. The graph was running the show. The user just didn't have to see it.

The lesson here is not anti-graph. It is anti-vanity-surface. The graph was a backend primitive masquerading as a frontend primitive. Once we let it be a backend primitive, the frontend got dramatically better.

A horizontal breadcrumb showing seven dots labeled with lesson names: three behind (smaller, faded), the current one (highlighted), three ahead (smaller, slightly pulsing).
The graph runs underneath. The user sees the path through it.

The graph was a backend primitive masquerading as a frontend primitive. Once we let it be a backend primitive, the frontend got dramatically better.

On the rebuild

The Postgres-versus-graph-database question

When you tell engineers you're building a graph-native learning product, the first question is always "are you using Neo4j." I get it. It's the conditioned response. Graphs go in graph databases.

We're not using Neo4j. We're using Postgres.

The reasoning was operational and we'd make the same call again. Postgres is what our team operates well, what every backend engineer who joins the project already understands, and what runs cleanly on every PaaS we deploy to. The cognitive cost of adding a second database — Neo4j alongside Postgres, with its own replication story, its own backup story, its own client library — was a price we weren't ready to pay for the kinds of graph queries we actually do.

Now: those queries are not free in Postgres. The most common one — "every node that is now unlocked, given the set of completed nodes" — is a recursive CTE. The first version we wrote was a CTE inlined directly into a FastAPI route. It worked. It also became the single most-touched, most-feared piece of code in the backend within two weeks. Every engineer who looked at it asked the same question ("could we just use Neo4j") and the answer was always the same (no, but please do not touch this without telling me).

The fix was small and we should have shipped it on day one. We built a tiny typed query module — `unlocked_for(completed_set)`, `downstream_of(node_id)`, `dependencies_of(node_id)` — that wrapped the recursive CTEs and gave the rest of the codebase a clean Python surface. Every engineer can call those functions without ever touching SQL. The CTEs only need to be correct in one place. The graph-database question quietly went away.

The principle I'd extract is unfussy. If a relational query is unergonomic, the answer is usually a thin typed wrapper, not a different database. The cost of switching is paid every day forever. The cost of a wrapper is paid once.

The AI mentor that knows where you are

I want to talk about the mentor briefly because the interesting part is not the model. We use Gemini. The prompt is around four hundred tokens of system instructions, and most of what makes it work is the context we inject at runtime.

Specifically: every mentor call receives, as structured JSON, the current node, the most recent five unlocked prerequisites in topological order, and the two immediate downstream nodes. The mentor sees the same local topology the learner sees in the breadcrumb.

The result is that the mentor never has to ask "what are you studying" — it already knows. When a learner says "I don't get this," the mentor's first move is to reference the most recent prior concept that this lesson builds on. It is a shockingly small change versus a generic Gemini call. It is also the entire difference between an LLM mentor that feels like a search engine and one that feels like it has read your last three lessons.

There is a longer version of this lesson which is that AI products live or die on context engineering, not on prompt engineering. I keep waiting for that essay to feel ready to write. It is not this essay.

OpenAPI codegen as a discipline

Two small notes for engineers. We use FastAPI on the backend, Next.js on the frontend, and we generate the entire frontend API client from FastAPI's OpenAPI schema. The frontend has zero hand-written client code, zero handwritten request/response types. Breaking the contract is a build error, not a Tuesday-afternoon support ticket.

If you are building a Next + FastAPI product and you are not doing this, set aside a day. `openapi-typescript` for the types, `openapi-fetch` for the client, TanStack Query for the surface. The first time the backend renames a field and the frontend lights up red at compile time, you will mail me a thank-you note.

The second note is that this discipline shapes how the API is designed. When every endpoint is going to be consumed via a generated client, you stop writing endpoints that take loose dicts. Every endpoint becomes a typed contract. The codebase gets quieter. The number of "why is this field sometimes null" Slacks drops to roughly zero.

What broke

Three things. We over-invested in the graph rendering in month one; we should have built the breadcrumb first and let the graph emerge as a side panel. The recursive CTE was inlined into a FastAPI route for three weeks before we wrapped it; we should have started with the wrapper. And we underestimated how often a learner would want to switch between two parallel learning paths — siblings in the graph that don't depend on each other — and the navigation did not surface that cleanly until version three.

None of these are catastrophic. All of them are the kind of mistake that compounds quietly when you're building a product whose value proposition is its underlying structure. The structure has to show up everywhere, including in the parts you didn't think were about structure.

What I'd put in a brief today

If you are designing an internal training program, a certification path, a paid course, a technical bootcamp — anything where one concept depends on another in a way that isn't well-served by a chronological list — the question I would ask first is: what is the actual dependency graph of the subject?

Not the chapter list. The dependency graph. Sit with someone who has actually mastered the subject and ask them to draw it. They will draw a graph. They will draw a graph because they know it's a graph. The chapter list is a fiction the product layer imposes on it.

The follow-up question is whether the surface you ship makes the graph navigable. You don't have to show the graph itself. You do have to let learners feel where they are in it.

The curriculum is the graph. The product is the topology.

Orno is a Synara product, currently in invite-only deployment. We're happy to scope a graph-native learning surface for internal training, certification paths, or technical education programs. The pattern is portable; the discipline is the part that's hard to copy.