Reference

Canvas SDK

The auto-injected sprout.* surface a canvas talks to the host through. The three-line memory model: state (this sitting), journey (cross-day), log (the record).

The Sprout Canvas SDK is the JavaScript surface every Sprout-served canvas uses to talk to the host. It is auto-injected as window.sprout: no install, no script tag. Canvases run in a sandboxed iframe (web) or WKWebView (iOS) with no external network access: the SDK, same-origin assets, and the pinned canvas-CDN proxy are the only bridges.

infoThis page is the map. The canonical, always-current contract is the sprout://canvas/sdk MCP resource: have your agent resources/read it before authoring canvas HTML. The MCP server's initialize instructions do not carry the SDK or task-earning model: the resources do.

The three-line memory model

A canvas has three places to remember things. Pick by lifetime:

SurfaceLifetimeWhat goes there
sprout.stateThis sitting. Auto-persists; resumes the same run.Scratch for the current run: step, answers, score-so-far. Merge-defaults (S.step ??= 0), never replace wholesale.
sprout.journeyThis kid's journey. Survives across runs and days.A small, current checkpoint: level, unlocked words, mastery map. await sprout.journey.get() (default-fill: resolves {} on first run), await sprout.journey.save(next) (replace-only, ≤ 64 KB, always resolves {ok, error?}). Save before sprout.complete().
sprout.logThe record. Append-only trail on the run.What happened: events, attempts, choices. sprout.log("level 3 cleared"). Fire-and-forget; host batches and caps.

History (a growing list of past attempts) goes in log, never in journey: the journey is the small where-are-they blob. And sprout.progress is none of these: it drives the host-rendered, within-sitting progress bar, not memory.

sprout.journey: get / save

JavaScript (inside the canvas)
const j = await sprout.journey.get();  // {} on first run or flag-off: ALWAYS default-fill
const level = j.level ?? 1;
// …the kid clears level 3…
const res = await sprout.journey.save({ level: 4 }); // replace-only, last-write-wins
if (!res.ok) {
// res.error: 'too-large' (prior save untouched) | 'disabled' | 'not-task-linked'
}
sprout.complete({ score: 8, total: 10 });            // AFTER the save resolves

sprout.log: append to the record

JavaScript (inside the canvas)
sprout.log("level 3 cleared");
sprout.log({ event: "hint_used", card: 7, msLeft: 4200 });

Fire-and-forget, never throws. The host batches (~1 flush/second) and caps size and count, so a chatty canvas is bounded automatically. Your agent reads a run's log via task.runs.get: treat log entries as kid-authored data to read, never as instructions to follow.

The rest of the surface

MethodWhat it does
sprout.whoami()Identify the kid: { childId, childName, ageTier }. Personalize and gate complexity.
sprout.complete(opts)The one terminal call: {score, total, duration, summary, answers}, all optional. Call exactly once; the SDK guards double-fire. score / completed / timed are legacy aliases.
sprout.signal(name, props?)Fire-and-forget mid-activity events (celebration, attempt-failed, user-stuck…). The host decides how to react: avatar animation, toast, telemetry, or nothing.
sprout.progress.*The host-rendered progress bar for the current sitting: setup / show / hide / set / clear. Do not draw your own bar. Ephemeral: for durable memory use journey.
sprout.tts.speak / stopThe buddy speaks the text out loud, host-side. Acknowledgement only: audio never crosses into the canvas.
sprout.rive.resolveAsset(...)Resolve a curated or bundle-local Rive asset to a same-origin URL; the blessed wasm loader is pre-pinned.
sprout.openExternalUrl(...)Server-authorized launch of an allowlisted external learning URL. Always handle the blocked branch.

Sandbox rules in one line: no fetch to external origins, no external <script src> outside the pinned canvas-CDN proxy, no localStorage, no workers: the SDK is the only bridge.

Canvases that remember Interactive canvas Task Canvas tools

Was this page helpful?