The Model

The skill arc

Every kid-facing thing in Sprout runs through a skill. Three beats: author, activate, deliver.

Every kid-facing thing in Sprout runs through a skill. Three beats: author, activate, deliver. Do not conflate them.

Three beats, distinct

Author writes the skill. Activate flips it live (records intent). Deliver makes the kid see it. Each beat is a different call. None of them imply the next.

BeatCallKid sees?
1. Authorcanvas.create then skill.writeNo
2. Activateskill.activateNo (records intent)
3. Delivertask.create or routine.createYes (finally)

The one rule

A skill is the first-class, trackable unit of agent behavior: the thing that is authored, activated, audited, and (later) shared. A kid never sees a raw canvas; they only ever see the output of an activated, delivered skill.

Artifact vs. vehicle

A canvas (also program, itinerary) is an artifact. It is inert on its own. canvas.create persists it but delivers nothing: a fresh canvas reaches no kid.

A skill is the vehicle. It links the artifact and is what gets delivered. One skill can both author/modify artifacts and assign them; these are non-exclusive jobs. You do not need a separate skill per job.

The three beats: do not conflate

1. Author

skill.write (or skill.update addCanvasIds) creates the skill and links the canvas(es). Author the canvas first via canvas.create (read sprout://canvas/sdk before writing canvas HTML).

2. Activate

skill.activate {skillId} flips the skill live for the family. This records INTENT. It is idempotent: re-activating an already-active skill is a safe no-op (status: 'already_active'). Do not treat that as an error and do not loop. Activation is NOT delivery: after this the kid still sees nothing.

3. Deliver

The only beat the kid actually sees. Do exactly one of:

skill.invoke is NOT kid delivery: it only renders the skill body into the agent conversation. The kid never sees an invoke.

kid-callable does not mean surfaced

kidCallable: true means the kid app can launch the skill; it does NOT mean the kid has been given it. An active, kid-callable skill with no task and no routine is still invisible to the kid. Do not report an activity as live / assigned / "the kid can play it" until the delivering task or routine exists.

Reading the nextStep copy

Tool responses and invariant errors are directional: they name the exact next call. canvas.create returns a nextStep; skill.activate returns a nextStep; a canvas-bearing task.create against an inactive or unlinked skill returns a BAD_INPUT whose message names the fix. Follow them literally rather than guessing.

Interaction discipline: checkpoint, do not barrel

You are collaborating with a parent, not executing a batch job. Every step here mutates the family's world; the parent stays in the loop. Pause and confirm before:

The only time you may barrel the whole pipeline is when the parent's request makes fully-autonomous execution UNAMBIGUOUS ("just build it and assign it", "YOLO it"). A bare "make a math game for Jay" is NOT that signal.

Recovery / partial state

Execution is best-effort; partial state is normal. The idempotency story differs per step:

Recovery is re-invocation that self-heals at the authoring/activation layers and agent-checked at the delivery layer. Never manual rollback.

Naming heads-up: activated vs triggered

The shipped enum value is activated. The honest meaning is closer to triggered: the flip fires, the execution that follows is best-effort. A rename to triggered plus a companion retry_friendly advisory (read by the agent before manual re-trigger; not a runtime lock) is in flight. Docs will track when it lands.

Try it

Shell
# Canonical sequence (canvas-backed activity)
canvas_create({name: "...", html: "<...>", dryRun: true, ...})
# returns {specHash}
canvas_create({..., dryRun: false, specHash: "<echo>"})
# returns {canvasId}
skill_write({canvasIds: ["<canvasId>"], kidCallable: true, ageRange: "10-12", ...})
# returns {skillId}
skill_activate({skillId: "<skillId>"})
# kid still sees nothing
task_create({
sourceSkillId: "<skillId>",
steps: [{canvasId: "<canvasId>", ...}],
...
})
# kid sees the card on next iPad sync

Further reading

Was this page helpful?
Canvas guts chevron_right