Skip to content

Tables and charts ​

A Read action can answer with a table: the person reads your query's rows, and the model says what stands out.

Copilot
Person:
Who posts the most?
Looked it updone
AuthorPostsPublished
Sam1275%
Alex850%
Robin5100%
Refresh
Posts by author, as a bar chart
Agent:
Sam posts the most. Robin has published every post.
  1. ActionTableThe rows your query returned, under the columns you declared.
  2. your chartThe server names the kind, bar. Your page draws it.
  3. the modelTold to say in a sentence or two what stands out, not to repeat the rows.
A copilot answer that shows a table.

Declare columns, return rows ​

Implement ShowsTable on a Read action. columns() says what each row may show, and handle() returns the rows: a query, an array or a generator. Nothing outside the declared columns leaves the server, on any surface. See Show a table and Column types.

app/Actions/PostsPerAuthor.php
php
final class PostsPerAuthor extends Action implements ShowsTable
{
    protected ?Effect $effect = Effect::Read;

    public function columns(): array
    {
        return [
            Column::text('author', __('Author')),
            Column::integer('posts', __('Posts')),
            Column::percent('published', __('Published')),
        ];
    }
}

One table, two readers ​

In a copilot turn, the rows split.

  1. handle()returns rows: a query, an array, a generator
  2. columns()each row keeps only the declared keys
  • to the person
    Every row, as a table
    • a data-view part after the call's row, up to 500 rows
    • <ActionTable> draws it, with its time and Refresh
    • your chart: bar, line, metric or none
  • to the model
    A short copy
    • The person now sees this as a table of 3 rows.
    • the first 20 rows, text cut to 80 characters
    • the columns' keys, labels and descriptions
    • Say in a sentence or two what stands out; do not repeat the rows.
The same rows, cut two ways after the columns allowlist.

Everywhere else, a route and MCP get the same table as JSON, and actions:run prints it as a table. See What each surface gets.

The chart follows the shape ​

The package draws no chart. It names one in table.chart, from the columns and the number of rows: one row of numbers is a metric, a date column then numbers is a line, and a text or yes-or-no column then numbers is a bar of up to 50 rows. One chart shows one unit, so the percentage above stays out of the bar chart of posts. Your page draws it with your own components. See The chart.

Kept and refreshed ​

A table is kept with a stored conversation, so a reload shows the rows the model described. When the action is exposed on the web, Refresh runs it again as the person, with the same input, through every check its route gets. Another person or another tenant gets a 404. See Refresh.

Datasets: the agent asks ​

A dataset lets the model ask its own question within names you declare: dimensions to group and filter by, and measures to compute. The package writes the query, scoped to the tenant the call runs in. The model never writes SQL.

  1. The model picks namesmeasures, by, since
  2. Only declared nameschecked before any query
    unknown: refused
  3. A scoped querythe tenant, global scopes, then scope()
  4. A tablewith a caption of what was asked
A dataset call: the model chooses among your names, and the package writes a scoped query.
The model's arguments
json
{"measures": ["posts"], "by": ["author"], "since": "-1m", "compare": true}

See Datasets, and the security notes on tables and datasets.

Released under the MIT License.