From f587211640970539d4df37a8301ef3ba3340d74c Mon Sep 17 00:00:00 2001
From: Adam Stankiewicz
Date: Sat, 29 Aug 2026 15:47:58 -0400
Subject: [PATCH 01/14] docs: the OSS landing page and adopter docs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
One branch carrying the project's public front door:
- docs/index.html — the a2learn landing in the warm-minimal register
(Geist + Source Serif, warm paper, verification green as the only
accent): agent-session hero, interactive loop demo, manifesto, lineage
section (H5P, xAPI/Caliper, LTI, open platforms), named activity types.
- docs/README.md — the adopter docs index and the two ground rules.
- docs/quickstart.md — connector setup, self-hosting, and the full
configuration reference.
- docs/registry.md — the centerpiece: the widget registry API. The
two-registry design and why, WidgetCatalogEntry and WidgetGenerator
contracts, who reads the registry, the add-a-kind recipe, and the
hand-maintained-union limitation, stated plainly.
- docs/mcp-tools.md — show_widget and score_draft as shipped, the widget
shell, and the planned v0.1 surface (find_activity, MCP_ACCESS_TOKEN,
/api/v0) labeled as planned.
- docs/evidence.md — the interaction event contract, its honesty rules,
what reads it, the student-identity boundary, and the universal
WidgetResult direction.
- docs/architecture.md — the four seams and where student data cannot go.
Co-Authored-By: Claude Fable 5
---
docs/README.md | 22 ++
docs/architecture.md | 87 ++++++++
docs/evidence.md | 81 +++++++
docs/index.html | 492 +++++++++++++++++++++++++++++++++++++++++++
docs/mcp-tools.md | 54 +++++
docs/quickstart.md | 62 ++++++
docs/registry.md | 125 +++++++++++
7 files changed, 923 insertions(+)
create mode 100644 docs/README.md
create mode 100644 docs/architecture.md
create mode 100644 docs/evidence.md
create mode 100644 docs/index.html
create mode 100644 docs/mcp-tools.md
create mode 100644 docs/quickstart.md
create mode 100644 docs/registry.md
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 00000000..5f95a72f
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,22 @@
+# a2learn docs
+
+Documentation for adopters and extenders of the activity server. The
+[landing page](./index.html) carries the why; these pages carry the how.
+
+| Page | Answers |
+|---|---|
+| [Quickstart](./quickstart.md) | Point an MCP host at it, or run your own instance, in minutes |
+| [The widget registry](./registry.md) | The core extension API: catalog entries, generators, and how a new activity kind ships |
+| [MCP tools](./mcp-tools.md) | The wire surface an agent calls: `show_widget`, `score_draft`, and what's coming |
+| [The evidence contract](./evidence.md) | What flows back when a student works, and what reads it |
+| [Architecture](./architecture.md) | The seams — registry, standards source, storage, protocol facades — and where student data does and doesn't go |
+
+The a2learn document format draft (surface + manifest, conformance classes)
+lives separately under `docs/a2learn/` once its branch merges.
+
+Two ground rules that shape everything here:
+
+1. **The model never writes code that runs.** Generated output fills Zod
+ schemas that render against components humans wrote and reviewed.
+2. **Student identity never reaches this server from an integration.**
+ Anonymous ids only on the wire; anything richer lives in the host.
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 00000000..899b195e
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,87 @@
+# Architecture
+
+One Next.js process, four deliberate seams. The app you see at `/` is the
+reference client for the server underneath — adopt the seams, not the app.
+
+```
+agent / MCP host product backend teachers & students
+ │ │ │
+ ▼ ▼ ▼
+ /api/mcp ◄──────────► /api/widget (REST) reference app (/)
+ └──────────────┬─────────────┘
+ ▼
+ service core (buildWidget, pathway pipeline)
+ │
+ ┌─────────────────┼──────────────────┐
+ ▼ ▼ ▼
+widget registry standards source storage adapter
+(catalog + (verify / (memory | supabase)
+ generators) decompose /
+ progression)
+```
+
+## Seam 1 — the widget registry
+
+The core extension API: catalog entries (schema + component + pedagogy
+metadata) and server-only generators, covered in depth in
+[registry.md](./registry.md).
+
+## Seam 2 — the standards source
+
+Verification is pluggable. A source implements one interface
+([`src/lib/standards/types.ts`](../src/lib/standards/types.ts)):
+
+```ts
+interface StandardsSource {
+ id: string;
+ label: string;
+ proposalPromptFragment: string; // notation and examples for the propose step
+ verify(code: string, jurisdiction?: string): Promise;
+ decompose(standard: StandardRef): Promise;
+ progression(standard: StandardRef, direction: 'backward' | 'forward'): Promise;
+}
+```
+
+`verify` returning `null` is the honest answer "that code doesn't exist" —
+the pipeline surfaces it instead of building on a hallucination. Learning
+Commons is the default source; `src/lib/standards/example.ts` is a built-in,
+keyless source that exists to be copied for a state framework, IB, or a
+district curriculum map. Select with `STANDARDS_SOURCE`. Nothing downstream
+assumes US notation or English.
+
+## Seam 3 — the storage adapter
+
+All persistence goes through one interface
+([`src/lib/storage/types.ts`](../src/lib/storage/types.ts)): sessions,
+interaction events, assignments, profiles. Two implementations ship —
+`memory` (the default; nothing survives a restart, perfect for trying the
+server) and `supabase` (a local stack via `pnpm db:start`). Select with
+`STORAGE_ADAPTER`. A different backend implements the interface; report
+aggregation logic is shared, not duplicated per adapter.
+
+## Seam 4 — the protocol facades
+
+MCP (`/api/mcp`) and REST (`/api/widget`, growing into `/api/v0`) are thin
+facades over the same service core. The rule is structural: logic never
+lives in a facade, so a new transport is a new file, not a fork of the
+business logic. [mcp-tools.md](./mcp-tools.md) documents the surface.
+
+## Where student data goes — and doesn't
+
+- **Model calls** (generation, scoring) run under the deployment's own keys.
+ Student work sent for scoring goes to the LLM provider *you* configured,
+ from *your* instance — never to a third party of ours.
+- **On the wire from integrations**, students exist only as anonymous ids.
+ There is no integration path that sends student identity to this server.
+- **The reference app's optional roster** (names a teacher enters) and all
+ interaction evidence live in the deployment's own storage adapter. Running
+ `memory`, they evaporate on restart; running your own Supabase, they're in
+ your database, under your data-processing terms.
+- **Educator identity** is the one identity that may eventually reach the
+ server edge (per-educator OAuth for hosted multi-user instances is
+ roadmapped, via an identity adapter). Student identity never does — hosts
+ own students.
+
+That boundary is the architecture expressing a value, the same way
+offline-first is for Kolibri: a district evaluating this doesn't have to
+trust a promise, just read where the data can't go.
diff --git a/docs/evidence.md b/docs/evidence.md
new file mode 100644
index 00000000..9f5678a6
--- /dev/null
+++ b/docs/evidence.md
@@ -0,0 +1,81 @@
+# The evidence contract
+
+The reason this project exists: when a student works through an activity,
+what happened flows back in a structured shape a program can act on — the
+agent that assigned it, the teacher's report, the remediation loop. Chat
+alone can't see whether teaching happened; this contract is how the loop
+closes.
+
+## The interaction event
+
+Every widget that assesses emits events through the shared telemetry hook
+(`useWidgetTelemetry`). The persisted shape
+([`src/lib/storage/types.ts`](../src/lib/storage/types.ts)):
+
+```ts
+type InteractionEvent = {
+ sessionId: string;
+ studentId: string; // anonymous id — see the boundary note below
+ widgetKind: string;
+ eventType: string;
+ standardCode: string | null;
+ learningComponentId: string | null;
+ elapsedMs: number;
+ correct: boolean | null; // null = this event carries no verdict
+ payload: Record; // per-kind detail; stepIndex rides here
+};
+```
+
+`eventType` is a string by design (adapters shouldn't need a migration for a
+new event), with these conventional values:
+
+| Event | When | `correct` |
+|---|---|---|
+| `widget_shown` | the activity mounts | `null` |
+| `answer_checked` | a checkable attempt is judged | `true` / `false` |
+| `hint_requested` | the student asks for help | `null` |
+| `widget_completed` | the activity's done moment | the final verdict, or `null` for non-assessing kinds |
+| `hesitation` | a long pause on an open question | `null` |
+
+Two rules keep the data honest:
+
+- **A verdict is a claim.** In-progress work (a draft being written, an
+ unfinished sort) reports `correct: null` — being mid-task is not being
+ wrong. Only kinds whose catalog entry says `assesses: true` emit verdicts
+ at all; see [the registry](./registry.md).
+- **`stepIndex` rides inside `payload`.** In a pathway, `widget_completed`
+ carries which step it was, which is what makes per-step reporting possible.
+
+## What reads it
+
+- **The session report** aggregates per-student accuracy, attempts, hints,
+ median time, and a per-step evidence strip (first try / needed attempts /
+ still wrong / not reached — the strip never guesses about events recorded
+ before `stepIndex` existed).
+- **The remediation loop** watches `widget_completed` verdicts; a wrong one
+ can inject a re-teach step after the student's current position. Injected
+ steps are announced, and cost the student nothing.
+- **The student profile** weights later pathway generation toward what a
+ student found hard.
+- **A hosting agent** — in a chat host, finish-line results are also
+ reported into the conversation in prose, so the assistant that assigned
+ the activity can adapt.
+
+## The boundary
+
+`studentId` is an anonymous identifier minted by the deployment. No
+integration path sends a student's identity to this server; when the
+reference app's optional roster maps anonymous ids to names, that mapping
+lives in **your** storage adapter, inside **your** deployment, and never
+leaves it. Educator identity may eventually reach the server edge (OAuth for
+hosted instances is on the roadmap); student identity never does.
+
+## Where the contract is going
+
+v0.1's headline work is a **universal `WidgetResult`** — one typed result
+shape across all sixteen kinds (status, verdict, attempts, and a per-kind
+`detail` naming what the struggle was), so an SDK consumer writes one
+`onResult` handler, not sixteen. The event stream above stays; the result is
+its per-activity summary. An xAPI statement export for LRS pipelines is a
+mapping over this contract, not a rewrite, and sits on the pull-gated
+roadmap.
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 00000000..34c10126
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,492 @@
+
+
+
+
+
+a2learn — the open activity server for teaching agents
+
+
+
+
+
+
+
AI tutors can talk. This makes them able to teach.
+
An open-source activity server and SDK that gives teaching agents an action space:
+ real interactive practice, verified against real learning standards, with evidence of what the
+ student did flowing back to the agent.
+
+
self-hostable
+
stores nothing about students
+
MCP + REST
+
16 activity types
+
+
+
+
+ One turn of the loop
+
› find_activity topic: "fractions on a number line" grade: 3
+ ✓ CCSS.MATH.3.NF.A.2 · verified against the graph
+ 4 options · number-line plot, drag-sort, flashcards, quick check
+
+› show_widget number-line-plot
+ student works · 1 attempt
+ ← { correct: true, attempts: 1, struggledWith: null }
+
+› agent adapts · next: comparing fractions
+
+
+
+
Or take a turn yourself (simulated — the real thing runs on the server)
+
+
Which point shows 1/3 on the number line?
+
+ 0ABC1
+
+
+
+
+
+
+
+
+
What flows back to the agent
+
+
+
+
+
Unlocked by your result — which is bigger?
+
+
+
+
+
+
+
+
+
+
+
+
Why this exists
+
+
Thousands of teams are building AI tutors, and nearly all of them have built the same half of one.
+ Their tutors explain, encourage, and answer questions. Then the student says "got it," and the tutor has
+ no way to find out whether that's true.
+
The research on this is blunt. In a 2025 randomized trial published in PNAS, students given unguided
+ ChatGPT for math practice solved 48% more practice problems and then scored 17% worse on the exam,
+ while reporting they felt more prepared. Talk alone doesn't just fail to teach. It can convince
+ everyone in the room that teaching happened. Meanwhile the same literature shows what works: AI tutoring
+ built around real practice reached an effect size near 1.0 in a Harvard randomized trial, and formative
+ feedback triples its effect when performance data actually drives the next decision.
+
So the missing piece isn't a better chat prompt. It's the loop: assign something worth doing, watch
+ it get done, and act on what happened. Every tutor needs that loop. No tutor company differentiates on
+ it. That's the definition of infrastructure that should be open, built once, and shared by everyone,
+ including companies that compete with each other. This project is that layer.
+
+
+
What we believe
+
+
+
Students learn by doing.
+
Retrieval practice, worked examples, and mastery loops carry some of the largest effect sizes in
+ education research. A tutor's job is to cause doing, and then respond to it.
+
+
+
Practice must be worth trusting.
+
Every activity here is checked against a real standards graph before it's built. A code that
+ doesn't exist gets an error naming the problem. When nothing verifies, the activity says so plainly
+ instead of dressing up. A teacher should be able to file what comes out of this without
+ fact-checking the citation.
+
+
+
The tutor should see what happened.
+
Every activity reports a structured result: correct or not, attempts, where the struggle was.
+ Correctness alone isn't enough; students reach right answers through wrong reasoning, and a tutor
+ that only sees a green checkmark misses it. Evidence is the product, and it also happens to be what
+ real efficacy research is made of.
+
+
+
Teachers approve what students see.
+
Generated content gets previewed by an adult, and assignment freezes the exact reviewed instance.
+ Students never meet an unreviewed roll of the dice. The research is unambiguous that generated
+ content needs expert review; we made the review step structural instead of optional.
+
+
+
Student data stays home.
+
The server stores nothing about students. Scoring calls run under your keys, inside your
+ deployment. In the year of COPPA opt-in defaults and consent lawsuits, this is not a feature flag.
+ It's the architecture.
+
+
+
The model never writes code that runs.
+
Generated output fills schemas that render against components humans wrote and reviewed. Activity
+ instances are data, open to share. New activity types are code, and travel a curated, signed path.
+ That line is the security model, and we will not blur it.
+
+
+
Open beats owned for this layer.
+
An activity corpus done well takes a year of rare, unglamorous work: interaction design, prompt
+ tuning past real failure modes, keyboard accessibility, pedagogy. Every tutor needs it and none is
+ differentiated by it. Building it once, in the open, under Apache-2.0, is the only version of this
+ that serves schools instead of a cap table.
+
+
+
+
What it is
+
+
An activity server. An MCP server with two tools: find_activity
+ answers a learning need with ranked, standards-verified options; show_widget builds one
+ and renders it in Claude or any MCP host. The registry is generative: it lists capabilities that
+ manufacture activities on demand, not a shelf of files. Sixteen today:
+
+
+
flashcards
swipe cards
drag-to-order
sort into groups
+
timeline builder
crossword
fraction area model
reading card
+
step-by-step reveal
narrated walkthrough
spot the mistake
draw the curve
+
defend the claim
live-scored draft
writing workshop
debate an AI
+
+
+
An SDK. For products with their own tutor:
+
+
const options = await findActivities({ topic: "fractions on a number line", grade: 3 });
+const activity = await createActivity(options[0]);
+
+<ActivityFrame activity={activity} onResult={(r) => agent.observe(r)} />
+
+
A document format. Activities are data: a surface (a profile of Google's A2UI,
+ flat components and shared state wired by plain references) plus a manifest (verified standards,
+ pedagogy, accessibility and language declarations, provenance). Drafted openly with conformance
+ classes and a changelog. We call it a format, not a specification; it earns the second word when a
+ second implementation exists.
+
A reference app. A full teacher-and-student application built on the same
+ registry: pathway builder, student walkthrough, rosters, reports. It's how we test everything and
+ what you can build; it isn't the thing you have to adopt.
+
+
+
How the pieces work
+
+
The registry. Each activity type is a React component plus a generator prompt,
+ registered with its pedagogy metadata: whether completing it measures correctness, how it completes,
+ which standards it fits. Adding a type is one file and one import. The planner, previews, and
+ discovery all read the same entry.
+
The standards layer. A source implements three functions: does this code exist,
+ what components make it up, what comes before it. Learning Commons is the default. A built-in,
+ keyless example source exists to be copied for a state framework, IB, or a district's own curriculum
+ map. Nothing downstream assumes US notation, or English.
+
The evidence contract. One result shape from interaction to data model to agent
+ to report. Adopters get research-grade interaction data from day one, which matters in a market where
+ only 12% of AI education products have published efficacy evidence.
+
+
+
Standing on prior art
+
+
Open-source education did not start here, and the projects that came before shape what this one
+ is. The open LMS ecosystem serves hundreds of millions of learners; what it lacks is a practice layer
+ an agent can drive. If a shorthand helps: H5P for the agent era.
+
+
+
+
H5P interactive content types
+
Proved that a shared, reusable library of interaction types beats every platform building its
+ own. The difference here: H5P's registry is a shelf of authored files; ours lists generative
+ capabilities, adds standards verification, and reports evidence to an agent instead of a gradebook
+ alone.
+
+
+
xAPI & Caliper learning analytics
+
Established that interaction evidence deserves a standard shape. Our result contract is narrower
+ on purpose — typed per activity, consumed in-loop by the agent that assigned it. An xAPI export for
+ LRS pipelines is a mapping, not a rewrite, and belongs on the roadmap.
+
+
+
LTI tool embedding
+
The door every LMS already has. MCP is this project's first transport because agents are the
+ first consumer; LTI Advantage lands at the gate where organizations embed activities in their own
+ platforms, so results can reach real gradebooks.
+
+
+
Open edX, Moodle, Kolibri open platforms
+
Demonstrated the governance path this project intends: real security policy, neutral stewardship
+ when more than one organization depends on it, and architecture that carries values — Kolibri made
+ offline-first an equity position; we make store-nothing-about-students one.
+
+
+
+
Get started
+
+
Try it
Add the server to Claude as a custom connector and ask for "a quick
+ activity on the water cycle for a 5th grader." Zero code.
+
Run it
One Vercel deploy or pnpm dev. One required secret (an LLM key).
+ No database. Set MCP_ACCESS_TOKEN on public instances, or anyone with your URL spends
+ your money.
+
Build on it
The SDK above, for tutors that need an action space and eyes on the
+ results.
+
Extend it
Add an activity type (one file), plug in your standards graph (copy the
+ example source), swap storage. If you need to fork to extend, we consider that a bug.
Education marketing has an honesty problem, so here is what you will not read from us. We won't
+ claim AI tutors outperform teachers; the evidence is conditional on design, and null results exist.
+ We won't claim generated content is classroom-ready without review; it isn't, and our own workflow
+ assumes it isn't. We won't cite the folklore formative-assessment effect sizes; real effects range
+ widely with implementation. And we won't call this a marketplace or a standard while there's one
+ publisher and one implementation. Ambition is cheap. Labels are earned.
+
+
+
Where it's going
+
The roadmap advances on pull, not dates. When an organization embeds activities in its own product,
+ we build the saved-activity shelf, the embeddable player, and identity forwarding, so results can land
+ in real gradebooks. When someone wants native rendering in their own design system, the headless widget
+ hooks and packages ship. When a second independent implementation exists, the format goes 1.0 with
+ schemas and conformance fixtures, the project moves to a neutral organization, and federated registries
+ with independent content review become the long-term shape: many servers, shared format, trust attached
+ by people qualified to attach it.
+
+
+
Status: v0.1, young, honest about it. One primary maintainer, a growing test
+ suite, security policy that tells you plainly what not to do yet. The most useful contributions right
+ now: a new activity type, a second standards source, or pointing your tutor at it and telling us what
+ broke. Born at the CodeAI education hackathon, built by people from several education companies, and
+ licensed Apache-2.0 so it stays consumable by all of them, including the ones that compete.
+
+
+
Format draft, conformance classes, and security/privacy considerations live in
+ docs/a2learn/. Governance, code of conduct, and contribution guides ship with the repo.
+ Research citations: Bastani et al., PNAS 2025; Kestin et al., Nature Scientific Reports 2025; and the
+ formative-assessment and retrieval-practice meta-analytic literature.
+
+
+
+
diff --git a/docs/mcp-tools.md b/docs/mcp-tools.md
new file mode 100644
index 00000000..ff730ccc
--- /dev/null
+++ b/docs/mcp-tools.md
@@ -0,0 +1,54 @@
+# MCP tools
+
+The server's wire surface for agents, served at `/api/mcp` (server name
+`interactive-learning-widgets`, MCP Apps capable). A REST mirror of the same
+service core exists at `/api/widget` for backends that don't want an MCP
+handshake; logic never lives in either facade.
+
+## `show_widget`
+
+Builds one standards-verified activity and renders it in the host.
+
+| Input | Required | Meaning |
+|---|---|---|
+| `topic` | no* | What the activity is about, in plain words — "comparing fractions". Enough on its own. |
+| `standardCode` | no* | A Common Core or NGSS code, if the caller already knows which one it wants. |
+| `gradeHint` | no | e.g. `"8th grade"` — narrows proposal and generation. |
+| `kind` | no | One of the registry's kinds. Leave it out and the best interaction for the standard is chosen via `coverageRule` + planner metadata. |
+
+*At least one of `topic` / `standardCode` in practice — a bare call has
+nothing to build from.
+
+The flow behind it: propose (or accept) a standard code → **verify it against
+the standards graph** (a hallucinated code gets an error naming the problem,
+not a quietly wrong activity) → pick a kind → run that kind's
+[generator](./registry.md) → validate the spec → return the rendered widget
+via the MCP Apps shell. When no code survives verification, the result says
+so plainly and renders as an exploration activity.
+
+## `score_draft`
+
+Scores a student's written response for the draft-meter kind — the model call
+runs server-side, under the instance's keys, so a chat host never needs its
+own scoring path.
+
+## The widget shell
+
+Widgets render in hosts through a prebuilt HTML shell shipped as the
+`learning-widget` MCP resource. It's compiled from the same component
+registry the app uses (`pnpm mcp:build`). Rebuild it whenever registry
+components change — a stale committed shell means new kinds silently fail in
+MCP hosts (a CI rebuild-and-diff guard for exactly this ships with the OSS
+hardening branch set).
+
+## Planned surface (v0.1)
+
+- **`find_activity`** — answers a learning need with ranked,
+ standards-verified listings derived from registry metadata; each listing
+ carries the exact `show_widget` arguments that build it. Implemented on
+ `feat/find-activity-mvp`, merging as part of v0.1.
+- **`MCP_ACCESS_TOKEN`** — bearer auth for public instances, with a rate
+ cap.
+- **`/api/v0/activities`** — a small, OpenAPI-documented REST facade
+ (find / create / fetch frozen instance) with the same shapes as the MCP
+ tools.
diff --git a/docs/quickstart.md b/docs/quickstart.md
new file mode 100644
index 00000000..7c66abe9
--- /dev/null
+++ b/docs/quickstart.md
@@ -0,0 +1,62 @@
+# Quickstart
+
+Two paths in: point an MCP host at a running instance, or run your own.
+
+## Point an agent at it
+
+Any MCP host that renders MCP Apps (Claude does) can use the server with zero
+code. Add the instance as a custom connector:
+
+```json
+{ "mcpServers": { "a2learn": { "url": "https://your-instance.example/api/mcp" } } }
+```
+
+Then ask for practice in plain words — "a quick activity on the water cycle
+for a 5th grader." The agent calls `show_widget`, the standard is verified
+against the graph, and the activity renders inline. See
+[MCP tools](./mcp-tools.md) for the full tool surface.
+
+## Run your own instance
+
+```bash
+pnpm install
+pnpm dev # http://localhost:3000
+```
+
+One secret is required: an LLM key. Everything else has a working default —
+no database, no accounts.
+
+```bash
+# .env.local — minimal
+ANTHROPIC_API_KEY=sk-ant-...
+```
+
+Deploy is one Vercel deploy of the repo with the same env vars.
+
+> **Public instances:** anyone who has your URL can spend your LLM budget.
+> Keep instances private until token auth (`MCP_ACCESS_TOKEN`, planned for
+> v0.1) lands, or put your own gateway in front.
+
+## Configuration reference
+
+| Variable | Default | What it does |
+|---|---|---|
+| `LLM_PROVIDER` | `anthropic` | Which provider builds activities: `anthropic`, `openai`, `bedrock`, or `openrouter` |
+| `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `OPENROUTER_API_KEY` | — | The key for the chosen provider |
+| `AWS_REGION`, `AWS_BEARER_TOKEN_BEDROCK` | `us-west-2`, — | Bedrock configuration when `LLM_PROVIDER=bedrock` |
+| `OPENAI_FALLBACK_API_KEY`, `OPENAI_FALLBACK_MODEL_ID` | —, `gpt-4o-mini` | Optional second provider used when the primary fails |
+| `STANDARDS_SOURCE` | `learning-commons` | Which standards graph verifies codes; `example` is the built-in keyless source |
+| `LEARNING_COMMONS_API_KEY`, `LEARNING_COMMONS_MCP_URL` | — | Credentials for the default standards source |
+| `STORAGE_ADAPTER` | `memory` | `memory` (nothing persists across restarts) or `supabase` |
+| `NEXT_PUBLIC_SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY` | — | Only when `STORAGE_ADAPTER=supabase`; `pnpm db:start` runs a local stack |
+| `SEED_DEMO_ROSTER` | off | Seeds demo students in the reference app |
+
+Model calls run under **your** keys, inside **your** deployment — scoring a
+student's work never leaves the instance you control.
+
+## What you just ran
+
+The same process serves three surfaces: the MCP endpoint (`/api/mcp`), a REST
+mirror for direct calls (`/api/widget`), and the reference teacher/student app
+at `/`. The app is the demo of the server, not the product you must adopt —
+[Architecture](./architecture.md) shows where the seams are.
diff --git a/docs/registry.md b/docs/registry.md
new file mode 100644
index 00000000..adfe829a
--- /dev/null
+++ b/docs/registry.md
@@ -0,0 +1,125 @@
+# The widget registry
+
+The registry is the project's core extension API. Every activity the server
+can produce — for the MCP tools, the pathway planner, the teacher previews,
+and the student walkthrough — comes from one registry of **capabilities that
+manufacture activities on demand**, not a shelf of authored files.
+
+Everything in this page is defined in
+[`src/lib/widgets/types.ts`](../src/lib/widgets/types.ts).
+
+## Two registries, deliberately
+
+Each activity kind registers into two maps, from two different files:
+
+| Registry | Entry type | Runs | Registered by |
+|---|---|---|---|
+| Catalog | `WidgetCatalogEntry` | client + server | `src/lib/widgets/definitions/.ts` |
+| Generators | `WidgetGenerator` | server only | `src/lib/widgets/definitions/.generate.ts` |
+
+The split is not stylistic. A single definition bundling the React component
+and the model call would drag the AI SDK into the browser bundle — static
+import graphs don't care that `generate` is never *called* client-side; the
+module defining it still gets pulled in. So the client-facing renderer
+(`components/widgets/registry.tsx`) imports only the catalog, and only
+`pathway/generate.ts` imports the generators.
+
+Registration is a side effect of importing the definition module. Two barrel
+files own the imports:
+
+- `src/lib/widgets/builtins.ts` — catalog entries (client-safe)
+- `src/lib/widgets/builtins.generate.ts` — generators (server-only)
+
+## `WidgetCatalogEntry`
+
+```ts
+interface WidgetCatalogEntry {
+ kind: WidgetKind;
+ schema: ZodType;
+ component: ComponentType<{ spec: Spec; onComplete?: (...args: any[]) => void }>;
+ plannerDescription: string;
+ assesses: boolean;
+ coverageRule?: (standard: StandardRef) => boolean;
+}
+```
+
+- **`schema`** — the Zod schema the model's output must validate against.
+ This is the security model in miniature: the model fills a schema; it never
+ emits code. `.describe()` calls on fields double as prompt text.
+- **`component`** — the human-written renderer, registered as a lazy import
+ so a kind's code loads only when it renders. `onComplete` signatures
+ genuinely differ per kind (payload-free, `(correct: boolean)`,
+ `(results: CardResult[])`), which is why the prop is loosely typed here and
+ strictly typed inside each definition module.
+- **`plannerDescription`** — one sentence of prose fed to the pathway
+ planner so it knows when to reach for this kind.
+- **`assesses`** — whether finishing this activity records if the student
+ was actually right. Required, not defaulted: a "check" step built on a
+ non-assessing kind would report mastery nothing measured. This flag is what
+ the [evidence contract](./evidence.md) keys on.
+- **`coverageRule`** — an optional predicate narrowing which standards the
+ kind fits (a fraction area model only fits fraction standards). Omitted
+ means it fits everything.
+
+## `WidgetGenerator`
+
+```ts
+interface WidgetGenerator {
+ kind: WidgetKind;
+ generate(ctx: WidgetGenerationContext): Promise<{ widget: WidgetSpec | null; note: string | null }>;
+}
+```
+
+`WidgetGenerationContext` carries the verified standard (`anchor`), the plan,
+the current step, and a prebuilt prompt fragment with the standard's
+decomposition, outcomes, and misconceptions. A generator does the model call
+and normalization in one step:
+
+- returning `widget: null` means "this kind couldn't produce something
+ usable here" — the orchestrator falls back to `fallbackWidgetKind()`
+ (swiper-flashcard by default, settable via `configureFallback`), and the
+ fallback's spec is honestly a *different kind's* spec;
+- `note` carries any graceful-degradation message shown to the teacher.
+
+## Who reads the registry
+
+One entry serves every consumer — that's the point:
+
+- **The planner** joins every `plannerDescription` + `coverageRule` into its
+ widget guidance when composing a pathway.
+- **`buildWidget`** (behind both `/api/widget` and the MCP `show_widget`
+ tool) resolves a kind, runs its generator against a verified standard, and
+ validates the result against `schema`.
+- **The renderer** (`components/widgets/registry.tsx`) looks up `component`
+ by `kind` — in the walkthrough, teacher previews, and the MCP widget shell.
+- **The walkthrough** derives completion semantics: kinds with their own
+ continue button advance themselves; kinds with no unambiguous done moment
+ get an always-enabled external button with the reason stated.
+- **Discovery** (`find_activity`, pre-merge) ranks listings straight from
+ catalog metadata — the registry *is* the inventory.
+
+## Adding a kind
+
+The complete recipe with code samples is in
+[CONTRIBUTING.md](../CONTRIBUTING.md#adding-a-widget); the shape of it:
+
+1. **Spec** — a Zod object in `src/lib/pathway/schema.ts`, added to the
+ `widgetSpec` union and `widgetKind` enum.
+2. **Component** — `src/components/widgets/YourWidget.tsx`, props
+ `{ spec, onComplete? }`, emitting `answer_checked` / `widget_completed`
+ telemetry if the kind assesses.
+3. **Catalog entry** — `src/lib/widgets/definitions/your-widget.ts` calling
+ `registerWidgetCatalog`, plus its import line in `builtins.ts`.
+4. **Generator** — `definitions/your-widget.generate.ts` calling
+ `registerWidgetGenerator`, plus its import line in `builtins.generate.ts`.
+
+Nothing in the pipeline, planner, previews, or MCP surface needs to learn the
+new kind exists.
+
+## Known limitation
+
+`widgetSpec` and `widgetKind` in `pathway/schema.ts` are still
+hand-maintained alongside the registry. Deriving them from the registry would
+close a circular import between `schema.ts` and the registry module; until
+that's untangled, adding a kind touches those two unions plus the four files
+above.
From 1875e59463ac8f87ebf37ce976630f77b826d785 Mon Sep 17 00:00:00 2001
From: Adam Stankiewicz
Date: Sat, 29 Aug 2026 15:55:32 -0400
Subject: [PATCH 02/14] feat: serve the docs from the app
/docs renders the markdown under docs/ through a whitelist of slugs (no
filesystem walk), with GitHub-relative links rewritten to their app
routes or GitHub blob URLs. The static landing ships as /open.html.
Pages are prerendered; unknown slugs 404.
Co-Authored-By: Claude Fable 5
---
public/open.html | 492 ++++++++++++++++++++++++++++++
src/app/docs/[[...slug]]/page.tsx | 178 +++++++++++
2 files changed, 670 insertions(+)
create mode 100644 public/open.html
create mode 100644 src/app/docs/[[...slug]]/page.tsx
diff --git a/public/open.html b/public/open.html
new file mode 100644
index 00000000..34c10126
--- /dev/null
+++ b/public/open.html
@@ -0,0 +1,492 @@
+
+
+
+
+
+a2learn — the open activity server for teaching agents
+
+
+
+
+
+
+
AI tutors can talk. This makes them able to teach.
+
An open-source activity server and SDK that gives teaching agents an action space:
+ real interactive practice, verified against real learning standards, with evidence of what the
+ student did flowing back to the agent.
+
+
self-hostable
+
stores nothing about students
+
MCP + REST
+
16 activity types
+
+
+
+
+ One turn of the loop
+
› find_activity topic: "fractions on a number line" grade: 3
+ ✓ CCSS.MATH.3.NF.A.2 · verified against the graph
+ 4 options · number-line plot, drag-sort, flashcards, quick check
+
+› show_widget number-line-plot
+ student works · 1 attempt
+ ← { correct: true, attempts: 1, struggledWith: null }
+
+› agent adapts · next: comparing fractions
+
+
+
+
Or take a turn yourself (simulated — the real thing runs on the server)
+
+
Which point shows 1/3 on the number line?
+
+ 0ABC1
+
+
+
+
+
+
+
+
+
What flows back to the agent
+
+
+
+
+
Unlocked by your result — which is bigger?
+
+
+
+
+
+
+
+
+
+
+
+
Why this exists
+
+
Thousands of teams are building AI tutors, and nearly all of them have built the same half of one.
+ Their tutors explain, encourage, and answer questions. Then the student says "got it," and the tutor has
+ no way to find out whether that's true.
+
The research on this is blunt. In a 2025 randomized trial published in PNAS, students given unguided
+ ChatGPT for math practice solved 48% more practice problems and then scored 17% worse on the exam,
+ while reporting they felt more prepared. Talk alone doesn't just fail to teach. It can convince
+ everyone in the room that teaching happened. Meanwhile the same literature shows what works: AI tutoring
+ built around real practice reached an effect size near 1.0 in a Harvard randomized trial, and formative
+ feedback triples its effect when performance data actually drives the next decision.
+
So the missing piece isn't a better chat prompt. It's the loop: assign something worth doing, watch
+ it get done, and act on what happened. Every tutor needs that loop. No tutor company differentiates on
+ it. That's the definition of infrastructure that should be open, built once, and shared by everyone,
+ including companies that compete with each other. This project is that layer.
+
+
+
What we believe
+
+
+
Students learn by doing.
+
Retrieval practice, worked examples, and mastery loops carry some of the largest effect sizes in
+ education research. A tutor's job is to cause doing, and then respond to it.
+
+
+
Practice must be worth trusting.
+
Every activity here is checked against a real standards graph before it's built. A code that
+ doesn't exist gets an error naming the problem. When nothing verifies, the activity says so plainly
+ instead of dressing up. A teacher should be able to file what comes out of this without
+ fact-checking the citation.
+
+
+
The tutor should see what happened.
+
Every activity reports a structured result: correct or not, attempts, where the struggle was.
+ Correctness alone isn't enough; students reach right answers through wrong reasoning, and a tutor
+ that only sees a green checkmark misses it. Evidence is the product, and it also happens to be what
+ real efficacy research is made of.
+
+
+
Teachers approve what students see.
+
Generated content gets previewed by an adult, and assignment freezes the exact reviewed instance.
+ Students never meet an unreviewed roll of the dice. The research is unambiguous that generated
+ content needs expert review; we made the review step structural instead of optional.
+
+
+
Student data stays home.
+
The server stores nothing about students. Scoring calls run under your keys, inside your
+ deployment. In the year of COPPA opt-in defaults and consent lawsuits, this is not a feature flag.
+ It's the architecture.
+
+
+
The model never writes code that runs.
+
Generated output fills schemas that render against components humans wrote and reviewed. Activity
+ instances are data, open to share. New activity types are code, and travel a curated, signed path.
+ That line is the security model, and we will not blur it.
+
+
+
Open beats owned for this layer.
+
An activity corpus done well takes a year of rare, unglamorous work: interaction design, prompt
+ tuning past real failure modes, keyboard accessibility, pedagogy. Every tutor needs it and none is
+ differentiated by it. Building it once, in the open, under Apache-2.0, is the only version of this
+ that serves schools instead of a cap table.
+
+
+
+
What it is
+
+
An activity server. An MCP server with two tools: find_activity
+ answers a learning need with ranked, standards-verified options; show_widget builds one
+ and renders it in Claude or any MCP host. The registry is generative: it lists capabilities that
+ manufacture activities on demand, not a shelf of files. Sixteen today:
+
+
+
flashcards
swipe cards
drag-to-order
sort into groups
+
timeline builder
crossword
fraction area model
reading card
+
step-by-step reveal
narrated walkthrough
spot the mistake
draw the curve
+
defend the claim
live-scored draft
writing workshop
debate an AI
+
+
+
An SDK. For products with their own tutor:
+
+
const options = await findActivities({ topic: "fractions on a number line", grade: 3 });
+const activity = await createActivity(options[0]);
+
+<ActivityFrame activity={activity} onResult={(r) => agent.observe(r)} />
+
+
A document format. Activities are data: a surface (a profile of Google's A2UI,
+ flat components and shared state wired by plain references) plus a manifest (verified standards,
+ pedagogy, accessibility and language declarations, provenance). Drafted openly with conformance
+ classes and a changelog. We call it a format, not a specification; it earns the second word when a
+ second implementation exists.
+
A reference app. A full teacher-and-student application built on the same
+ registry: pathway builder, student walkthrough, rosters, reports. It's how we test everything and
+ what you can build; it isn't the thing you have to adopt.
+
+
+
How the pieces work
+
+
The registry. Each activity type is a React component plus a generator prompt,
+ registered with its pedagogy metadata: whether completing it measures correctness, how it completes,
+ which standards it fits. Adding a type is one file and one import. The planner, previews, and
+ discovery all read the same entry.
+
The standards layer. A source implements three functions: does this code exist,
+ what components make it up, what comes before it. Learning Commons is the default. A built-in,
+ keyless example source exists to be copied for a state framework, IB, or a district's own curriculum
+ map. Nothing downstream assumes US notation, or English.
+
The evidence contract. One result shape from interaction to data model to agent
+ to report. Adopters get research-grade interaction data from day one, which matters in a market where
+ only 12% of AI education products have published efficacy evidence.
+
+
+
Standing on prior art
+
+
Open-source education did not start here, and the projects that came before shape what this one
+ is. The open LMS ecosystem serves hundreds of millions of learners; what it lacks is a practice layer
+ an agent can drive. If a shorthand helps: H5P for the agent era.
+
+
+
+
H5P interactive content types
+
Proved that a shared, reusable library of interaction types beats every platform building its
+ own. The difference here: H5P's registry is a shelf of authored files; ours lists generative
+ capabilities, adds standards verification, and reports evidence to an agent instead of a gradebook
+ alone.
+
+
+
xAPI & Caliper learning analytics
+
Established that interaction evidence deserves a standard shape. Our result contract is narrower
+ on purpose — typed per activity, consumed in-loop by the agent that assigned it. An xAPI export for
+ LRS pipelines is a mapping, not a rewrite, and belongs on the roadmap.
+
+
+
LTI tool embedding
+
The door every LMS already has. MCP is this project's first transport because agents are the
+ first consumer; LTI Advantage lands at the gate where organizations embed activities in their own
+ platforms, so results can reach real gradebooks.
+
+
+
Open edX, Moodle, Kolibri open platforms
+
Demonstrated the governance path this project intends: real security policy, neutral stewardship
+ when more than one organization depends on it, and architecture that carries values — Kolibri made
+ offline-first an equity position; we make store-nothing-about-students one.
+
+
+
+
Get started
+
+
Try it
Add the server to Claude as a custom connector and ask for "a quick
+ activity on the water cycle for a 5th grader." Zero code.
+
Run it
One Vercel deploy or pnpm dev. One required secret (an LLM key).
+ No database. Set MCP_ACCESS_TOKEN on public instances, or anyone with your URL spends
+ your money.
+
Build on it
The SDK above, for tutors that need an action space and eyes on the
+ results.
+
Extend it
Add an activity type (one file), plug in your standards graph (copy the
+ example source), swap storage. If you need to fork to extend, we consider that a bug.
Education marketing has an honesty problem, so here is what you will not read from us. We won't
+ claim AI tutors outperform teachers; the evidence is conditional on design, and null results exist.
+ We won't claim generated content is classroom-ready without review; it isn't, and our own workflow
+ assumes it isn't. We won't cite the folklore formative-assessment effect sizes; real effects range
+ widely with implementation. And we won't call this a marketplace or a standard while there's one
+ publisher and one implementation. Ambition is cheap. Labels are earned.
+
+
+
Where it's going
+
The roadmap advances on pull, not dates. When an organization embeds activities in its own product,
+ we build the saved-activity shelf, the embeddable player, and identity forwarding, so results can land
+ in real gradebooks. When someone wants native rendering in their own design system, the headless widget
+ hooks and packages ship. When a second independent implementation exists, the format goes 1.0 with
+ schemas and conformance fixtures, the project moves to a neutral organization, and federated registries
+ with independent content review become the long-term shape: many servers, shared format, trust attached
+ by people qualified to attach it.
+
+
+
Status: v0.1, young, honest about it. One primary maintainer, a growing test
+ suite, security policy that tells you plainly what not to do yet. The most useful contributions right
+ now: a new activity type, a second standards source, or pointing your tutor at it and telling us what
+ broke. Born at the CodeAI education hackathon, built by people from several education companies, and
+ licensed Apache-2.0 so it stays consumable by all of them, including the ones that compete.
+
+
+
Format draft, conformance classes, and security/privacy considerations live in
+ docs/a2learn/. Governance, code of conduct, and contribution guides ship with the repo.
+ Research citations: Bastani et al., PNAS 2025; Kestin et al., Nature Scientific Reports 2025; and the
+ formative-assessment and retrieval-practice meta-analytic literature.
+
+
+
+
diff --git a/src/app/docs/[[...slug]]/page.tsx b/src/app/docs/[[...slug]]/page.tsx
new file mode 100644
index 00000000..883328f3
--- /dev/null
+++ b/src/app/docs/[[...slug]]/page.tsx
@@ -0,0 +1,178 @@
+import { readFile } from 'node:fs/promises';
+import path from 'node:path';
+
+import type { Metadata } from 'next';
+import Link from 'next/link';
+import { notFound } from 'next/navigation';
+import ReactMarkdown from 'react-markdown';
+import remarkGfm from 'remark-gfm';
+
+/**
+ * Serves the adopter docs (the markdown under `docs/`) from the app itself,
+ * so a hosted instance carries its own documentation. The files stay plain
+ * markdown — GitHub remains a first-class reader — and this route is just a
+ * themed renderer over them: a whitelist of slugs, not a filesystem walk, so
+ * a request can never read outside `docs/`.
+ */
+
+const REPO_BLOB = 'https://github.com/adamstankiewicz/interactive-learning-experiences/blob/main';
+
+const PAGES: Record = {
+ '': { file: 'README.md', title: 'Docs', nav: 'Overview' },
+ quickstart: { file: 'quickstart.md', title: 'Quickstart', nav: 'Quickstart' },
+ registry: { file: 'registry.md', title: 'The widget registry', nav: 'Registry' },
+ 'mcp-tools': { file: 'mcp-tools.md', title: 'MCP tools', nav: 'MCP tools' },
+ evidence: { file: 'evidence.md', title: 'The evidence contract', nav: 'Evidence' },
+ architecture: { file: 'architecture.md', title: 'Architecture', nav: 'Architecture' },
+};
+
+/**
+ * The markdown's relative links are written for GitHub. In the app they
+ * resolve to: sibling `.md` pages → their `/docs` route; the static landing →
+ * `/open.html`; anything reaching out of `docs/` (source files, CONTRIBUTING)
+ * → the file on GitHub.
+ */
+function rewriteHref(href: string): string {
+ if (/^[a-z]+:/i.test(href) || href.startsWith('#') || href.startsWith('/')) return href;
+ const [target, hash = ''] = href.split('#');
+ const anchor = hash ? `#${hash}` : '';
+ const clean = target.replace(/^\.\//, '');
+
+ if (clean === 'index.html') return '/open.html';
+ if (clean.startsWith('../')) return `${REPO_BLOB}/${clean.replace(/^(\.\.\/)+/, '')}${anchor}`;
+ if (clean.endsWith('.md')) {
+ const name = clean.slice(0, -3);
+ return name === 'README' ? `/docs${anchor}` : `/docs/${name}${anchor}`;
+ }
+ return href;
+}
+
+function pageFor(slug: string[] | undefined) {
+ const key = (slug ?? []).join('/');
+ return key in PAGES ? PAGES[key] : null;
+}
+
+export function generateStaticParams() {
+ return Object.keys(PAGES).map((key) => ({ slug: key ? key.split('/') : [] }));
+}
+
+export const dynamicParams = false;
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ slug?: string[] }>;
+}): Promise {
+ const page = pageFor((await params).slug);
+ return { title: page ? `${page.title} · a2learn docs` : 'Docs' };
+}
+
+export default async function DocsPage({ params }: { params: Promise<{ slug?: string[] }> }) {
+ const { slug } = await params;
+ const page = pageFor(slug);
+ if (!page) notFound();
+
+ const markdown = await readFile(path.join(process.cwd(), 'docs', page.file), 'utf8');
+ const activeKey = (slug ?? []).join('/');
+
+ return (
+
+ );
+}
From 50fe9ad9c76400cf7ed5ca94f27b0bc2edefe919 Mon Sep 17 00:00:00 2001
From: Adam Stankiewicz
Date: Sun, 30 Aug 2026 11:03:05 -0400
Subject: [PATCH 03/14] docs: cross-vertical lineage on the landing page
Two additions from the comparables research: STACK/Numbas as the proof the
open-practice-engine category works and lasts, and the OpenTelemetry /
Sentry / Home Assistant playbooks (neutral contract governance, SDK-first
adoption, registry-as-contribution-ladder). The open-beats-owned creed item
now cites observability's version of the same experiment.
Co-Authored-By: Claude Fable 5
---
docs/index.html | 20 ++++++++++++++++++--
public/open.html | 20 ++++++++++++++++++--
2 files changed, 36 insertions(+), 4 deletions(-)
diff --git a/docs/index.html b/docs/index.html
index 34c10126..a1ade201 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -356,8 +356,10 @@
The model never writes code that runs.
Open beats owned for this layer.
An activity corpus done well takes a year of rare, unglamorous work: interaction design, prompt
tuning past real failure modes, keyboard accessibility, pedagogy. Every tutor needs it and none is
- differentiated by it. Building it once, in the open, under Apache-2.0, is the only version of this
- that serves schools instead of a cap table.
+ differentiated by it. Observability already ran this experiment: direct competitors co-maintain
+ OpenTelemetry because instrumentation is a shared need, not an edge. Practice infrastructure is
+ education's version of that layer — built once, in the open, under Apache-2.0, consumable by
+ companies that compete.
@@ -433,6 +435,20 @@
LTI tool embedding
first consumer; LTI Advantage lands at the gate where organizations embed activities in their own
platforms, so results can reach real gradebooks.
+
+
STACK & Numbas open assessment engines
+
Proof that an open practice engine consumed by other platforms works and lasts — a decade of
+ sustained use across hundreds of institutions, carried by named university anchors. They are
+ authored and single-discipline; this project is generative and cross-domain, with the evidence
+ addressed to an agent.
+
+
+
OpenTelemetry, Sentry, Home Assistant infrastructure playbooks
+
The scripts this project follows from outside education: OpenTelemetry for governance
+ (competitors co-maintaining a neutral contract), Sentry for adoption (the SDK is the product; the
+ server is a detail behind it), and Home Assistant for community (a typed registry where supporting
+ your own use case is a first contribution).
+
Open edX, Moodle, Kolibri open platforms
Demonstrated the governance path this project intends: real security policy, neutral stewardship
diff --git a/public/open.html b/public/open.html
index 34c10126..a1ade201 100644
--- a/public/open.html
+++ b/public/open.html
@@ -356,8 +356,10 @@
The model never writes code that runs.
Open beats owned for this layer.
An activity corpus done well takes a year of rare, unglamorous work: interaction design, prompt
tuning past real failure modes, keyboard accessibility, pedagogy. Every tutor needs it and none is
- differentiated by it. Building it once, in the open, under Apache-2.0, is the only version of this
- that serves schools instead of a cap table.
+ differentiated by it. Observability already ran this experiment: direct competitors co-maintain
+ OpenTelemetry because instrumentation is a shared need, not an edge. Practice infrastructure is
+ education's version of that layer — built once, in the open, under Apache-2.0, consumable by
+ companies that compete.
@@ -433,6 +435,20 @@
LTI tool embedding
first consumer; LTI Advantage lands at the gate where organizations embed activities in their own
platforms, so results can reach real gradebooks.
+
+
STACK & Numbas open assessment engines
+
Proof that an open practice engine consumed by other platforms works and lasts — a decade of
+ sustained use across hundreds of institutions, carried by named university anchors. They are
+ authored and single-discipline; this project is generative and cross-domain, with the evidence
+ addressed to an agent.
+
+
+
OpenTelemetry, Sentry, Home Assistant infrastructure playbooks
+
The scripts this project follows from outside education: OpenTelemetry for governance
+ (competitors co-maintaining a neutral contract), Sentry for adoption (the SDK is the product; the
+ server is a detail behind it), and Home Assistant for community (a typed registry where supporting
+ your own use case is a first contribution).
+
Open edX, Moodle, Kolibri open platforms
Demonstrated the governance path this project intends: real security policy, neutral stewardship
From 22a7b22cdee2e4e79ca52a3de4996acbd8a633e9 Mon Sep 17 00:00:00 2001
From: Adam Stankiewicz
Date: Sun, 30 Aug 2026 11:19:08 -0400
Subject: [PATCH 04/14] =?UTF-8?q?docs:=20Di=C3=A1taxis=20IA,=20pedagogy=20?=
=?UTF-8?q?grounding,=20and=20a=20sidebar=20docs=20layout?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- New Learn section: the-loop.md (practice as a tool call, the three
return edges, the unbuilt edge named) and pedagogy.md — the
learning-science lineage of every structural choice (backward design,
gradual release, retrieval practice, worked examples, formative loops,
feedback discipline, rich evidence, structural review) plus the roadmap
for deeper grounding, under the no-folklore-claims policy.
- Configuration reference extracted from the quickstart into its own page.
- Docs home rewritten audience-first (tutor builders, evaluators,
educators, contributors) with a grouped Learn/Guides/Reference TOC.
- /docs route gains a sticky grouped sidebar on desktop; small screens
keep the wrapped nav row.
Co-Authored-By: Claude Fable 5
---
docs/README.md | 43 +++++++++++++---
docs/configuration.md | 24 +++++++++
docs/pedagogy.md | 84 ++++++++++++++++++++++++++++++
docs/quickstart.md | 26 +++-------
docs/the-loop.md | 75 +++++++++++++++++++++++++++
src/app/docs/[[...slug]]/page.tsx | 85 +++++++++++++++++++++++--------
6 files changed, 291 insertions(+), 46 deletions(-)
create mode 100644 docs/configuration.md
create mode 100644 docs/pedagogy.md
create mode 100644 docs/the-loop.md
diff --git a/docs/README.md b/docs/README.md
index 5f95a72f..1e0e5a47 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,15 +1,46 @@
# a2learn docs
-Documentation for adopters and extenders of the activity server. The
-[landing page](./index.html) carries the why; these pages carry the how.
+The [landing page](./index.html) carries the why; these pages carry the
+how. Start from who you are:
+
+- **Building an AI tutor?** [Quickstart](./quickstart.md) →
+ [The evidence contract](./evidence.md) — point your agent at the server,
+ then read what flows back when a student works.
+- **Evaluating for a platform or district?**
+ [How the loop works](./the-loop.md) → [Architecture](./architecture.md)
+ — the mechanism end to end, then the seams and where student data
+ cannot go.
+- **An educator or instructional designer?**
+ [The learning science behind the design](./pedagogy.md) — every
+ structural choice, traced to its finding.
+- **Contributing an activity kind or a standards source?**
+ [The widget registry](./registry.md) →
+ [CONTRIBUTING](../CONTRIBUTING.md).
+
+## All pages
+
+**Learn** — how and why it works
+
+| Page | Answers |
+|---|---|
+| [How the loop works](./the-loop.md) | Practice as a tool call: one request edge in, three return edges out |
+| [Pedagogy](./pedagogy.md) | The learning-science lineage of the design, and what deeper grounding is coming |
+| [Architecture](./architecture.md) | The four seams, the protocol facades, and the student-data boundary |
+
+**Guides** — get something running
| Page | Answers |
|---|---|
| [Quickstart](./quickstart.md) | Point an MCP host at it, or run your own instance, in minutes |
-| [The widget registry](./registry.md) | The core extension API: catalog entries, generators, and how a new activity kind ships |
-| [MCP tools](./mcp-tools.md) | The wire surface an agent calls: `show_widget`, `score_draft`, and what's coming |
-| [The evidence contract](./evidence.md) | What flows back when a student works, and what reads it |
-| [Architecture](./architecture.md) | The seams — registry, standards source, storage, protocol facades — and where student data does and doesn't go |
+| [Configuration](./configuration.md) | Every environment variable, and the two operational warnings |
+
+**Reference** — the contracts
+
+| Page | Answers |
+|---|---|
+| [The widget registry](./registry.md) | The core extension API: catalog entries, generators, adding a kind |
+| [MCP tools](./mcp-tools.md) | The wire surface an agent calls, shipped and planned |
+| [The evidence contract](./evidence.md) | The event shapes, their honesty rules, and what reads them |
The a2learn document format draft (surface + manifest, conformance classes)
lives separately under `docs/a2learn/` once its branch merges.
diff --git a/docs/configuration.md b/docs/configuration.md
new file mode 100644
index 00000000..739b6bd2
--- /dev/null
+++ b/docs/configuration.md
@@ -0,0 +1,24 @@
+# Configuration reference
+
+Every environment variable the server reads. One secret is required (an LLM
+key); everything else has a working default — no database, no accounts.
+
+| Variable | Default | What it does |
+|---|---|---|
+| `LLM_PROVIDER` | `anthropic` | Which provider builds activities: `anthropic`, `openai`, `bedrock`, or `openrouter` |
+| `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `OPENROUTER_API_KEY` | — | The key for the chosen provider |
+| `AWS_REGION`, `AWS_BEARER_TOKEN_BEDROCK` | `us-west-2`, — | Bedrock configuration when `LLM_PROVIDER=bedrock` |
+| `OPENAI_FALLBACK_API_KEY`, `OPENAI_FALLBACK_MODEL_ID` | —, `gpt-4o-mini` | Optional second provider used when the primary fails |
+| `STANDARDS_SOURCE` | `learning-commons` | Which standards graph verifies codes; `example` is the built-in keyless source |
+| `LEARNING_COMMONS_API_KEY`, `LEARNING_COMMONS_MCP_URL` | — | Credentials for the default standards source |
+| `STORAGE_ADAPTER` | auto | `memory` (nothing persists across restarts) or `supabase`; unset picks Supabase only when its vars are configured |
+| `NEXT_PUBLIC_SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY` | — | Only when using the Supabase adapter; `pnpm db:start` runs a local stack |
+| `SEED_DEMO_ROSTER` | off | Seeds demo students in the reference app |
+
+Two operational notes:
+
+- Model calls run under **your** keys, inside **your** deployment — scoring
+ a student's work never leaves the instance you control.
+- **Public instances:** anyone who has your URL can spend your LLM budget.
+ Keep instances private until token auth (`MCP_ACCESS_TOKEN`, planned for
+ v0.1) lands, or put your own gateway in front.
diff --git a/docs/pedagogy.md b/docs/pedagogy.md
new file mode 100644
index 00000000..8508d6bc
--- /dev/null
+++ b/docs/pedagogy.md
@@ -0,0 +1,84 @@
+# The learning science behind the design
+
+Every structural choice in this project traces to a finding in the learning
+sciences — this page names the lineage, so adopters can evaluate the design
+on the discipline's own terms, and contributors know which rules are
+load-bearing. Citations are to real findings; the
+[what we won't claim](./index.html) policy applies here most of all.
+
+## What the shape already encodes
+
+**Backward design.** The pathway planner works outcome-first: it derives
+learning outcomes from the verified standard's decomposition, enumerates
+likely misconceptions, and only then composes steps that serve those
+outcomes (Wiggins & McTighe, *Understanding by Design*). The plan document
+shows coverage per outcome — including when coverage is thin — because a
+plan that can't say what each step is *for* isn't a plan.
+
+**Gradual release.** The four step purposes — `activate`, `model`,
+`practice`, `check` — are the gradual-release arc ("I do, we do, you do,
+show me") made machine-readable. The planner sequences them; the UI colors
+them; the registry's `coverageRule` and `assesses` metadata keep a "check"
+step from being built on an activity that measures nothing.
+
+**Retrieval practice over re-reading.** Practice and check steps are built
+from kinds that make the student *produce* — sort, place, argue, draft —
+because testing beats restudying for retention (Roediger & Karpicke, 2006).
+Reading kinds exist for the `model` beat, deliberately serif, deliberately
+calm; they are never the whole pathway.
+
+**Worked examples and cognitive load.** Step-reveal and narrated kinds are
+worked examples: study-the-solution scaffolds for early acquisition (Sweller's
+cognitive load framework), sequenced before independent practice rather than
+after.
+
+**Formative loops, low stakes.** Evidence drives the next decision, not a
+grade: a wrong finish can inject a re-teach step immediately (Black &
+Wiliam's formative assessment case, without the folklore effect sizes), the
+injected step is announced as help, and it costs the student nothing — no
+star lost. Struggle is information, not penalty.
+
+**Feedback discipline.** Feedback in activities and remediation copy is
+immediate, task-level, and misconception-naming — "two of your answers were
+objects at rest; you're reading 'no motion' as 'no force'" — never
+ego-level praise or blame (Hattie & Timperley, 2007; Kluger & DeNisi, 1996
+on why person-directed feedback backfires). This is a review rule for
+contributed kinds, not a style preference.
+
+**Rich evidence over booleans.** The evidence contract carries *what the
+struggle was* (`struggledWith`, attempts, per-step outcomes), because
+students reach right answers through wrong reasoning and a green checkmark
+hides it. Evidence structured this way is also what real efficacy research
+is made of — the contract doubles as a research instrument.
+
+**Expert review as structure.** Generated content is previewed by an adult
+and assignment freezes the reviewed instance. The literature on generative
+content in classrooms is unambiguous that expert review is required; here
+it is architectural, not optional.
+
+## Where deeper grounding lands next
+
+These are roadmap commitments, listed so the direction is inspectable:
+
+- **Pedagogy metadata on the registry entry** — each kind declares its
+ practice type (retrieval, worked example, elaboration, generation), so
+ the planner composes pathways on learning-science grounds, not variety.
+- **A UDL + accessibility checklist in the widget proposal template** —
+ every contributed kind answers CAST's multiple-means questions and the
+ keyboard/ARIA contract the same way it must already answer `assesses`.
+- **Spacing and interleaving as pathway primitives** — distributed practice
+ and interleaving carry some of the largest effects in the field and are
+ *scheduling* features; they arrive with saved activities.
+- **Evidence tiers** — shaping the universal result so adopters can climb
+ ESSA-style evidence levels (usage → correlational → causal) without
+ changing instrumentation.
+- **An eval harness for generation quality** — rubric-scored checks on
+ generated activities, so pedagogy regressions fail builds the way type
+ errors do.
+
+## What this page will not do
+
+No universal claims about AI tutoring outperforming teachers, no
+classroom-ready-without-review claims, no d=0.4-to-0.7 formative folklore.
+Where the research is conditional, the design treats the *conditions* as
+requirements — that is the whole method.
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 7c66abe9..7e661dc4 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -33,26 +33,12 @@ ANTHROPIC_API_KEY=sk-ant-...
Deploy is one Vercel deploy of the repo with the same env vars.
-> **Public instances:** anyone who has your URL can spend your LLM budget.
-> Keep instances private until token auth (`MCP_ACCESS_TOKEN`, planned for
-> v0.1) lands, or put your own gateway in front.
-
-## Configuration reference
-
-| Variable | Default | What it does |
-|---|---|---|
-| `LLM_PROVIDER` | `anthropic` | Which provider builds activities: `anthropic`, `openai`, `bedrock`, or `openrouter` |
-| `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `OPENROUTER_API_KEY` | — | The key for the chosen provider |
-| `AWS_REGION`, `AWS_BEARER_TOKEN_BEDROCK` | `us-west-2`, — | Bedrock configuration when `LLM_PROVIDER=bedrock` |
-| `OPENAI_FALLBACK_API_KEY`, `OPENAI_FALLBACK_MODEL_ID` | —, `gpt-4o-mini` | Optional second provider used when the primary fails |
-| `STANDARDS_SOURCE` | `learning-commons` | Which standards graph verifies codes; `example` is the built-in keyless source |
-| `LEARNING_COMMONS_API_KEY`, `LEARNING_COMMONS_MCP_URL` | — | Credentials for the default standards source |
-| `STORAGE_ADAPTER` | `memory` | `memory` (nothing persists across restarts) or `supabase` |
-| `NEXT_PUBLIC_SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY` | — | Only when `STORAGE_ADAPTER=supabase`; `pnpm db:start` runs a local stack |
-| `SEED_DEMO_ROSTER` | off | Seeds demo students in the reference app |
-
-Model calls run under **your** keys, inside **your** deployment — scoring a
-student's work never leaves the instance you control.
+## Configuration
+
+One required secret (the LLM key); everything else defaults. The
+[configuration reference](./configuration.md) lists every variable —
+including the two operational warnings worth reading before deploying
+anywhere public.
## What you just ran
diff --git a/docs/the-loop.md b/docs/the-loop.md
new file mode 100644
index 00000000..420272f3
--- /dev/null
+++ b/docs/the-loop.md
@@ -0,0 +1,75 @@
+# How the loop works
+
+The project's one innovation, mechanically: **practice as a tool call** — an
+agent invokes a verified learning activity the way it invokes any function,
+and the student's actual work comes back as the return value. This page
+walks the loop through the code: one request edge in, three return edges
+out.
+
+```
+agent/host ──① ask──► ② verify ──► manufacture ──► render ──③ student works
+ ▲ │
+ └── ④a prose into the conversation │ typed events
+ ④b remediation injected mid-walkthrough ◄─────────────┤
+ ④c teacher report and student profile ◄─────────────┘
+```
+
+## ① Ask
+
+An agent calls `show_widget` on `/api/mcp` with a topic or a standard code;
+a product backend can hit the REST mirror instead. Discovery-first flows go
+through `find_activity`, which ranks listings derived from
+[registry](./registry.md) metadata — each listing carries the exact
+`show_widget` arguments that would build it, because the inventory is
+generative, not a shelf.
+
+## ② Verify, then manufacture
+
+`buildWidget` has the model propose candidate standard codes, then the
+pluggable standards source's `verify()` checks each against the graph.
+`null` means the model hallucinated the code — surfaced as an error, never
+built on. If nothing verifies, the activity is still built but explicitly
+labeled exploration: degraded honestly rather than dressed up.
+
+A kind is chosen through the registry's `coverageRule` and planner
+metadata, that kind's server-side generator makes one model call, and the
+output must validate against the kind's Zod schema. The model fills
+schemas; it never emits code. A failed generation falls back to a simpler
+kind, note attached.
+
+## ③ Student works → typed evidence
+
+The spec renders through the same component registry everywhere — the MCP
+Apps shell in chat hosts, the walkthrough in the reference app. Components
+emit typed events (`widget_shown`, `answer_checked`, `hint_requested`,
+`widget_completed`, `hesitation`) with the honesty rules the
+[evidence contract](./evidence.md) documents: in-progress work carries no
+verdict, and only kinds whose registry entry says `assesses: true` carry
+verdicts at all.
+
+## ④ The loop closes on three edges
+
+**④a — back to the assigning agent.** Inside a chat host, the widget posts
+`ui/update-model-context` (MCP Apps) into the host's model context — a
+prose sentence written for a model reader: *"Located the flawed step on the
+second try, then misdiagnosed it as a sample-size problem."* The
+assistant's next turn can act on it. Outside a host this is a no-op, so
+widgets call it unconditionally.
+
+**④b — inside the walkthrough, with no agent at all.** The telemetry
+endpoint watches completion verdicts; a wrong one generates a re-teach
+activity server-side and injects it after the student's current position —
+announced as *just added · extra practice*, costing the student nothing.
+The loop runs autonomously even when the caller is just a shared link.
+
+**④c — to the teacher.** The same events aggregate into the session
+report's per-step evidence strips, the mastery rollup, and the student
+profile that weights what the next generated pathway emphasizes.
+
+## The edge still being built
+
+Chat hosts get prose (right for a model reader); teachers get aggregates.
+The typed, programmatic return for product backends — one universal
+`WidgetResult` per activity, delivered to an SDK `onResult` handler — is
+designed and is v0.1's centerpiece. The events underneath it already flow;
+the universal result is a summary layer over them.
diff --git a/src/app/docs/[[...slug]]/page.tsx b/src/app/docs/[[...slug]]/page.tsx
index 883328f3..e5d43058 100644
--- a/src/app/docs/[[...slug]]/page.tsx
+++ b/src/app/docs/[[...slug]]/page.tsx
@@ -17,15 +17,20 @@ import remarkGfm from 'remark-gfm';
const REPO_BLOB = 'https://github.com/adamstankiewicz/interactive-learning-experiences/blob/main';
-const PAGES: Record = {
- '': { file: 'README.md', title: 'Docs', nav: 'Overview' },
- quickstart: { file: 'quickstart.md', title: 'Quickstart', nav: 'Quickstart' },
- registry: { file: 'registry.md', title: 'The widget registry', nav: 'Registry' },
- 'mcp-tools': { file: 'mcp-tools.md', title: 'MCP tools', nav: 'MCP tools' },
- evidence: { file: 'evidence.md', title: 'The evidence contract', nav: 'Evidence' },
- architecture: { file: 'architecture.md', title: 'Architecture', nav: 'Architecture' },
+const PAGES: Record = {
+ '': { file: 'README.md', title: 'Docs', nav: 'Overview', section: '' },
+ 'the-loop': { file: 'the-loop.md', title: 'How the loop works', nav: 'How the loop works', section: 'Learn' },
+ pedagogy: { file: 'pedagogy.md', title: 'Pedagogy', nav: 'Pedagogy', section: 'Learn' },
+ architecture: { file: 'architecture.md', title: 'Architecture', nav: 'Architecture', section: 'Learn' },
+ quickstart: { file: 'quickstart.md', title: 'Quickstart', nav: 'Quickstart', section: 'Guides' },
+ configuration: { file: 'configuration.md', title: 'Configuration', nav: 'Configuration', section: 'Guides' },
+ registry: { file: 'registry.md', title: 'The widget registry', nav: 'Widget registry', section: 'Reference' },
+ 'mcp-tools': { file: 'mcp-tools.md', title: 'MCP tools', nav: 'MCP tools', section: 'Reference' },
+ evidence: { file: 'evidence.md', title: 'The evidence contract', nav: 'Evidence contract', section: 'Reference' },
};
+const SECTIONS = ['Learn', 'Guides', 'Reference'];
+
/**
* The markdown's relative links are written for GitHub. In the app they
* resolve to: sibling `.md` pages → their `/docs` route; the static landing →
@@ -78,11 +83,60 @@ export default async function DocsPage({ params }: { params: Promise<{ slug?: st
return (
-
+
a2learn docs
-
+
+
+
+
+
+
+ {/* Small screens get the nav as a wrapped row instead of a rail. */}
+
-
- About →
-
-
-
-
-
{markdown}
-
+
+
);
}
From 2de423095183b6675dd1d2763f1e5c1638f9d078 Mon Sep 17 00:00:00 2001
From: Adam Stankiewicz
Date: Sun, 30 Aug 2026 11:26:18 -0400
Subject: [PATCH 05/14] docs: active learning is why the project exists;
evidence is how the loop closes
Co-Authored-By: Claude Fable 5
---
docs/evidence.md | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/docs/evidence.md b/docs/evidence.md
index 9f5678a6..990a621c 100644
--- a/docs/evidence.md
+++ b/docs/evidence.md
@@ -1,10 +1,12 @@
# The evidence contract
-The reason this project exists: when a student works through an activity,
-what happened flows back in a structured shape a program can act on — the
-agent that assigned it, the teacher's report, the remediation loop. Chat
-alone can't see whether teaching happened; this contract is how the loop
-closes.
+The project exists so students *do* — think critically, practice, produce —
+instead of passively consuming AI output. The extensible widgets and the
+standards-verified loop are that commitment made structural. The evidence
+contract is how the loop closes: when a student works through an activity,
+what happened flows back in a shape a program can act on — the agent that
+assigned it, the teacher's report, the remediation loop. Chat alone can't
+see whether the doing happened; this contract is what makes it visible.
## The interaction event
From c9a940f87b25a4e182a677979f103746c4dabf1c Mon Sep 17 00:00:00 2001
From: Adam Stankiewicz
Date: Sun, 30 Aug 2026 11:31:27 -0400
Subject: [PATCH 06/14] docs: PBL positioning and the Reich cautions in the
pedagogy page
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Where this sits relative to project-based learning (the scaffolding layer
projects need, per the guided-instruction critique — never claimed as PBL
support), and the Failure to Disrupt warnings the design answers or
honestly cannot: routine assessment, the Matthew effect, the pedagogy of
poverty. Teacher Moments noted as kindred OSS.
Co-Authored-By: Claude Fable 5
---
docs/pedagogy.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/docs/pedagogy.md b/docs/pedagogy.md
index 8508d6bc..67cca38b 100644
--- a/docs/pedagogy.md
+++ b/docs/pedagogy.md
@@ -56,6 +56,52 @@ and assignment freezes the reviewed instance. The literature on generative
content in classrooms is unambiguous that expert review is required; here
it is architectural, not optional.
+## Where this sits relative to project-based learning
+
+This is a deliberate-practice layer, not a project platform. Gold-standard
+project-based learning — sustained inquiry, authentic public products,
+critique and revision over weeks — needs multi-session artifacts,
+collaboration, and rubric judgment of open-ended work, none of which this
+server pretends to provide. The relationship is complementary, and it is
+the one the research demands: the standing critique of project-based
+learning (Kirschner, Sweller & Clark on minimally guided instruction) is
+that projects fail novices when underlying skills aren't explicitly
+scaffolded, and PBL's own gold-standard rubrics list embedded scaffolding
+as required. That scaffold is what this project is: just-in-time skill
+practice a project — or the agent supporting it — can call mid-stream,
+with evidence of the doing coming back. A few kinds already lean
+constructive (the writing workshop's critique-and-revision cycle, the
+debate), proof the registry can hold production, not just recognition.
+Claiming "PBL support" beyond that would violate the labels-are-earned
+rule, so we don't.
+
+## Cautions we take from the critical literature
+
+Justin Reich's *Failure to Disrupt* is the pre-mortem this project designs
+against, and three of its warnings are load-bearing here:
+
+- **The trap of routine assessment.** Software gravitates toward what is
+ easy to assess, and a typed evidence contract could become a drill
+ machine. The design pushes back — open-ended kinds carry no false
+ verdicts (`correct: null` is a first-class value), generation includes
+ constructive tasks, and human review is structural — but the limit is
+ real: the most valuable learning stays ill-structured and human-judged,
+ and this contract measures the part that can be measured, not the whole.
+- **The EdTech Matthew effect.** Free tools disproportionately reach the
+ already-advantaged. "Free and self-hostable" is an equity *precondition*,
+ not an equity strategy; reaching under-resourced settings is distribution
+ work through the institutions that serve them, and the roadmap treats it
+ that way.
+- **The pedagogy of poverty.** Registries don't decide which students get
+ the drill kinds and which get the constructive ones — deployments do. We
+ can name the risk, ship both halves of the registry, and surface usage
+ honestly in reports; we cannot engineer the risk away, and won't claim
+ to.
+
+The same lab's open-source practice simulations for teachers (Teacher
+Moments) are a kindred project: the practice-loop idea, aimed at teacher
+learning — a reminder that "the student" in this loop need not be a child.
+
## Where deeper grounding lands next
These are roadmap commitments, listed so the direction is inspectable:
From 825434c3d34b36a182d6d13882ab59838110f29f Mon Sep 17 00:00:00 2001
From: Adam Stankiewicz
Date: Sun, 30 Aug 2026 11:34:04 -0400
Subject: [PATCH 07/14] docs: framework-first framing on the landing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Per the positioning steer: the registry API and custom experiences are
the product; the sixteen built-ins are seed content. The hero meta says
'extensible activity registry' instead of a count, the full kind gallery
compresses to four examples plus a dashed '+ your kind · one file' chip,
and a new bullet names the extension API before the collection.
Co-Authored-By: Claude Fable 5
---
docs/index.html | 16 ++++++++++------
public/open.html | 16 ++++++++++------
2 files changed, 20 insertions(+), 12 deletions(-)
diff --git a/docs/index.html b/docs/index.html
index a1ade201..1ffdb49c 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -170,6 +170,7 @@
background: var(--sunk); border: 1px solid var(--line); border-radius: 5px;
padding: 0.22rem 0.55rem; margin: 0;
}
+ .kinds li.yours { background: transparent; border-style: dashed; color: var(--muted); }
.lineage { margin: 1rem 0; }
.lineage .row { padding: 0.85rem 0; border-bottom: 1px solid var(--line); }
.lineage .row:last-child { border-bottom: none; }
@@ -248,7 +249,7 @@
AI tutors can talk. This makes them able to teach.
self-hostable
stores nothing about students
MCP + REST
-
16 activity types
+
extensible activity registry
@@ -368,13 +369,16 @@
What it is
An activity server. An MCP server with two tools: find_activity
answers a learning need with ranked, standards-verified options; show_widget builds one
and renders it in Claude or any MCP host. The registry is generative: it lists capabilities that
- manufacture activities on demand, not a shelf of files. Sixteen today:
+ manufacture activities on demand, not a shelf of files.
+
An extension API, before it is a widget collection. A registry entry is a
+ schema, a component, a generator, and declared pedagogy (assesses, coverage) — adding a
+ kind is one file, and nothing in the planner, previews, or MCP surface needs to learn it exists.
+ Sixteen built-in kinds ship as the seed, from flashcards to a live-scored writing workshop and a
+ debate against an AI; the point is what you build against the same contract:
AI tutors can talk. This makes them able to teach.
self-hostable
stores nothing about students
MCP + REST
-
16 activity types
+
extensible activity registry
@@ -368,13 +369,16 @@
What it is
An activity server. An MCP server with two tools: find_activity
answers a learning need with ranked, standards-verified options; show_widget builds one
and renders it in Claude or any MCP host. The registry is generative: it lists capabilities that
- manufacture activities on demand, not a shelf of files. Sixteen today:
+ manufacture activities on demand, not a shelf of files.
+
An extension API, before it is a widget collection. A registry entry is a
+ schema, a component, a generator, and declared pedagogy (assesses, coverage) — adding a
+ kind is one file, and nothing in the planner, previews, or MCP surface needs to learn it exists.
+ Sixteen built-in kinds ship as the seed, from flashcards to a live-scored writing workshop and a
+ debate against an AI; the point is what you build against the same contract:
-
flashcards
swipe cards
drag-to-order
sort into groups
-
timeline builder
crossword
fraction area model
reading card
-
step-by-step reveal
narrated walkthrough
spot the mistake
draw the curve
-
defend the claim
live-scored draft
writing workshop
debate an AI
+
drag-to-order
fraction area model
writing workshop
debate an AI
+
+ your kind · one file
An SDK. For products with their own tutor:
From f0acd885a72e0c7bd6cc16406b5d802447a57b64 Mon Sep 17 00:00:00 2001
From: Adam Stankiewicz
Date: Sun, 30 Aug 2026 11:43:42 -0400
Subject: [PATCH 08/14] =?UTF-8?q?docs:=20the=20messaging=20guide=20?=
=?UTF-8?q?=E2=80=94=20brand=20hardened=20into=20claim-hygiene=20rules?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
One sentence in three registers, the two approved category attachments,
the enemy (passive consumption, with the PNAS fact phrased exactly so it
can't drift), one move per audience, the NOT-list, voice rules including
framework-first, an approved/banned claims table, the proof hierarchy,
and boilerplate for About fields and announcement leads. Published
openly, in a new Project section of the docs.
Co-Authored-By: Claude Fable 5
---
docs/README.md | 6 ++
docs/messaging.md | 124 ++++++++++++++++++++++++++++++
src/app/docs/[[...slug]]/page.tsx | 3 +-
3 files changed, 132 insertions(+), 1 deletion(-)
create mode 100644 docs/messaging.md
diff --git a/docs/README.md b/docs/README.md
index 1e0e5a47..066da3b0 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -42,6 +42,12 @@ how. Start from who you are:
| [MCP tools](./mcp-tools.md) | The wire surface an agent calls, shipped and planned |
| [The evidence contract](./evidence.md) | The event shapes, their honesty rules, and what reads them |
+**Project** — for people who represent it
+
+| Page | Answers |
+|---|---|
+| [Messaging guide](./messaging.md) | The sentence, the category, the claim-hygiene rules, and the boilerplate — so everyone pitches it identically |
+
The a2learn document format draft (surface + manifest, conformance classes)
lives separately under `docs/a2learn/` once its branch merges.
diff --git a/docs/messaging.md b/docs/messaging.md
new file mode 100644
index 00000000..7966672c
--- /dev/null
+++ b/docs/messaging.md
@@ -0,0 +1,124 @@
+# Messaging guide
+
+How to talk about a2learn, for anyone who represents it — maintainers,
+contributors, conference hallways. Published openly because a project whose
+brand is honesty has nothing to hide about how it communicates. When in
+doubt, say less and show the transcript.
+
+## The sentence
+
+One claim, three registers. Do not invent a fourth.
+
+| Register | Use it | The line |
+|---|---|---|
+| Emotional | Talks, headlines, the hero | "AI tutors can talk. This makes them able to teach." |
+| Technical | Developer audiences, READMEs | "Practice as a tool call — assign verified activities, get evidence of what the student did back." |
+| Category | Directories, About fields, introductions | "The open activity server for teaching agents." |
+
+## The name
+
+**a2learn** — agent-to-learner, deliberately rhyming with A2A and A2UI so
+protocol-literate readers shelve it correctly before reading a word. Always
+lowercase, one word, mono-set where the medium allows. Never "A2Learn",
+never "A2L" (collides with an automotive format).
+
+## The category, and what to attach it to
+
+"Activity server" is a new category; new categories only stick when
+relentlessly attached to known ones. The two approved attachments:
+
+- For infrastructure people: **"What OpenTelemetry is to observability,
+ this is to practice"** — the loop every tutor needs and none
+ differentiates on, co-maintainable by competitors.
+- For education people: **"The open assessment engines — STACK, Numbas —
+ proved this category; this is that, generative and agent-native."**
+ ("H5P for the agent era" is acceptable shorthand with the caveat that
+ H5P is a content library and this is a framework.)
+
+## The enemy
+
+Never a competitor. The enemy is **passive consumption of AI output**. The
+one repeatable fact, phrased exactly (drift breeds misquotes):
+
+> In a 2025 randomized trial published in PNAS, students given unguided
+> ChatGPT for math practice solved 48% more practice problems — then scored
+> 17% worse on the exam, while reporting they felt more prepared.
+
+Every pitch has the same skeleton: consumption harms → the loop works →
+the loop shouldn't be proprietary → here it is, Apache-2.0.
+
+## One move per audience
+
+| Audience | The move | The line |
+|---|---|---|
+| AI-tutor builders (primary) | Sell the return value | "Your agent can explain; it can't verify anything landed. Three lines of SDK and it can." |
+| Platform / district evaluators | Compliance as architecture | "Stores nothing about students — not a promise, an architecture you can read." |
+| Educators | The review gate and the low stakes | "Nothing reaches a student a teacher didn't review, and struggle costs nothing." |
+| Contributors | The registry, never the catalog | "Your subject needs an activity kind that doesn't exist? One file." |
+
+## What it is NOT — say it first
+
+Not a tutor (it makes yours able to teach). Not an LMS (it plugs into what
+you have). Not a content library (a registry of capabilities, not a shelf).
+Not a chatbot (it is what the chatbot is missing).
+
+## Voice rules
+
+1. **Never overclaim.** The banned-claims list below is load-bearing. "What
+ we won't claim" is a feature of the product.
+2. **Framework first.** Lead with the registry API and "one file to add a
+ kind." Built-in kinds are seed content — a few examples, never an
+ enumerated gallery, never a count in hero position.
+3. **Show, then say.** The transcript (find → ✓ verified → evidence
+ returns) is the argument; prose is commentary on it.
+4. **Verification green is semantic.** In any designed material, green
+ appears only where something is verified — never decoration. The palette
+ is the epistemology.
+5. **Students do; agents adapt; teachers approve.** Every description
+ should let all three subjects act. If a paragraph has only the AI doing
+ things, rewrite it.
+6. **Plain words.** "Activity", "evidence", "verified" — not "learning
+ objects", "insights", "AI-powered".
+
+## Claim hygiene
+
+Approved, with sources: the PNAS finding (Bastani et al., 2025); the
+Harvard practice-tutoring effect (Kestin et al., 2025, effect size near
+1.0); "self-hostable, stores nothing about students" (architecture);
+"Apache-2.0, cross-org" (license, provenance).
+
+Banned, permanently: AI tutors outperform teachers; generated content is
+classroom-ready without review; folklore formative-assessment effect
+sizes; "marketplace" or "standard" while there is one publisher and one
+implementation; any student-outcome claim about a2learn itself until real
+deployments produce evidence.
+
+## The proof hierarchy
+
+Ten seconds: say the category sentence.
+Thirty seconds: show the transcript, say the PNAS fact, say
+"Apache-2.0, cross-org — born at the CodeAI education hackathon."
+Two minutes: add the loop's three return edges (agent adapts, remediation
+injects, teacher sees evidence) and the one-file registry story. Stop
+talking; the demo answers questions better than a roadmap does.
+
+## Boilerplate
+
+**GitHub About / directory listing (≤120 chars):**
+"The open activity server for teaching agents — verified practice out,
+evidence back. Apache-2.0."
+
+**One paragraph:**
+"a2learn gives teaching agents an action space: real interactive practice,
+verified against real learning standards, with evidence of what the
+student did flowing back to the agent that assigned it. It ships as an
+open-source MCP server, a widget registry you extend with one file, and an
+evidence contract one SDK handler consumes. Self-hostable; stores nothing
+about students; Apache-2.0."
+
+**Announcement lead:**
+"Chat alone doesn't teach — in a 2025 PNAS trial, students practicing with
+unguided ChatGPT scored 17% worse while feeling more prepared. What works
+is the loop: assign real practice, watch it get done, act on what
+happened. Every AI tutor needs that loop; none differentiates on it. So we
+built it once, in the open."
diff --git a/src/app/docs/[[...slug]]/page.tsx b/src/app/docs/[[...slug]]/page.tsx
index e5d43058..dc2d3ebf 100644
--- a/src/app/docs/[[...slug]]/page.tsx
+++ b/src/app/docs/[[...slug]]/page.tsx
@@ -27,9 +27,10 @@ const PAGES: Record
Date: Sun, 30 Aug 2026 11:51:34 -0400
Subject: [PATCH 09/14] docs: build_pathway, the agent-as-sequencer pattern,
and the report channel
Co-Authored-By: Claude Fable 5
---
docs/mcp-tools.md | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/docs/mcp-tools.md b/docs/mcp-tools.md
index ff730ccc..a39b645a 100644
--- a/docs/mcp-tools.md
+++ b/docs/mcp-tools.md
@@ -26,6 +26,47 @@ not a quietly wrong activity) → pick a kind → run that kind's
via the MCP Apps shell. When no code survives verification, the result says
so plainly and renders as an exploration activity.
+## `build_pathway`
+
+The full pipeline as one tool call (about half a minute): plans 4–6
+sequenced activities against a verified standard, persists the session, and
+returns a student link (`/learn/{sessionId}`) plus a structured plan
+summary. Use it when someone wants a complete lesson to hand off; an
+instance without persistent storage says so honestly instead of returning a
+dead link. *(Implemented on `feat/mcp-pathway-and-reporting`, merging with
+v0.1.)*
+
+## Two ways to run a sequence
+
+`build_pathway` is for handing a lesson off. In a live conversation, **the
+agent is the sequencer** — and that pattern needs no extra tool:
+
+1. Call `show_widget` for the first activity.
+2. The widget reports what the student did back into the conversation.
+3. Decide what the evidence calls for; call `show_widget` again.
+
+That adaptive loop is the product demonstrated in its purest form: the
+pathway plan lives in the agent's judgment, informed by verified evidence
+after every step, instead of being fixed up front.
+
+## What the widget says back
+
+Widgets report into the conversation over `ui/update-model-context`
+(MCP Apps). The channel carries two things per message:
+
+- **A prose sentence written for a model reader** — "The student worked
+ through the drag-sort (3.NF.A.2) and got it right. It took two
+ attempts." Prose leads because it is what the model responds to.
+- **A structured block alongside** — `widget_result` (kind, standard,
+ correct, attempts, hints used, score, per-kind detail) so exact fields
+ survive without parsing English. This is the same shape the SDK's
+ universal `WidgetResult` converges on.
+
+Reporting is deliberately quiet: one message at completion, plus at most
+one early **struggle signal** — after three wrong checks without a finish,
+the conversation hears about stuck work once, so an agent can help before
+the finish line instead of only after it.
+
## `score_draft`
Scores a student's written response for the draft-meter kind — the model call
From d3661513898f45dc0f13ed5ea281061e8ce29dac Mon Sep 17 00:00:00 2001
From: Adam Stankiewicz
Date: Sun, 30 Aug 2026 12:31:33 -0400
Subject: [PATCH 10/14] =?UTF-8?q?docs:=20the=20A2UI=20surface=20is=20draft?=
=?UTF-8?q?ed=20as=20a=20profile=20=E2=80=94=20say=20so?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The app's own wire format maps to it at the boundary and conformance is
tracked openly; an evaluator who greps for A2UI should find the claim
matching the code.
Co-Authored-By: Claude Fable 5
---
docs/index.html | 5 +++--
public/open.html | 5 +++--
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/docs/index.html b/docs/index.html
index 1ffdb49c..70e3d181 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -388,8 +388,9 @@
A document format. Activities are data: a surface (a profile of Google's A2UI,
- flat components and shared state wired by plain references) plus a manifest (verified standards,
+
A document format. Activities are data: a surface (drafted as a profile of
+ Google's A2UI — flat components and shared state wired by plain references; the app's own
+ wire format maps to it at the boundary, and conformance is tracked openly) plus a manifest (verified standards,
pedagogy, accessibility and language declarations, provenance). Drafted openly with conformance
classes and a changelog. We call it a format, not a specification; it earns the second word when a
second implementation exists.
A document format. Activities are data: a surface (a profile of Google's A2UI,
- flat components and shared state wired by plain references) plus a manifest (verified standards,
+
A document format. Activities are data: a surface (drafted as a profile of
+ Google's A2UI — flat components and shared state wired by plain references; the app's own
+ wire format maps to it at the boundary, and conformance is tracked openly) plus a manifest (verified standards,
pedagogy, accessibility and language declarations, provenance). Drafted openly with conformance
classes and a changelog. We call it a format, not a specification; it earns the second word when a
second implementation exists.
Status: v0.1, young, honest about it. One primary maintainer, a growing test
- suite, security policy that tells you plainly what not to do yet. The most useful contributions right
+
Status: v0.1, young, honest about it. A maintainer group still forming from
+ the cross-org team that built it — governance says so plainly — with a growing test suite and a
+ security policy that tells you what not to do yet. The most useful contributions right
now: a new activity type, a second standards source, or pointing your tutor at it and telling us what
broke. Born at the CodeAI education hackathon, built by people from several education companies, and
licensed Apache-2.0 so it stays consumable by all of them, including the ones that compete.
Status: v0.1, young, honest about it. One primary maintainer, a growing test
- suite, security policy that tells you plainly what not to do yet. The most useful contributions right
+
Status: v0.1, young, honest about it. A maintainer group still forming from
+ the cross-org team that built it — governance says so plainly — with a growing test suite and a
+ security policy that tells you what not to do yet. The most useful contributions right
now: a new activity type, a second standards source, or pointing your tutor at it and telling us what
broke. Born at the CodeAI education hackathon, built by people from several education companies, and
licensed Apache-2.0 so it stays consumable by all of them, including the ones that compete.
From 2a0d814203743df37894e9a71cb076bf23c1ba4c Mon Sep 17 00:00:00 2001
From: Adam Stankiewicz
Date: Sun, 30 Aug 2026 15:45:55 -0400
Subject: [PATCH 12/14] docs: the semantic-discovery embedding knobs
Co-Authored-By: Claude Fable 5
---
docs/configuration.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/configuration.md b/docs/configuration.md
index 739b6bd2..ca57a5ad 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -9,6 +9,7 @@ key); everything else has a working default — no database, no accounts.
| `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `OPENROUTER_API_KEY` | — | The key for the chosen provider |
| `AWS_REGION`, `AWS_BEARER_TOKEN_BEDROCK` | `us-west-2`, — | Bedrock configuration when `LLM_PROVIDER=bedrock` |
| `OPENAI_FALLBACK_API_KEY`, `OPENAI_FALLBACK_MODEL_ID` | —, `gpt-4o-mini` | Optional second provider used when the primary fails |
+| `OPENROUTER_EMBED_MODEL_ID` / `BEDROCK_EMBED_MODEL_ID` | `openai/text-embedding-3-small` / `amazon.titan-embed-text-v2:0` | Embedding model for semantic activity discovery. Discovery ranks semantically when the provider can embed (OpenRouter, Bedrock, or an armed OpenAI fallback key) and falls back to lexical ranking otherwise — the result says which ran |
| `STANDARDS_SOURCE` | `learning-commons` | Which standards graph verifies codes; `example` is the built-in keyless source |
| `LEARNING_COMMONS_API_KEY`, `LEARNING_COMMONS_MCP_URL` | — | Credentials for the default standards source |
| `STORAGE_ADAPTER` | auto | `memory` (nothing persists across restarts) or `supabase`; unset picks Supabase only when its vars are configured |
From 880836dccf64a542dcca42786379abe5a23a4f2a Mon Sep 17 00:00:00 2001
From: Adam Stankiewicz
Date: Sun, 30 Aug 2026 15:52:33 -0400
Subject: [PATCH 13/14] docs: the tool reference documents audience, and the
alias it kept
Follows the rename downstack. `audience` is the documented argument; the
row for `gradeHint` stays so a reader of the deployed tool finds it and
learns it is an alias rather than guessing the doc is stale.
Co-Authored-By: Claude Opus 5 (1M context)
---
docs/mcp-tools.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/mcp-tools.md b/docs/mcp-tools.md
index a39b645a..3f21ce96 100644
--- a/docs/mcp-tools.md
+++ b/docs/mcp-tools.md
@@ -13,7 +13,8 @@ Builds one standards-verified activity and renders it in the host.
|---|---|---|
| `topic` | no* | What the activity is about, in plain words — "comparing fractions". Enough on its own. |
| `standardCode` | no* | A Common Core or NGSS code, if the caller already knows which one it wants. |
-| `gradeHint` | no | e.g. `"8th grade"` — narrows proposal and generation. |
+| `audience` | no | Who it is for, in plain words — `"8th grade"`, `"undergraduate intro stats"`, `"new hires"`. Narrows proposal and generation. A hint, not a verified claim. |
+| `gradeHint` | no | Deprecated alias for `audience`, still accepted: this tool shipped with it. |
| `kind` | no | One of the registry's kinds. Leave it out and the best interaction for the standard is chosen via `coverageRule` + planner metadata. |
*At least one of `topic` / `standardCode` in practice — a bare call has
From 4902df12df846d7050e577ded65c88405aa29ecf Mon Sep 17 00:00:00 2001
From: Adam Stankiewicz
Date: Sun, 30 Aug 2026 16:24:33 -0400
Subject: [PATCH 14/14] docs: the tool reference catches up with reality
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
audienceHint replaces the bare name in show_widget's table, matching
c3cacf3's boundary rule — audience stays reserved for the scheme-scoped,
verified manifest field. find_activity graduates out of the planned
section: it merged to main and answers on the production endpoint, so
documenting it as unshipped had become the dishonest direction. Its new
section states the ranking honesty contract (semantic vs lexical).
Co-Authored-By: Claude Fable 5
---
docs/mcp-tools.md | 28 ++++++++++++++++++++++------
1 file changed, 22 insertions(+), 6 deletions(-)
diff --git a/docs/mcp-tools.md b/docs/mcp-tools.md
index 3f21ce96..04226975 100644
--- a/docs/mcp-tools.md
+++ b/docs/mcp-tools.md
@@ -13,8 +13,8 @@ Builds one standards-verified activity and renders it in the host.
|---|---|---|
| `topic` | no* | What the activity is about, in plain words — "comparing fractions". Enough on its own. |
| `standardCode` | no* | A Common Core or NGSS code, if the caller already knows which one it wants. |
-| `audience` | no | Who it is for, in plain words — `"8th grade"`, `"undergraduate intro stats"`, `"new hires"`. Narrows proposal and generation. A hint, not a verified claim. |
-| `gradeHint` | no | Deprecated alias for `audience`, still accepted: this tool shipped with it. |
+| `audienceHint` | no | Who it is for, in plain words — `"8th grade"`, `"undergraduate intro stats"`, `"new hires"`. Narrows proposal and generation. The suffix is the contract: `audience` names the scheme-scoped, graph-verified field on emitted manifests, and this unverified steer never borrows that name. |
+| `gradeHint` | no | Deprecated alias for `audienceHint`, still accepted: this tool shipped with it. |
| `kind` | no | One of the registry's kinds. Leave it out and the best interaction for the standard is chosen via `coverageRule` + planner metadata. |
*At least one of `topic` / `standardCode` in practice — a bare call has
@@ -27,6 +27,26 @@ not a quietly wrong activity) → pick a kind → run that kind's
via the MCP Apps shell. When no code survives verification, the result says
so plainly and renders as an exploration activity.
+## `find_activity`
+
+Browses the registry before building: ranked, standards-verified activity
+listings for a learning need, each carrying the exact `show_widget`
+arguments that build it. Listings are generative — capabilities that
+manufacture an activity on demand, not a shelf of files.
+
+| Input | Required | Meaning |
+|---|---|---|
+| `topic` | no* | The learning need in plain words — "comparing fractions". |
+| `standardCode` | no* | A known code, verified against the standards graph before anything is listed. |
+| `audienceHint` | no | Who it is for, free text. Same contract as on `show_widget`. |
+| `need` | no | A preference in plain words — "a game", "something they write", "a quick check". Steers ranking. |
+
+*At least one of `topic` / `standardCode`.
+
+The result names its ranker honestly: `ranking: "semantic"` when the
+deployment can embed (see [configuration](./configuration.md)),
+`"lexical"` otherwise — discovery always answers, and never pretends.
+
## `build_pathway`
The full pipeline as one tool call (about half a minute): plans 4–6
@@ -85,10 +105,6 @@ hardening branch set).
## Planned surface (v0.1)
-- **`find_activity`** — answers a learning need with ranked,
- standards-verified listings derived from registry metadata; each listing
- carries the exact `show_widget` arguments that build it. Implemented on
- `feat/find-activity-mvp`, merging as part of v0.1.
- **`MCP_ACCESS_TOKEN`** — bearer auth for public instances, with a rate
cap.
- **`/api/v0/activities`** — a small, OpenAPI-documented REST facade