diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..066da3b0 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,59 @@ +# a2learn docs + +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 | +| [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 | + +**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. + +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/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..ca57a5ad --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,25 @@ +# 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 | +| `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 | +| `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/evidence.md b/docs/evidence.md new file mode 100644 index 00000000..990a621c --- /dev/null +++ b/docs/evidence.md @@ -0,0 +1,83 @@ +# The evidence contract + +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 + +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..20961553 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,514 @@ + + + + + +a2learn — the open activity server for teaching agents + + + + + + +
+
+ a2learn + docs · Apache-2.0 +
+ +
+

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
  • +
  • extensible activity registry
  • +
+
+ + + +
+

Or take a turn yourself (simulated — the real thing runs on the server)

+
+

Which point shows 1/3 on the number line?

+ +
+ + + +
+
+ + +
+ +

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

+
+
+ +

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.
  • +
  • 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:
  • +
+
    +
  • drag-to-order
  • fraction area model
  • writing workshop
  • debate an AI
  • +
  • + your kind · one file
  • +
+
    +
  • 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 (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 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.

+
+
+

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 + 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 itAdd 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 itOne 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 itThe SDK above, for tutors that need an action space and eyes on the + results.
Extend itAdd 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.
+
{ "mcpServers": { "a2learn": { "url": "https://your-instance.example/api/mcp" } } }
+ +

What we won't claim

+
+

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

+
+ +

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..04226975 --- /dev/null +++ b/docs/mcp-tools.md @@ -0,0 +1,112 @@ +# 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. | +| `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 +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. + +## `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 +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 +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) + +- **`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/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/docs/pedagogy.md b/docs/pedagogy.md new file mode 100644 index 00000000..67cca38b --- /dev/null +++ b/docs/pedagogy.md @@ -0,0 +1,130 @@ +# 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 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: + +- **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 new file mode 100644 index 00000000..7e661dc4 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,48 @@ +# 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. + +## 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 + +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. 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/public/open.html b/public/open.html new file mode 100644 index 00000000..20961553 --- /dev/null +++ b/public/open.html @@ -0,0 +1,514 @@ + + + + + +a2learn — the open activity server for teaching agents + + + + + + +
+
+ a2learn + docs · Apache-2.0 +
+ +
+

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
  • +
  • extensible activity registry
  • +
+
+ + + +
+

Or take a turn yourself (simulated — the real thing runs on the server)

+
+

Which point shows 1/3 on the number line?

+ +
+ + + +
+
+ + +
+ +

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

+
+
+ +

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.
  • +
  • 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:
  • +
+
    +
  • drag-to-order
  • fraction area model
  • writing workshop
  • debate an AI
  • +
  • + your kind · one file
  • +
+
    +
  • 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 (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 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.

+
+
+

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 + 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 itAdd 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 itOne 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 itThe SDK above, for tutors that need an action space and eyes on the + results.
Extend itAdd 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.
+
{ "mcpServers": { "a2learn": { "url": "https://your-instance.example/api/mcp" } } }
+ +

What we won't claim

+
+

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

+
+ +

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..dc2d3ebf --- /dev/null +++ b/src/app/docs/[[...slug]]/page.tsx @@ -0,0 +1,224 @@ +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', 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' }, + messaging: { file: 'messaging.md', title: 'Messaging guide', nav: 'Messaging', section: 'Project' }, +}; + +const SECTIONS = ['Learn', 'Guides', 'Reference', 'Project']; + +/** + * 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 ( +
+
+
+ + a2learn docs + + + About → + +
+
+ +
+ + +
+ {/* Small screens get the nav as a wrapped row instead of a rail. */} + + ( +

{children}

+ ), + h2: ({ children }) => ( +

+ {children} +

+ ), + h3: ({ children }) =>

{children}

, + p: ({ children }) =>

{children}

, + ul: ({ children }) =>
    {children}
, + ol: ({ children }) =>
    {children}
, + li: ({ children }) =>
  • {children}
  • , + a: ({ href, children }) => { + const target = rewriteHref(href ?? ''); + const external = /^https?:/.test(target); + return external ? ( + + {children} + + ) : ( + + {children} + + ); + }, + code: ({ children, className }) => + className ? ( + // Block code (```lang) — the wrapping
     handles the box.
    +                {children}
    +              ) : (
    +                
    +                  {children}
    +                
    +              ),
    +            pre: ({ children }) => (
    +              
    +                {children}
    +              
    + ), + blockquote: ({ children }) => ( +
    + {children} +
    + ), + table: ({ children }) => ( +
    + {children}
    +
    + ), + th: ({ children }) => ( + + {children} + + ), + td: ({ children }) => ( + {children} + ), + }} + > + {markdown} + +
    +
    +
    + ); +}