A spec of Open Learn Protocol

EduProgressv1 draft

The learning app knows what the child did. EduProgress emits that knowledge as structured events to a collector — typically the launcher or a parent dashboard — so progress is portable across apps without a custom integration per pair.

The idea

The learning app emits envelope-shaped events as they happen — session started, activity completed, skill progress, the child got stuck — and batches them to an HTTPS endpoint defined by the collector. The collector validates each event against a JSON Schema, deduplicates by id, and stores or forwards as it sees fit.

There is no streaming, no shared skill taxonomy, no analytics, no queries back. v1 is just ingest. Anything richer is left to the consumer.

The flow

┌────────────┐  1. authenticated POST   ┌──────────────┐
│ Learning   │ ──────────────────────►  │ Collector    │
│ app        │   { events: [...] }      │ (launcher,   │
│ (reporter) │                          │  dashboard)  │
│            │ ◄──────────────────────  │              │
└────────────┘   202 { accepted: N }    └──────────────┘

The event envelope

Every event has the same outer shape. The data field is type-specific.

{
  "event_id": "01HX5XYV6FPK3R3D6T2H8E2VPR",
  "type": "activity.completed",
  "occurred_at": "2026-05-20T14:32:17.412Z",
  "child_ref": {
    "iss": "https://issuer.example-launcher.com",
    "sub": "child:abc123",
    "email": "student@example.com"
  },
  "app_ref": { "app_id": "your-app-id", "version": "2.4.1" },
  "session_id": "01HX5XYTX5N3DTSXJC9MFAV0QH",
  "data": {
    "activity_id": "fractions-intro-3",
    "score": 0.85,
    "time_ms": 124000,
    "attempts": 3,
    "passed": true
  }
}

Core event types

session.started Child begins using the app.
session.ended Session ends for any reason. Carries active_ms and reason.
activity.started Child begins a unit of work — a lesson, exercise, level.
activity.completed The unit ends with a result. score, attempts, passed.
skill.progress Mastery of a skill changes meaningfully. level and delta.
struggle.signal Child is stuck, thrashing, or abandoning. The negative-signal channel.
v1 does not maintain a shared skill or topic taxonomy. skill_id and topic are opaque strings, defined by each app. The protocol standardizes the envelope, not the pedagogy.

Transport

POST <collector_url>/v1/events
Authorization: Bearer <reporter_token>
Content-Type: application/json

{ "events": [ /* envelope objects */ ] }

Batched HTTPS. ≤ 500 events or ≤ 1 MB per batch. Reporters retry transient failures; collectors deduplicate by event_id.

Read the spec