The gap
Widgets already talk back to the model, but each transport improvises its own dialect:
- MCP Apps:
ui/update-model-context with prose-first reports + a widget_result structured block, plus the single struggle signal (src/lib/mcp/report.ts, shell completion reporter — deliberately shared wire format, not shared code).
- A2UI: one
a2learn.widget_completed button action; the function channel (CallRendererFunction / FunctionResponse) entirely unused.
- AG-UI:
a2learn.* CUSTOM events on the pathway transport.
Same semantics, three ad-hoc spellings. What's missing is a standard vocabulary owned by the registry — defined once, projected into each envelope, the same move #92 made for the tool surface.
Proposed vocabulary
Small on purpose. Two events (widget → model, fire-and-forget):
a2learn.widget_completed — the evidence report: prose sentence for the model reader first, widget_result block alongside (kind, standard, correct: null unless a real check ran, attempts, hints, score, per-kind detail). Verdict fields gated on the registry entry's assesses — invariant 3 enforced by the contract shape, not by convention.
a2learn.widget_struggle — at most once per run, after repeated failed checks without completion. The existing signal, standardized.
Two functions (widget ↔ agent, request/response, value XOR error):
a2learn.score — run a check the surface cannot run itself (model-call scoring under the instance's keys; the standards-graph lookups). Generalizes score_draft into the protocol-native shape.
a2learn.assist — student-initiated help: the widget asks, the agent answers into the surface. Pull-based by design.
Doctrine the contract must encode
- Quiet reporting stays law. One completion + at most one struggle. No step-by-step telemetry stream — a chatty contract would turn every widget into a surveillance instrument and every transcript into noise. Interactivity beyond that is pull-based (functions the student's own action initiates), which keeps the model in the loop without making it a spectator.
- Prose leads on conversational transports — a sentence about a student, not a JSON blob; the block rides along for exact fields.
- Anonymous ids only in every payload (invariant 2).
- Capability declaration: a registry entry declares which events/functions its kind uses, next to
assesses — hosts know what to expect, and an undeclared emission is a bug, not a feature.
Mechanics
One Zod-defined vocabulary module in the registry; per-transport projections (MCP Apps envelope, A2UI action/function messages, AG-UI CUSTOM events); conformance fixtures with negative controls for each projection. Registry-entry declaration lands with the registry-owned-semantics refactor.
Relations
🤖 Generated with Claude Code
The gap
Widgets already talk back to the model, but each transport improvises its own dialect:
ui/update-model-contextwith prose-first reports + awidget_resultstructured block, plus the single struggle signal (src/lib/mcp/report.ts, shell completion reporter — deliberately shared wire format, not shared code).a2learn.widget_completedbutton action; the function channel (CallRendererFunction/FunctionResponse) entirely unused.a2learn.*CUSTOM events on the pathway transport.Same semantics, three ad-hoc spellings. What's missing is a standard vocabulary owned by the registry — defined once, projected into each envelope, the same move #92 made for the tool surface.
Proposed vocabulary
Small on purpose. Two events (widget → model, fire-and-forget):
a2learn.widget_completed— the evidence report: prose sentence for the model reader first,widget_resultblock alongside (kind, standard,correct: nullunless a real check ran, attempts, hints, score, per-kind detail). Verdict fields gated on the registry entry'sassesses— invariant 3 enforced by the contract shape, not by convention.a2learn.widget_struggle— at most once per run, after repeated failed checks without completion. The existing signal, standardized.Two functions (widget ↔ agent, request/response,
valueXORerror):a2learn.score— run a check the surface cannot run itself (model-call scoring under the instance's keys; the standards-graph lookups). Generalizesscore_draftinto the protocol-native shape.a2learn.assist— student-initiated help: the widget asks, the agent answers into the surface. Pull-based by design.Doctrine the contract must encode
assesses— hosts know what to expect, and an undeclared emission is a bug, not a feature.Mechanics
One Zod-defined vocabulary module in the registry; per-transport projections (MCP Apps envelope, A2UI action/function messages, AG-UI CUSTOM events); conformance fixtures with negative controls for each projection. Registry-entry declaration lands with the registry-owned-semantics refactor.
Relations
a2learn.score.get_pathway_resultsaggregates exactly whatwidget_completedcarries, so the shapes should be designed together.widget_resultshape is already documented as converging with the SDK's universal WidgetResult — this issue is where that convergence becomes a written contract.🤖 Generated with Claude Code