From a1ece4fb503dfa81ef04ed7599633342a0148b5c Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Fri, 21 Aug 2026 19:54:16 -0400 Subject: [PATCH 1/4] =?UTF-8?q?docs:=20a2learn=20draft=20v0=20=E2=80=94=20?= =?UTF-8?q?a=20spec,=20deliberately=20not=20a=20protocol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three sketch documents defining the agent-to-learning format as data riding existing protocols: surface (a profile of A2UI — flat components, bindTo references, the WidgetResult contract, namespaced learning extensions that plain A2UI renderers safely ignore), manifest (the registry/marketplace listing: verified standards alignment, pedagogy metadata, two-tier content/code trust split, provenance with reviewer attestations), and events (payload shapes with two transport bindings, AG-UI and MCP Apps, plus the normative two-tier interaction rule). Every section is tagged Shipped / Prototype / Planned so the spec formalizes reality rather than aspiration. Prose only — no schemas, no validator, no src/ changes. Co-Authored-By: Claude Fable 5 --- docs/a2learn/README.md | 64 +++++++++++++++++++++++++++ docs/a2learn/events.md | 70 +++++++++++++++++++++++++++++ docs/a2learn/manifest.md | 95 +++++++++++++++++++++++++++++++++++++++ docs/a2learn/surface.md | 96 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 325 insertions(+) create mode 100644 docs/a2learn/README.md create mode 100644 docs/a2learn/events.md create mode 100644 docs/a2learn/manifest.md create mode 100644 docs/a2learn/surface.md diff --git a/docs/a2learn/README.md b/docs/a2learn/README.md new file mode 100644 index 00000000..00bd6e1c --- /dev/null +++ b/docs/a2learn/README.md @@ -0,0 +1,64 @@ +# a2learn — draft v0 (experimental) + +**a2learn** ("agent-to-learning") is a specification for **agent-generated, +agent-discoverable interactive learning activities**. It defines *data*, not +wire behavior: what a learning activity **is** (its surface), how it is +**described for discovery** (its manifest), and what its **interactions +report** (its events). Transport, rendering, and distribution are borrowed +from specifications that already exist and already have hosts: + +| Layer | Provided by | a2learn's relationship | +| --- | --- | --- | +| Surface description | [A2UI](https://a2ui.org) | a2learn-surface is a *profile*: valid A2UI plus namespaced learning extensions | +| Agent ↔ UI transport | [AG-UI](https://github.com/ag-ui-protocol/ag-ui) | a2learn payloads ride AG-UI events unchanged | +| Distribution into chat hosts | [MCP Apps](https://modelcontextprotocol.io) | activities are invokable MCP tools; surfaces render in MCP Apps iframes | + +This is a deliberate scope choice: **a2learn is a spec, not a protocol.** New +protocols require every host to implement wire behavior; formats spread by +emit-and-validate. The one part with protocol flavor — the event vocabulary — +is specified as payload shapes with *bindings* to AG-UI and MCP, never as a +new wire protocol. + +## The three documents + +1. [`surface.md`](./surface.md) — the activity itself: an A2UI-conformant + surface carrying learning semantics (state contract, gating, language). +2. [`manifest.md`](./manifest.md) — the listing: standards-verified, + pedagogy-aware discovery metadata an agent can query at runtime. This is + the registry/marketplace's format. +3. [`events.md`](./events.md) — what interactions report: result and action + payloads, with two transport bindings. + +## Design principles + +- **Data, never code.** An activity is renderable data resolved against a + host's component catalog. Model output never ships executable logic; the + trust boundary is the catalog. (Marketplace corollary: activity *instances* + are open data; new component *kinds* are code and travel a separate, + curated channel.) +- **Generation-shaped.** Structured output reliably fails on nested unions + and wide flat objects. The spec's wire shapes may be rich; *authoring* + representations stay flat, with mapping at the boundary. References are + plain strings (`bindTo`), never expression languages. +- **Standards-anchored, verifiably.** Manifest alignment claims name their + source and verification status. Unverified claims are representable but + distinguishable — an index can rank on it. +- **Degrade gracefully.** A plain A2UI renderer that ignores every a2learn + extension still renders a usable surface. Extensions add semantics; they + must not be load-bearing for basic rendering. + +## Status + +Draft v0, reference-implementation-led: this repository is the reference +implementation, and the spec documents what ships (or is explicitly marked +**Planned**). Breaking changes are allowed and changelogged throughout 0.x. +The spec formalizes reality, never aspiration — see the format-strategy +lesson: implementation first (Jupyter, glTF, Markdown), spec-first is how +formats fragment (QTI). + +Each section is tagged with an implementation status: + +- **Shipped** — on `main`, exercised by the app. +- **Prototype** — working in the composition prototype (`/demo/ag-ui-compose` + lineage), not yet in the core product path. +- **Planned** — designed, not yet built; most likely to change. diff --git a/docs/a2learn/events.md b/docs/a2learn/events.md new file mode 100644 index 00000000..bca1367c --- /dev/null +++ b/docs/a2learn/events.md @@ -0,0 +1,70 @@ +# a2learn-events — draft v0 + +What interactions report back to the agent side. These are **payload +shapes with transport bindings**, not a wire protocol: a2learn defines the +JSON; AG-UI and MCP define how it moves. This is the smallest of the three +documents on purpose — most interaction never leaves the client. + +## The two-tier interaction rule *(normative, Prototype)* + +- **Local tier** — clicks, drags, reveals, in-game moves resolve entirely in + the renderer at zero latency and zero tokens. They are never events. +- **Event tier** — pedagogy-changing moments reach the agent: a result was + produced, help was requested, an author steered. Only these payloads + exist in this spec. + +A conforming host MUST NOT forward local-tier interaction to a model by +default; chatty surfaces would otherwise be a cost and privacy hazard. + +## Payloads *(Planned; the debate-ai and writing-workshop widgets ship +working precedents of the loop today)* + +```json +{ "type": "widget_result", + "surfaceId": "…", "componentId": "quiz-1", + "result": { "status": "complete", "correct": false, "score": null, "attempts": 1 } } + +{ "type": "action", + "surfaceId": "…", "componentId": "help-button", + "name": "help_requested", "context": { "step": 3 } } + +{ "type": "author_edit", + "surfaceId": "…", "componentId": "intro-text", + "op": "set_text", "payload": { "text": "…" } } +``` + +`widget_result` carries the same `WidgetResult` contract the surface's data +model uses ([`surface.md`](./surface.md)) — one shape from interaction to +data model to event to report. A `widget_result` with `correct: false` is +the canonical remediation trigger: the agent answers on the same stream +with a `surfaceUpdate` adding a gated remediation section. + +## Binding 1 — AG-UI *(Prototype)* + +Native hosts run activities over an AG-UI event stream. Downstream, surfaces +and progress ride standard events (`RUN_STARTED`, `CUSTOM` carrying +surface messages, `STATE_DELTA` for data-model patches, `RUN_FINISHED`). +Upstream, a2learn payloads post to the host's run endpoint and appear to +the agent as structured input — never as prompt text. + +## Binding 2 — MCP Apps *(partially Shipped)* + +Chat-embedded activities run in MCP Apps iframes and report through the +`ui/*` channel: + +- `ui/update-model-context` — silent context: results the agent should know + without a visible turn *(Shipped for three widgets)*. +- UI-initiated messages — events that should *trigger* an agent turn + (help requests). +- Host-proxied tool calls — consent-gated in third-party hosts. + +The host owns the policy map from payload type → conversation behavior +(silent context · visible turn · tool call). A first-party host may add +richer channels (identity, first-party telemetry); the payloads stay +identical. + +## Untrusted-content rule *(normative)* + +Event payloads and surface context can contain learner-authored text. Hosts +MUST label it as untrusted content when it enters a model context — it is +data about the learner, never instructions to the agent. diff --git a/docs/a2learn/manifest.md b/docs/a2learn/manifest.md new file mode 100644 index 00000000..782e33bb --- /dev/null +++ b/docs/a2learn/manifest.md @@ -0,0 +1,95 @@ +# a2learn-manifest — draft v0 + +The **manifest** is an activity's listing: the metadata that lets an *agent* +— a tutor bot, a lesson planner, a chat assistant — discover the right +activity at runtime and decide whether to invoke it. It is the registry/ +marketplace's native format, and the most original part of a2learn: UI specs +describe how activities render; nothing else describes what they *teach*, +in machine-checkable terms. + +All of this section is **Planned** unless noted; the field vocabulary is +drawn from contracts that ship today (the widget registry's +`assesses`/`completion`/coverage metadata *(Shipped)* and the standards +verification pipeline *(Shipped)*). + +## Example + +```json +{ + "a2learn": 0, + "id": "cloud-factory-floor", + "kind": "activity", + "title": "Cloud Factory Floor", + "summary": "A solo-clear board game: claim every station on the factory floor by answering water-cycle questions.", + "tier": "content", + + "standards": [ + { "code": "5-ESS2-1", "source": "learning-commons", "verified": true } + ], + "gradeBand": { "min": 4, "max": 6 }, + + "pedagogy": { + "assesses": true, + "completion": "signal", + "mechanics": ["game-board", "single-choice"], + "purposes": ["practice", "check"] + }, + + "delivery": { + "surface": "https://…/activities/cloud-factory-floor/surface.json", + "mcp": { "server": "https://…/api/mcp", "tool": "show_activity" }, + "catalogRequires": ["GameBoard", "Counter", "Stack", "Card"] + }, + + "lang": ["en"], + "a11y": { "keyboard": true, "reducedMotion": true, "screenReader": "partial" }, + + "provenance": { + "publisher": "…", + "license": "CC-BY-4.0", + "generated": true, + "reviewed": [] + } +} +``` + +## Field groups + +- **Identity** — `id`, `kind` (`activity` | `pathway` — a pathway manifest + lists an ordered `sequence` of activity references with gating rules), + `title`, `summary`, `tier`. +- **Alignment** — `standards[]` with `source` and `verified`. Verification + means the code resolved against a real standards graph (the reference + implementation verifies against Learning Commons). Unverified claims are + legal but rank differently; an index MUST NOT present them as verified. +- **Pedagogy** — `assesses` (does completing it measure correctness — a + "check" step may only use assessing activities), `completion` + (`internal-cta` | `signal` | `none`), `mechanics`, `purposes` + (activate | model | practice | check). +- **Delivery** — where the surface lives and how to invoke it. An activity + is invokable if the consuming host can satisfy `catalogRequires` (native + rendering) *or* via the listed MCP server (zero-catalog rendering in any + MCP Apps host). +- **Access** — `lang` (BCP-47), `a11y` declarations. Declarations, not + certifications; a trust layer (below) can attest them. +- **Provenance** — publisher, license, whether content was AI-generated, + and `reviewed[]`: attestations by named reviewers/organizations. The + trust layer is *structurally* part of the manifest but *institutionally* + separate — a registry records attestations; it does not grant them. + +## Two tiers *(normative)* + +- `tier: "content"` — the activity is pure data rendered by a host's + existing catalog. Open publication. +- `tier: "code"` — the listing introduces new catalog components + (executable). Curated channel only: signed publishers and review gates. + A registry MUST NOT serve code-tier listings as if they were content-tier. + +## Discovery *(sketch)* + +The query surface is deliberately specified as MCP tools rather than a new +API style — e.g. `find_activity({ standard, gradeBand, mechanics, lang, +assesses })` returning ranked manifests. The reference implementation's +existing `show_widget(standardCode, kind)` *(Shipped)* is the invocation +half; `find_activity` over a manifest index is the MVP of the discovery +half. diff --git a/docs/a2learn/surface.md b/docs/a2learn/surface.md new file mode 100644 index 00000000..7f5ae0d6 --- /dev/null +++ b/docs/a2learn/surface.md @@ -0,0 +1,96 @@ +# a2learn-surface — draft v0 + +An **activity surface** is what a learner sees and touches: a declarative, +renderable description resolved against a host's component catalog. An +a2learn surface is **valid A2UI** — flat component list, id references, +shared data model — plus learning semantics carried in a namespaced +extension member, so any A2UI renderer displays the surface and simply +ignores what it does not understand. + +## Base shape *(Prototype)* + +```json +{ + "version": 0, + "root": "root-stack", + "components": [ + { + "id": "root-stack", + "component": "Stack", + "props": { "direction": "column", "gap": "lg" }, + "children": ["intro-card", "game", "score"] + }, + { + "id": "game", + "component": "GameBoard", + "props": { "boardSize": "3x3", "gameMode": "solo-clear", "questions": ["…"] } + }, + { + "id": "score", + "component": "Counter", + "props": { "counterLabel": "Clouds collected" }, + "bindTo": "game" + } + ], + "dataModel": {}, + "a2learn": { "lang": "en", "theme": { "hue": 210, "name": "Cloud Factory Floor" } } +} +``` + +- `components` is a **flat list with id references**, never a nested tree — + partial lists stream progressively and are always renderable. +- `component` names resolve against the rendering host's **catalog**. The + catalog is the trust boundary: props are validated against the entry's + schema, unknown names degrade to a visible notice, and no surface can + introduce code. +- Updates are **merge-by-id** (`surfaceUpdate` semantics from A2UI): partial + component lists merge over the current surface; there is deliberately no + operation-based patch DSL. + +## State: reference, not expression *(Prototype)* + +Every stateful component **writes one value keyed by its own id** into the +shared data model. Anything reactive names that id in a plain string: + +- `bindTo: "game"` — read another component's value (a Counter bound to a + GameBoard is a live scoreboard). +- `visibleWhen: { "path": "quiz-1.correct", "equals": true }` — gate a + component on another's state. This is the entire progression mechanism. + +Generating agents never author expressions, computed bindings, or pointer +syntax — a reference graph of strings is both safer and dramatically more +reliable to generate. + +## The result contract *(Planned)* + +Stateful learning components write a `WidgetResult` shape so completion and +correctness mean the same thing across every component, catalog, and host: + +```json +{ "status": "complete", "correct": true, "score": 400, "attempts": 2 } +``` + +This single convention is what connects surfaces to sequencing (gates read +it), reporting (snapshots aggregate it), and the event vocabulary +([`events.md`](./events.md) carries it to agents). + +## Learning extensions (`a2learn` member) + +- `lang` *(Planned)* — BCP-47 language of the content; may also appear + per-component for mixed-language activities. +- `theme` *(Prototype)* — a single hue (0–360) plus an in-world name; the + renderer derives a full palette at fixed lightness/chroma so any + model-chosen hue keeps contrast. Agents author intent, never raw colors. +- `sequence` *(Planned)* — for multi-activity artifacts (pathways): ordered + activity references with gating rules over `WidgetResult` state. This is + the layer no existing UI spec carries — see `manifest.md` for how + sequences are listed. + +## Accessibility ground rules *(Planned)* + +Composed surfaces must maintain heading order and landmarks, honor reduced +motion, and announce injected content (e.g. remediation arriving +mid-session) via live regions. Interaction accessibility (keyboard, +focus, ARIA state) is a property of catalog components, not of surfaces — +hosts certify their catalogs; surfaces cannot break what the catalog +guarantees. From 39aab57200c0b9aa06f444661c9d32d15b8a7d6c Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Fri, 21 Aug 2026 19:59:59 -0400 Subject: [PATCH 2/4] =?UTF-8?q?docs:=20harden=20a2learn=20to=20spec=20disc?= =?UTF-8?q?ipline=20=E2=80=94=20conformance,=20considerations,=20changelog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The precision pass, not a feature pass. conformance.md makes conformance claimable: four classes (Producer, Renderer, Registry, Host) with RFC 2119 requirements distilled from statements previously scattered as narrative, plus versioning/extension rules and a public 1.0 gate (two independent Renderer implementations + fixtures + a two-month stability window). considerations.md gives the security/privacy/a11y sections an education spec owes its adopters first — the catalog trust boundary as invariant, the two-tier threat-model split, both prompt-injection doors, and the WidgetResult-is-an-education-record division of FERPA-shaped responsibility (content and people never in the same artifact). CHANGELOG.md starts the breaking-change ledger. README gains the RFC 2119 conventions note. Still prose only. Co-Authored-By: Claude Fable 5 --- docs/a2learn/CHANGELOG.md | 19 +++++++ docs/a2learn/README.md | 16 +++++- docs/a2learn/conformance.md | 99 ++++++++++++++++++++++++++++++++++ docs/a2learn/considerations.md | 65 ++++++++++++++++++++++ 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 docs/a2learn/CHANGELOG.md create mode 100644 docs/a2learn/conformance.md create mode 100644 docs/a2learn/considerations.md diff --git a/docs/a2learn/CHANGELOG.md b/docs/a2learn/CHANGELOG.md new file mode 100644 index 00000000..2f2cc2b1 --- /dev/null +++ b/docs/a2learn/CHANGELOG.md @@ -0,0 +1,19 @@ +# a2learn spec changelog + +Breaking changes are allowed throughout v0 and are recorded here. Entries +say what changed *and what an implementer must do about it*. + +## v0 draft — 2026-08-21 + +Initial draft: README (scope: a spec riding AG-UI/A2UI/MCP, not a +protocol), `surface.md` (A2UI profile: flat components, `bindTo` +references, `WidgetResult` contract, namespaced `a2learn` extensions), +`manifest.md` (discovery/listing format: verified standards alignment, +pedagogy metadata, content/code tiers, provenance + attestations), +`events.md` (payload shapes with AG-UI and MCP Apps bindings; two-tier +interaction rule), `conformance.md` (Producer / Renderer / Registry / Host +classes; versioning and extension rules; the 1.0 gate), and +`considerations.md` (security, privacy, accessibility). + +Reference implementation: this repository. Status tags (Shipped / +Prototype / Planned) mark which sections are exercised by running code. diff --git a/docs/a2learn/README.md b/docs/a2learn/README.md index 00bd6e1c..792b66ff 100644 --- a/docs/a2learn/README.md +++ b/docs/a2learn/README.md @@ -19,7 +19,7 @@ emit-and-validate. The one part with protocol flavor — the event vocabulary is specified as payload shapes with *bindings* to AG-UI and MCP, never as a new wire protocol. -## The three documents +## The documents 1. [`surface.md`](./surface.md) — the activity itself: an A2UI-conformant surface carrying learning semantics (state contract, gating, language). @@ -28,6 +28,20 @@ new wire protocol. the registry/marketplace's format. 3. [`events.md`](./events.md) — what interactions report: result and action payloads, with two transport bindings. +4. [`conformance.md`](./conformance.md) — conformance classes (Producer, + Renderer, Registry, Host), versioning and extension rules, and the gate + for 1.0. +5. [`considerations.md`](./considerations.md) — security, privacy + (education records, minors), and accessibility considerations. +6. [`CHANGELOG.md`](./CHANGELOG.md) — every breaking change, with what an + implementer must do about it. + +## Conventions + +The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and +**MAY** are to be interpreted per RFC 2119; text without them is +informative. Conformance is always claimed against a class in +[`conformance.md`](./conformance.md), never against "a2learn" in general. ## Design principles diff --git a/docs/a2learn/conformance.md b/docs/a2learn/conformance.md new file mode 100644 index 00000000..72179553 --- /dev/null +++ b/docs/a2learn/conformance.md @@ -0,0 +1,99 @@ +# a2learn conformance — draft v0 + +The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and +**MAY** are to be interpreted as described in +[RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). Statements without +these keywords are informative. + +Nothing conforms to "a2learn" in general; implementations conform to one or +more **conformance classes**. This is what makes conformance claimable and +eventually testable. + +## Classes + +### Producer + +Emits surfaces and/or manifests — a generation pipeline, an authoring tool, +an exporter. + +- MUST emit surfaces whose base shape is valid A2UI (flat component list, + id references) and whose learning semantics live only in the namespaced + `a2learn` member. +- MUST NOT emit executable code in any surface or manifest. +- MUST ensure referential integrity before publishing: every `children`, + `bindTo`, and gate path names an id that exists. +- MUST NOT mark a standards claim `verified: true` unless the code resolved + against the named source. +- SHOULD keep authoring-side generation shapes flat (see README design + principles); the wire shape is what this spec governs. + +### Renderer + +Consumes surfaces and presents them to a learner. + +- MUST resolve `component` names only against a catalog under its control; + unknown names MUST degrade to a visible notice, never code execution or a + crash. +- MUST validate props against the catalog entry before rendering. +- MUST ignore unknown members of the `a2learn` extension (render the + surface as if they were absent) and MUST NOT fail on them. +- MUST apply surface updates by id-merge; a renderer MAY reject an update + whose `version` it does not support. +- SHOULD render partial component lists progressively during streaming. +- MUST implement the local/event interaction tiers per + [`events.md`](./events.md): local-tier interaction MUST NOT be forwarded + to a model by default. + +### Registry + +Indexes manifests and answers discovery queries. + +- MUST NOT present unverified standards claims as verified, in query + results or listings. +- MUST NOT serve `tier: "code"` listings through a content-tier channel; + code-tier distribution requires the curated path (signed publishers, + review). +- MUST preserve manifest fields it does not understand (round-tripping), + and MUST record — not grant — reviewer attestations. +- SHOULD rank or filter on `verified`, `a11y`, and `reviewed` so consuming + agents can express trust requirements. +- MUST NOT include learner data in manifests or query responses; a registry + indexes content, never people. + +### Host + +Runs the event bindings between an activity and an agent (a native app, an +MCP Apps host, a first-party embedded client). + +- MUST deliver event-tier payloads to the agent side as structured data, + never by interpolating them into prompt text as instructions. +- MUST label learner-authored text entering a model context as untrusted + content. +- MUST own an explicit policy map from payload type to conversation + behavior (silent context · visible turn · tool call) and SHOULD apply + rate limits so a chatty surface cannot storm a model. +- MAY extend the binding with richer channels (identity, telemetry); the + payload shapes MUST remain as specified. + +## Versioning + +Surfaces and manifests carry an integer `version` / `a2learn` field. +During v0, breaking changes are allowed and are recorded in +[`CHANGELOG.md`](./CHANGELOG.md); implementations SHOULD pin to the version +they were built against and MUST NOT silently reinterpret a document with a +higher version. + +## Extension rule + +Third parties MAY add fields under their own namespace prefix (`x-` or a +reverse-DNS key). Producers and registries MUST preserve such fields; +renderers MUST ignore the ones they do not understand. The `a2learn` +namespace itself is reserved for this specification. + +## Path to 1.0 + +v1.0 is gated on: at least **two independent implementations** of the +Renderer class, machine-readable schemas plus a conformance fixture suite, +and a window of **two months without breaking changes**. The spec +formalizes what implementations ship — a requirement no implementation +exercises is a candidate for removal before 1.0, not a reason to build. diff --git a/docs/a2learn/considerations.md b/docs/a2learn/considerations.md new file mode 100644 index 00000000..35866442 --- /dev/null +++ b/docs/a2learn/considerations.md @@ -0,0 +1,65 @@ +# a2learn — security, privacy, and accessibility considerations (draft v0) + +Informative unless marked with RFC 2119 keywords. This spec targets +learners who are often children; these sections are not boilerplate. + +## Security considerations + +**The catalog is the trust boundary.** Activities are data resolved against +host-controlled catalogs; nothing in a conforming pipeline lets a surface +introduce executable code (see Producer/Renderer requirements in +[`conformance.md`](./conformance.md)). Any implementation change that lets +model- or publisher-authored content reach the page by another route is a +vulnerability, not a feature request. + +**Two-tier distribution exists because the tiers have different threat +models.** Content-tier listings are inert data; the worst malicious content +can do is be pedagogically bad or offensive — a moderation problem. +Code-tier listings are a software supply chain aimed at classrooms — +extension-store rules apply (signed publishers, review before listing, +revocation). Registries MUST keep the tiers distinguishable end to end. + +**Prompt injection has two doors here.** (1) Manifest text (titles, +summaries, catalog descriptions) is injected into composer/agent prompts by +consuming systems — registries SHOULD bound length and character repertoire, +and consumers SHOULD treat manifest text as content, not instructions. +(2) Learner-authored text travels in event payloads and surface context — +hosts MUST label it untrusted when it enters a model context. + +**Embedded rendering** inherits the MCP Apps sandbox model (iframes, opaque +origins, host-mediated messaging). Hosts SHOULD NOT weaken that sandbox for +content-tier activities. + +## Privacy considerations + +**A `WidgetResult` stream is an education record.** Aggregated results and +data-model snapshots constitute student performance data; deployments in +US K-12 contexts inherit FERPA/COPPA-shaped obligations, and equivalents +elsewhere. The spec's division of responsibility: + +- **Manifests and surfaces MUST NOT contain learner data.** Content and + people never travel in the same artifact; registries index only content. +- **Event payloads SHOULD be data-minimal.** `detail` payloads SHOULD NOT + carry direct identifiers; identity binding is the host's job, at the + transport layer, under its own compliance regime. +- **Persistence is the deployer's obligation**, not the format's: hosts + that store results own retention, deletion, and access rights. The + format's contribution is separability — learner-keyed state is + structurally distinct from content, so deletion can cascade cleanly. + +**Generated content in front of minors.** `provenance.generated` exists so +consuming systems can require review gates for AI-generated activities. +Hosts serving minors SHOULD run a content-safety pass on generated +surfaces before first exposure; the reviewer-attestation mechanism lets a +trust layer make that check portable. + +## Accessibility considerations + +Interaction accessibility (keyboard operability, focus, ARIA state) is a +property of catalog components; structural accessibility (heading order, +landmarks, reduced motion, live-region announcements for injected content) +is a property of surfaces. Manifest `a11y` fields are **declarations** — +useful for filtering, honest only if attested. A trust layer MAY certify +them; a registry MUST NOT convert declarations into certifications on its +own authority. Language access is first-class: `lang` is required in +manifests so agents can filter honestly rather than assume English. From 0e99a15faa0c972d2187ab1b96263d05d5a4f5d5 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Sun, 30 Aug 2026 14:40:00 -0400 Subject: [PATCH 3/4] docs: the format calls itself a format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docs predate the labels-are-earned rule and said 'spec' throughout while the PR body and landing promise 'a format, not a specification — it earns the second word when a second implementation exists.' Now the README states that rule itself, and every self-reference says format. References to other projects' specifications (A2UI, AG-UI) stay as they are — those earned the word. Co-Authored-By: Claude Fable 5 --- docs/a2learn/CHANGELOG.md | 4 ++-- docs/a2learn/README.md | 17 +++++++++-------- docs/a2learn/conformance.md | 6 +++--- docs/a2learn/considerations.md | 4 ++-- docs/a2learn/events.md | 2 +- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/a2learn/CHANGELOG.md b/docs/a2learn/CHANGELOG.md index 2f2cc2b1..5b704e70 100644 --- a/docs/a2learn/CHANGELOG.md +++ b/docs/a2learn/CHANGELOG.md @@ -1,11 +1,11 @@ -# a2learn spec changelog +# a2learn format changelog Breaking changes are allowed throughout v0 and are recorded here. Entries say what changed *and what an implementer must do about it*. ## v0 draft — 2026-08-21 -Initial draft: README (scope: a spec riding AG-UI/A2UI/MCP, not a +Initial draft: README (scope: a format riding AG-UI/A2UI/MCP, not a protocol), `surface.md` (A2UI profile: flat components, `bindTo` references, `WidgetResult` contract, namespaced `a2learn` extensions), `manifest.md` (discovery/listing format: verified standards alignment, diff --git a/docs/a2learn/README.md b/docs/a2learn/README.md index 792b66ff..aaa5da6a 100644 --- a/docs/a2learn/README.md +++ b/docs/a2learn/README.md @@ -1,6 +1,6 @@ # a2learn — draft v0 (experimental) -**a2learn** ("agent-to-learning") is a specification for **agent-generated, +**a2learn** ("agent-to-learning") is a document format for **agent-generated, agent-discoverable interactive learning activities**. It defines *data*, not wire behavior: what a learning activity **is** (its surface), how it is **described for discovery** (its manifest), and what its **interactions @@ -13,9 +13,10 @@ from specifications that already exist and already have hosts: | Agent ↔ UI transport | [AG-UI](https://github.com/ag-ui-protocol/ag-ui) | a2learn payloads ride AG-UI events unchanged | | Distribution into chat hosts | [MCP Apps](https://modelcontextprotocol.io) | activities are invokable MCP tools; surfaces render in MCP Apps iframes | -This is a deliberate scope choice: **a2learn is a spec, not a protocol.** New -protocols require every host to implement wire behavior; formats spread by -emit-and-validate. The one part with protocol flavor — the event vocabulary — +This is a deliberate scope choice: **a2learn is a format, not a protocol** — +and we say *format*, not *specification*, on purpose: it earns the second +word when a second independent implementation exists. New protocols require +every host to implement wire behavior; formats spread by emit-and-validate. The one part with protocol flavor — the event vocabulary — is specified as payload shapes with *bindings* to AG-UI and MCP, never as a new wire protocol. @@ -51,7 +52,7 @@ informative. Conformance is always claimed against a class in are open data; new component *kinds* are code and travel a separate, curated channel.) - **Generation-shaped.** Structured output reliably fails on nested unions - and wide flat objects. The spec's wire shapes may be rich; *authoring* + and wide flat objects. The format's wire shapes may be rich; *authoring* representations stay flat, with mapping at the boundary. References are plain strings (`bindTo`), never expression languages. - **Standards-anchored, verifiably.** Manifest alignment claims name their @@ -64,10 +65,10 @@ informative. Conformance is always claimed against a class in ## Status Draft v0, reference-implementation-led: this repository is the reference -implementation, and the spec documents what ships (or is explicitly marked +implementation, and the format documents what ships (or is explicitly marked **Planned**). Breaking changes are allowed and changelogged throughout 0.x. -The spec formalizes reality, never aspiration — see the format-strategy -lesson: implementation first (Jupyter, glTF, Markdown), spec-first is how +The format formalizes reality, never aspiration — see the format-strategy +lesson: implementation first (Jupyter, glTF, Markdown), standards-first is how formats fragment (QTI). Each section is tagged with an implementation status: diff --git a/docs/a2learn/conformance.md b/docs/a2learn/conformance.md index 72179553..33f669f5 100644 --- a/docs/a2learn/conformance.md +++ b/docs/a2learn/conformance.md @@ -25,7 +25,7 @@ an exporter. - MUST NOT mark a standards claim `verified: true` unless the code resolved against the named source. - SHOULD keep authoring-side generation shapes flat (see README design - principles); the wire shape is what this spec governs. + principles); the wire shape is what this format governs. ### Renderer @@ -88,12 +88,12 @@ higher version. Third parties MAY add fields under their own namespace prefix (`x-` or a reverse-DNS key). Producers and registries MUST preserve such fields; renderers MUST ignore the ones they do not understand. The `a2learn` -namespace itself is reserved for this specification. +namespace itself is reserved for this formatification. ## Path to 1.0 v1.0 is gated on: at least **two independent implementations** of the Renderer class, machine-readable schemas plus a conformance fixture suite, -and a window of **two months without breaking changes**. The spec +and a window of **two months without breaking changes**. The format formalizes what implementations ship — a requirement no implementation exercises is a candidate for removal before 1.0, not a reason to build. diff --git a/docs/a2learn/considerations.md b/docs/a2learn/considerations.md index 35866442..059b2b05 100644 --- a/docs/a2learn/considerations.md +++ b/docs/a2learn/considerations.md @@ -1,6 +1,6 @@ # a2learn — security, privacy, and accessibility considerations (draft v0) -Informative unless marked with RFC 2119 keywords. This spec targets +Informative unless marked with RFC 2119 keywords. This format targets learners who are often children; these sections are not boilerplate. ## Security considerations @@ -35,7 +35,7 @@ content-tier activities. **A `WidgetResult` stream is an education record.** Aggregated results and data-model snapshots constitute student performance data; deployments in US K-12 contexts inherit FERPA/COPPA-shaped obligations, and equivalents -elsewhere. The spec's division of responsibility: +elsewhere. The format's division of responsibility: - **Manifests and surfaces MUST NOT contain learner data.** Content and people never travel in the same artifact; registries index only content. diff --git a/docs/a2learn/events.md b/docs/a2learn/events.md index bca1367c..b2de9445 100644 --- a/docs/a2learn/events.md +++ b/docs/a2learn/events.md @@ -11,7 +11,7 @@ documents on purpose — most interaction never leaves the client. the renderer at zero latency and zero tokens. They are never events. - **Event tier** — pedagogy-changing moments reach the agent: a result was produced, help was requested, an author steered. Only these payloads - exist in this spec. + exist in this format. A conforming host MUST NOT forward local-tier interaction to a model by default; chatty surfaces would otherwise be a cost and privacy hazard. From 6896896cae221ef66e33c8921bf56e0c6a50297e Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Sun, 30 Aug 2026 15:44:19 -0400 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20audience=20replaces=20gradeBand=20?= =?UTF-8?q?=E2=80=94=20the=20listing=20field=20survives=20leaving=20K-12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `gradeBand: {min, max}` was normative and numeric, so it could only ever describe K-12 grade levels; an undergraduate year or a new-hire cohort has no honest value to put in it. `audience` is a list of scheme-scoped label sets, which is the shape `standards[]` already uses — the naming authority travels with the value, so `"4"` is unambiguous once `scheme` says which system named it. Safe because nothing consumes the field: the reference implementation's find_activity writes gradeBand into the manifest and never reads it back, and its ranking scores plannerDescription and assess-anchors, not audience. No producer outside this repository emits it, so the change is a rename in an unmerged draft rather than a break for anyone. Only `k12-us` is registered. Higher-ed and professional schemes are structurally supported but deliberately unnamed — the format documents what a producer emits today, and none emits them yet. `purposes` is stated as an open vocabulary in the same pass, so the enum that would need a new beat for workplace practice isn't frozen shut. Co-Authored-By: Claude Opus 5 (1M context) --- docs/a2learn/CHANGELOG.md | 20 ++++++++++++++++++ docs/a2learn/manifest.md | 44 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/docs/a2learn/CHANGELOG.md b/docs/a2learn/CHANGELOG.md index 5b704e70..9a3378e8 100644 --- a/docs/a2learn/CHANGELOG.md +++ b/docs/a2learn/CHANGELOG.md @@ -3,6 +3,26 @@ Breaking changes are allowed throughout v0 and are recorded here. Entries say what changed *and what an implementer must do about it*. +## v0 draft — 2026-08-30 + +**`manifest.gradeBand` → `manifest.audience` (breaking).** The old field was +a `{min, max}` integer range, which can only express K-12 grade levels: an +undergraduate year or a new-hire cohort has no honest representation in it. +`audience` is a list of scheme-scoped label sets, carrying the naming +authority with the value the way `standards[]` already carries `source` +alongside `code`. + +*What an implementer must do:* replace `gradeBand: { min, max }` with an +`audience` entry under scheme `k12-us`, values as strings (`"K"`, `"1"`– +`"12"`). A consumer filtering on grade moves from range comparison to label +matching within a recognized scheme, and MUST treat an unrecognized scheme +as opaque rather than parsing it. No producer outside this repository emits +`gradeBand`, so no migration window is provided. + +**`manifest.pedagogy.purposes` is an open vocabulary**, stated normatively: +a consumer ignores a purpose it does not recognize rather than rejecting the +listing. Not breaking for the four existing values. + ## v0 draft — 2026-08-21 Initial draft: README (scope: a format riding AG-UI/A2UI/MCP, not a diff --git a/docs/a2learn/manifest.md b/docs/a2learn/manifest.md index 782e33bb..73ab216a 100644 --- a/docs/a2learn/manifest.md +++ b/docs/a2learn/manifest.md @@ -26,7 +26,7 @@ verification pipeline *(Shipped)*). "standards": [ { "code": "5-ESS2-1", "source": "learning-commons", "verified": true } ], - "gradeBand": { "min": 4, "max": 6 }, + "audience": [{ "scheme": "k12-us", "values": ["4", "5", "6"] }], "pedagogy": { "assesses": true, @@ -62,10 +62,15 @@ verification pipeline *(Shipped)*). means the code resolved against a real standards graph (the reference implementation verifies against Learning Commons). Unverified claims are legal but rank differently; an index MUST NOT present them as verified. + Who the activity is *for* is `audience`, specified below — separately, + because alignment and audience answer different questions and not every + standards graph carries both. - **Pedagogy** — `assesses` (does completing it measure correctness — a "check" step may only use assessing activities), `completion` (`internal-cta` | `signal` | `none`), `mechanics`, `purposes` - (activate | model | practice | check). + (`activate` | `model` | `practice` | `check`) — an open vocabulary, not a + closed enum: a consumer MUST ignore a purpose it does not recognize rather + than reject the listing. - **Delivery** — where the surface lives and how to invoke it. An activity is invokable if the consuming host can satisfy `catalogRequires` (native rendering) *or* via the listed MCP server (zero-catalog rendering in any @@ -77,6 +82,39 @@ verification pipeline *(Shipped)*). trust layer is *structurally* part of the manifest but *institutionally* separate — a registry records attestations; it does not grant them. +## Audience *(normative)* + +`audience` names who an activity is for, as scheme-scoped labels: + +```json +"audience": [{ "scheme": "k12-us", "values": ["4", "5", "6"] }] +``` + +A bare label cannot carry this on its own — `"4"` is grade 4 in one +education system and year 4 or level 4 in another — so the naming authority +travels with the value, exactly as `standards[]` carries `source` alongside +`code`. + +- A producer MUST emit `scheme` on every entry, and MUST NOT emit values for + a scheme it does not own. +- A consumer MUST treat an unrecognized `scheme` as opaque: filter on it, + never parse it. A listing whose every entry is unrecognized is still valid. +- An activity MAY carry several entries when it genuinely serves more than + one audience. +- `audience` MAY be absent. Absent means *unstated*, not *universal*; a + consumer MUST NOT read a missing `audience` as "suitable for anyone". + +One scheme is registered in v0: + +| Scheme | Values | Meaning | +| --- | --- | --- | +| `k12-us` | `"K"`, `"1"`–`"12"` | US K-12 grade levels | + +Higher education, professional and workplace training are structurally +supported and deliberately unregistered: this format documents what a +producer emits today, and none emits them yet. They join under the +[extension rule](./conformance.md#extension-rule) until one does. + ## Two tiers *(normative)* - `tier: "content"` — the activity is pure data rendered by a host's @@ -88,7 +126,7 @@ verification pipeline *(Shipped)*). ## Discovery *(sketch)* The query surface is deliberately specified as MCP tools rather than a new -API style — e.g. `find_activity({ standard, gradeBand, mechanics, lang, +API style — e.g. `find_activity({ standard, audience, mechanics, lang, assesses })` returning ranked manifests. The reference implementation's existing `show_widget(standardCode, kind)` *(Shipped)* is the invocation half; `find_activity` over a manifest index is the MVP of the discovery