Skip to content

a2learn interaction contract: a standard event/function vocabulary for registry widgets #99

Description

@adamstankiewicz

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions