/v1/dashboards), with the same session token the widget holds, so
your product renders them wherever it wants.
How the pieces fit
What a dashboard is made of
Filters are the controls on the page: adate_range (with a preset
default such as “last 30 days”, so a refresh stays relative), a select
(a property, a store, a team), a text, number or date. Source arguments
read them as {{filters.<key>}} — {{filters.<key>.from}} and .to for a
range.
Sources are the data: one entry per tool call, with the tool’s arguments
and, when it paginates, a loop (all_pages, fields to keep, a row
filter). Several panels can read one source.
Panels are what is shown, in order, on a 12-column grid. Each has a
span (3, 4, 6, 8 or 12 columns) and a height (sm, md, lg).
stats, chart and table panels name their sources and carry a
transform: a few lines of JavaScript that turn the sources’ raw output
into the panel’s payload — exactly the payload of the showStats,
showChart or showTable component, so the SDK renders a
panel with the same code it renders a chat answer with. text panels carry
markdown.
How the agent builds one
The dashboard agent never guesses a field name. It lists the read-only tools of the network (list_source_tools), reads a tool’s arguments
(describe_source_tool), runs the sources for real with the filters
resolved (preview_dashboard_sources) and writes each transform against the
output it saw. save_dashboard then compiles the spec, runs a full test
refresh, and publishes a version only when every panel rendered; a failed
test hands the agent the failing step with the data it received, so it fixes
the transform and saves again. The user sees a dashboard only once a refresh
has succeeded.
Only read-only tools qualify as sources: a custom API tool with a GET
method, an MCP tool whose server marks it readOnlyHint (or whose name reads
as one: get_, list_, search_…), a toolkit action with a read verb. A
dashboard can never send a write.
Running the dashboard agent
Send the chat a turn withmode: "dashboard" and the network’s copilotId;
routing is skipped and the dashboard agent answers. A thread that started
this way stays in dashboard mode, so follow-ups need no flag. With
dashboardId, the turn is about that dashboard: an edit (“make the chart
wider”), or a question about its data (“what stands out this week?”) — the
agent sees the spec and the current data of every panel and answers from it,
and only saves when asked to change something.
system_agents.dashboard — see the platform agents.
A thread the dashboard agent held carries mode: "dashboard" and the
dashboardId in its metadata, so GET /v1/threads?dashboardId=… lists the
conversations about one dashboard (the widget’s “previous conversations”),
and a client that lists the roster’s threads can leave them out.
Rendering and refreshing
GET /v1/dashboards/{id} returns everything a page needs: the spec (filters,
panels, their span, height and order), the filter values to open on, and
data — the latest refresh with each panel’s rendered payload
(panels[].component.args is the showStats / showChart / showTable
input) or its markdown.
wait: true answers once the
run is over instead of 202. A refresh with the same filters started moments
ago is joined rather than started again. The workflow.run.finished event on
GET /v1/events fires when a refresh ends.
GET /v1/dashboards/{id}/versions lists what the chat published;
POST /v1/dashboards/{id}/activate goes back to one of them and refreshes
it. DELETE /v1/dashboards/{id} removes the dashboard.
A refresh runs with the user’s own tool credentials, as a chat turn does —
the ones the session carries, or the ones captured when the dashboard was
saved. An API key refreshing on a user’s behalf names the user with
X-User-Id and may send runtimeAuth.