tool_result frames. Richer views can also emit a panel_open frame, while large or persisted payloads are retrieved through the artifacts API.
Each
tool_result and any following panel_open arrives before the terminal message frame, wrapped in the durable WebSocket envelope.Read the tool-result envelope
Normal tool results use this shape:frame.data.output, not frame.data itself. tool_call_id, depth, and skill are correlation metadata and can be absent.
Saved-query turns are the compatibility exception: their synthetic tool_result.data is the bare legacy payload {rows, row_count, truncated, note?} and has no output wrapper. Normalize both forms before rendering:
SQL tables
Successful SQL results haveoutput.kind: "table":
columns and rows are parallel: each row is a value array in column order. type_hint is a renderer-friendly coarse type; sql_type can be absent. row_count is the number of returned rows, and truncated: true means more rows matched upstream. Values are unformatted and can include null.
Recoverable failures use kind: "error"; access-control refusals use kind: "denied". Inspect kind before assuming table fields exist, and display the returned error or reason without retrying denied queries from the client.
Saved-query result
The first result for a saved-query turn uses rows as objects for backward compatibility:truncated is true, an explanatory note can also be present.
Charts
The chart tool returns a renderer-neutral chart spec capped at 2,000 rows inline and persists the fuller spec produced from the scoped query as an artifact:chart_type values are bar, line, pie, scatter, area, and histogram. chart_spec.data is columnar (columns plus 2D rows), not an array of row objects.
The inline chart_spec.data.rows array contains at most 2,000 rows. When more rows were available, it contains the first 2,000 and chart_spec.data.truncated is true. That flag can also be true when the scoped query itself reached an upstream result limit.
The chart_spec artifact is persisted before the inline row cap is applied, so its payload can contain more than 2,000 rows. Inspect the artifact payload’s data.truncated value to distinguish upstream query truncation: true there means the persisted query result was also truncated, while false means the artifact contains the full result returned for that chart. The artifact is fuller than the inline response, not an unbounded export.
The current artifact_url value is empty; construct /v1/artifacts/{artifact_id} from artifact_id. Render the inline spec immediately or retrieve the fuller spec later with Get Artifact and Get Artifact Payload.
Generic analytics agents can instead produce a rendered HTML artifact:
/v1/artifacts/{artifact_id} and /payload. The external API has no /v1/plots route.
Full table exports
When the agent exports a complete SQL result set, it returns an XLSX artifact reference rather than putting the rows on the stream:truncated flag describes that export ceiling, not the much smaller inline SQL preview cap. Poll Get Artifact if needed, then download /v1/artifacts/{artifact_id}/payload.
Typed panels
Schedules, editable tables, media grids, decks, and exports can be persisted as panels and announced withpanel_open after the producing tool result:
panel_id. entity_id, entity_version, tool_call_id, and chat_message_id can be null.
Handling example
404 rather than disclose another member’s resources.