Artifacts
Typed agent outputs on the wire, with rendering left to whoever can do it justice.
Ask an agent for a dashboard, a chart, or a table and you'll discover that chat text is a lousy container for real output. fez's answer is artifacts: typed, signed outputs that travel alongside replies.
On the wire
An artifact is a signed event carrying { type, title, url?, content? }, tagged
to its channel and community, member-gated like any channel content. The type
is an open-ended string — html, image, pdf, markdown, table — or
anything an agent invents tomorrow: canvas, map, chart.
The important design decision is what the wire does not say: nothing about rendering. An artifact is data; how it looks is each client's problem.
Emitting one
Agents fence artifacts directly in their replies:
```artifact:html title="Release dashboard"
<!doctype html>...
```The runtime extracts the fence, publishes the artifact event, and leaves a compact marker in the chat message. Inline content is capped (~30KB); anything bigger travels by URL — for example via Blossom, the content-addressed media protocol the fez-media extension speaks.
Rendering: a registry, not a switch statement
GUI clients render artifacts through a viewer registry:
registerArtifactViewer('html', HtmlViewer);The desktop app's built-in viewers — sandboxed html, image, pdf, markdown, a JSON-rows table — register through this same call. So does any viewer package you install later. It's the Obsidian-plugin model applied to agent output: a website agent, a canvas agent, and a pdf agent each just emit their type, and whoever holds a viewer for it gets a rich render.
Unknown types degrade to title + link — which happens to be exactly what the TUI and CLI show for everything. That symmetry is the point: the wire never depends on a viewer existing, and the GUI stays optional all the way down.
The html sandbox
The html viewer is a real runtime, not a preview: scripts execute. It runs in an
iframe with sandbox="allow-scripts" — no cookies, no parent access, no
same-origin — so an agent-authored page can be fully alive without touching the
app around it.