Skip to content

Canvas artifacts

A canvas artifact is a designed, self-contained HTML page an agent publishes to the canvas drawer: a run report, an audit briefing, a chart of usage over time. Ask for one in chat (“make a report of this run”) and the agent authors the page, publishes it with the canvas_publish tool, and the drawer opens on it. Artifacts persist under <home>/artifacts/ as plain .html files, survive server restarts, and update in place when the agent republishes the same name.

The pipeline has three parts:

  • Design guidance. When canvas_publish is available, the chat system prompt carries the authoring contract: theme through CSS custom properties (keelson’s dark values on :root, light overrides on :root[data-theme="light"]), system font stack, no external resources, and the keelson palette inlined as literal hex. The canvas_design_guide tool serves deeper references on demand: a ready class kit, the house voice for page copy, page types and layout, chart-form selection, the color system, mark anatomy, inline SVG diagram craft, the structured board view’s section catalog, and an anti-pattern catalog.
  • Computable validation. A page that carries categorical chart series declares its palette on <body data-palette-dark="…" data-palette-light="…">. Publishing validates color-vision-deficiency separation and surface contrast per theme and rejects hard failures with a per-check report, so the agent fixes the colors and retries in the same turn. Palettes are checked by computation, never by eyeball.
  • The sandboxed frame. Artifacts render through the same hardened html canvas kind ribs use: an opaque-origin iframe under a CSP that denies by default and re-allows only what a page needs to draw itself, inline script and style plus data: and https: images and fonts. The load-bearing line is connect-src 'none', which blocks script-initiated network egress (fetch, XHR, sendBeacon, WebSocket), so frame script cannot exfiltrate. The host stamps the app’s resolved theme onto the frame and pushes toggles live, so a token-styled page re-themes without a reload.

Pages can opt into transient UI state with keelson.saveState and keelson.onRestore. Snapshot-backed pages retain it by key in bounded browser-tab memory; inline text and direct artifact sources without a snapshot key retain it only for the component’s lifetime. This is not artifact or draft storage, and a page reload loses it. See the HTML state bridge reference for the API, lifecycle, and limits.

Any provider with the tool registry sees canvas_publish. A typical exchange:

you> Chart last week's token usage by model and publish it as a page.
agent> [canvas_design_guide: form] [canvas_design_guide: color]
agent> [canvas_publish: "Token usage, June 29 to July 5"]

The drawer opens on the published page. The tool result records the artifact key (canvas:artifact:<slug>), and republishing with name: <slug> updates that page in place rather than minting a sibling.

The bundled design-artifact workflow is the reference shape: one prompt node with allowed_tools: [canvas_publish, canvas_design_guide] that reads the guide, authors the page, and publishes through the validated gate.

Terminal window
keelson workflow run design-artifact "a briefing on this week's merged PRs"

Artifact quality tracks the authoring model. Pin provider: or model: in the workflow (or set KEELSON_WORKFLOW_PROVIDER) to route artifact authoring at your strongest option.

Generated pages match the app because they inline the same palette the SPA renders: @keelson/shared exports DESIGN_TOKENS (surfaces, ink, semantic accents, the six chart-series slots, the five identity tones) for both themes, and a drift guard keeps the module identical to the app stylesheet. The sandboxed frame cannot read the app’s CSS custom properties across its origin boundary, so inlining the values is what keeps an artifact on-palette. Rib authors can import the same tokens and validateCategoricalPalette / validateOrdinalRamp checks to build their own validated producers.

Two guide sections do most of the work of making a page look and read like keelson. The kit section is one stylesheet: the token block (which also paints the page background, since the frame’s own ground is white) followed by ready classes for the masthead, summary box, legend, stat tiles, tags, tables, callouts, terminal output, and inline SVG diagrams. Tone classes are one vocabulary across all of them: .good, .warn, .crit, and .info mark state, and .id-1 to .id-5 mark who, so an actor keeps one color across every figure and table on the page.

The voice section sets how the copy reads. A page leads with the state of the work, uses keelson’s own terms (run, node, workflow, rib), points each claim at the run id, node, file, or command that proves it, and writes “not measured” instead of a zero when a check never ran. It speaks as the page, never as the agent that wrote it.