Boards¶
A board is a custom HTML page written by an agent (or you) and stored as an artifact: a results report, a demo gallery with audio/images, a comparison dashboard, a notebook-style writeup — anything a page can be. Boards are not screenshots: a board can fetch the read-only API at view time, so it renders current data whenever it’s opened.
Creating one¶
No special command — a board is an HTML artifact on a run or an experiment:
h.log_artifact("report.html", media_type="text/html", note="Ablation report")
# over MCP: run_log_artifact(run, path, media_type="text/html", note=...)
# cross-run views hang on the experiment instead:
# experiment_log_artifact(experiment, path, media_type="text/html", note=...)
Run boards are one-run views; experiment boards are the home for cross-run
comparisons and summaries that outlive any single run. Either way the web UI
renders the board full-page at #/board/<sha256> (bookmarkable) and lists
it in the board gallery — the Boards page in the sidebar, the Boards
section of the owning experiment, and the boards group of the run’s
Artifacts tab. Boards are content-addressed and immutable — updating one
means logging a new version; old versions stay addressable forever.
Give it a title. The gallery names a board by its note, falling back
to the HTML <title>, then the filename — pass note="<human title>" when
logging. Boards are also searchable: the title/filename is indexed into the
owning node’s search text, so graph_query("ablation board") surfaces the
run or experiment that carries it.
The board contract¶
A board renders inside a sandboxed iframe with a strict CSP: it gets its own opaque origin, read-only API access, and no network beyond this host. Write within these rules:
Self-contained. Inline all CSS and JS. External hosts (CDN scripts, fonts, trackers) are CSP-blocked — a reference to one simply won’t load. This is also the durability rule: a board must render unchanged years later. The one loadable script is the host’s own optional helper:
<script src="/boards-lib/mlparty.js"></script>
which defines
window.mlparty—node(ref),metrics(runId, {name}),query(q, {type, limit}),boards({experiment_id}),artifactUrl(sha, mediaType), andstream(runId, onRecord, onEnd)for SSE live-tailing (returns a stop function). It is served by the same host (never a CDN), so it passes the CSP and keeps boards durable.Reference store content by address. Other artifacts embed via the inline endpoint:
<img src="/api/artifacts/<sha256>?inline=true&media_type=image/png"> <audio controls src="/api/artifacts/<sha256>?inline=true&media_type=audio/wav">
Fetch live data from the read-only API.
/api/nodes/<id>,/api/runs/<id>/metrics,/api/query?q=…, the SSE stream/api/runs/<id>/metrics/stream— all reachable viafetch()/EventSourcefrom inside the sandbox. This is what makes a board a live view instead of a frozen export.Read-only by construction. Write endpoints reject requests from the sandbox. Don’t design a board that wants to write; that’s what the agent itself is for.
Boards under authentication¶
On a login-gated server (Deployment), the sandbox’s opaque origin
carries no session cookie — so the UI hands the board iframe a
short-lived read-only token via ?bt= in its URL. The helper attaches
it to every request automatically; if you build URLs by hand (e.g. a bare
<img src="/api/artifacts/…">), read it from location.search and append
it, or use mlparty.artifactUrl(...), which does. The token can only
read: writes and minting further tokens are rejected.
Security model¶
Boards are data, never trusted UI. Enforcement is server-side and applies
even when a board URL is opened directly: the response carries
Content-Security-Policy: sandbox allow-scripts; … connect-src 'self', so
the page always runs in an opaque origin, cannot read the viewer UI’s
storage, cannot reach external hosts, and cannot write to the store.
Relatedly, /api/artifacts inline rendering is safelisted to media types
that can’t execute (images, audio, video, plain text) — HTML renders only
through the sandboxed /boards/<sha256> route.
Boards and the knowledge graph¶
Boards deliberately do not become graph nodes and get no produces
edges: a board’s provenance is already fully expressed by containment — it
sits in the artifacts list of the run or experiment that produced it.
Discovery goes through the gallery (/api/boards) and through search (the
board’s title/filename is indexed into the carrying node’s search text).
This keeps the graph spine reserved for knowledge claims and lineage.