Inspiration

Plato's Meno opens with a paradox: how do you search for knowledge you don't yet have? If you already knew it you wouldn't need to look, and if you don't, you won't recognise it when you find it.

Every tutor runs into a version of this, and almost all of them start teaching in the wrong place, either assuming a prerequisite you're missing or explaining one you already hold. Ask an LLM to explain attention and it either assumes you know softmax, or spends three paragraphs explaining softmax you already know. It has no way to find out which. And because a chat thread forgets you the moment it ends, it makes exactly the same mistake tomorrow.

We wanted an agent that finds out where you actually are before it opens its mouth, and that remembers.

What it does

Give Meno a topic. A bare concept name, or a paper abstract you got stuck on. Then it:

  1. Investigates it with Gemini 3.7 and Google Search grounding, and extracts the atomic concepts involved along with the prerequisite edges between them: a DAG, not a reading list.
  2. Diagnoses you. It writes short diagnostic questions, grades your answers, and anything you demonstrate is marked known and dropped from the plan before any teaching starts.
  3. Previews the path, showing the whole journey upfront, so you can see what it thinks you need and take things off it.
  4. Teaches one concept at a time, generating each lesson only when you reach it. You can ask anything in your own words. You take the concept's one mastery check when you feel ready. Passing offers the move without making it, so you leave when you're satisfied rather than the moment you're graded.
  5. Adapts its own plan. "Break it down" makes it diagnose the missing prerequisite and splice a remedial concept into the path. An answer that also demonstrates the next concept makes it skip that one. The path reshapes live while you watch.
  6. Remembers. Everything you unlock lands in one knowledge graph that outlives every session. You can rename or delete nodes, and those edits are recorded and fed back to the agent, so it curates with you rather than around you. A later session on a related topic attaches to concepts you already have instead of teaching them twice.

Sessions behave like conversations: several can be in progress at once, each resumable exactly where you left it, and they all feed the one graph.

How we built it

One Next.js 16 app on a single Cloud Run service, in four deliberate layers.

Server interface: twelve route handlers, the entire surface the browser can reach. Each resolves the signed-in user before anything else. The browser holds no domain logic at all, so every move is a server round-trip and a reload resumes exactly where the session stood.

Domain core, pure and model-free: linearizing the prerequisite DAG into a path, deciding what comes next, and the two bounded adjustments the agent is allowed to make. Keeping this out of the prompts is what makes the behaviour testable without a model.

Agent layer: nine Genkit flows across three files, on Gemini 3.7 Flash via Vertex AI. investigate does grounded research, then structured extraction, then attach. diagnose writes the checks and grades them. lesson teaches, replies, writes and grades the mastery check, breaks a concept down, and writes the recap. Every extraction and grading call returns a Zod-typed schema.

Firestore: one graph per account at graphs/{uid}/, holding concepts, sessions, lessons, checks, and an append-only edit log.

Identity is structural rather than filtered: the Firebase uid is the graph id. Google sign-in happens in the browser only to obtain an ID token, which is traded once for an httpOnly session cookie, so every other fetch in the app is an ordinary same-origin call that knows nothing about auth. Every storage function takes the graph id as a required argument, so a forgotten one is a compile error rather than a silent read of someone else's graph, and the Firestore rules deny all direct access: only the Admin SDK, behind the route handlers, ever touches the data.

Data sources. Google Search grounding (the live web) at investigation time, and the learner's own answers, questions and graph edits as the other input. There is no pre-built corpus or content library; every path is generated per learner, per topic.

Delivery. Every push to main runs lint, typecheck and the full test suite, then builds a container, pushes it to Artifact Registry tagged by commit, deploys to Cloud Run, and smoke-checks the deployed URL. Authentication to Google Cloud is via Workload Identity Federation, so there are no stored keys anywhere in the pipeline.

Challenges we ran into

Grounding and structured output don't combine. Google Search grounding and JSON-schema output aren't reliable together on Gemini, so investigation is deliberately two calls: grounded research into prose notes, then structured extraction from those notes. Fighting this for a while and then accepting it produced a better design than the one we set out to build.

Trusting model output. The rule we settled on is that the model proposes and the domain core decides. Prerequisite keys pointing at concepts that don't exist, duplicate keys, self-references, and attach ids the graph doesn't hold are all dropped before anything is written. The model's job is to suggest structure; validating it is ours.

Letting the agent adapt without letting it thrash. An agent that can replan freely will replan constantly. We bounded it to exactly two moves, inserting a remedial concept or skipping the next one, and wrote down why as an architecture decision record. The skip rides only a passing answer: the path is a prerequisite claim, so knowing the next concept while failing this one means the path is wrong, not that the learner may move past it.

Concurrency, because sessions really are concurrent. Every state move is a single guarded transactional commit: all model calls land before the write, and the write is conditional on the state the caller read. A duplicate click or a stale tab is refused with a 409 rather than half-applied, and a failed generation leaves the session exactly where it stood.

Getting a model to write mathematics that actually renders. The lesson surface renders Markdown and LaTeX through KaTeX, and models cheerfully emit TeX that KaTeX can't parse: $$\begin{aligned} on one line, custom macros, unescaped dollar amounts read as maths. The fix was a shared voice block in the prompts specifying exactly how to set a displayed formula, restricted to commands KaTeX supports. When it works, a lesson can say what it means:

$$ \mathrm{Attention}(Q, K, V) = \mathrm{softmax}!\left(\frac{QK^{\top}}{\sqrt{d_k}}\right)V $$

Accomplishments that we're proud of

The whole test suite runs with no cloud credentials and no network. A single seam selects the model for every flow, and setting it to scripted swaps in a deterministic fake. The 109 tests across 12 files then run black-box against the real route handlers, the same way the browser calls them, on the local Firestore emulator. No GCP project, no keys, no spend, and it runs identically in CI.

Interface work costs zero model calls. A seed script fills the emulator with a graph covering every UI state: two sessions, a remedial detour, both kinds of skip, lessons with mathematics, a check and an edit. It refuses to run unless it can reach an emulator, so it can never touch live data.

The decisions are written down. Five architecture decision records cover the choices that were expensive to reach, so the reasoning outlives the memory of making it.

What we learned

The hard part of a teaching agent isn't generating explanations. Models are already good at that, and it is the part that looks like the product.

The hard part is the state machine underneath: what counts as knowing something, who is allowed to decide you've moved on, what happens when the plan turns out to be wrong, and what survives when the conversation ends. Every one of those is a design decision that a prompt cannot make for you, and every one of them we initially tried to push into the prompt and had to pull back out.

The clearest version of this is the mastery check. It's written against the exposition it tests and never sees the conversation that follows, so the questions you happened to ask can't reshape the bar you're held to. That's four lines of plumbing and it's the reason the whole thing can be trusted.

What's next for Meno

  • Merging nodes you decide are the same. The agent already reconciles concepts across sessions when it recognises one at investigation time, so a second session on a related topic attaches to what you have rather than duplicating it. What's missing is the manual case: pointing at two nodes in your graph and saying these are one idea.
  • Manual edges, so you can draw a prerequisite the agent didn't find.
  • Diagrams in lessons: a fenced block the tutor can draw with, so a concept that wants a picture gets one. Prototyped, not yet good enough to ship.
  • An analysis layer over the edit log, so the agent learns your curation style and not just your knowledge: what you consider one idea rather than two, and what you'd rather it never split again.

Built With

Share this project:

Updates

Submission history