api.getAiTools() returns Studio's tools, bound to the Studio instance and ready to run. They read live dashboard state when they execute.
Inside a createAiHarness config builder the same tools arrive on tools.studio, so you do not need to call api.getAiTools() yourself:
// Your AgLlmAdapter - see Direct LLM Runner.
const adapter = myOpenAiAdapter({ endpoint: '/api/llm' });
ai: ({ api }) =>
createAiHarness(api, ({ tools: { studio } }) => ({
agents: [
directLlmRunner({
id: 'analyst',
adapter,
tools: () => [studio.viewSchema(), studio.executeQuery(), studio.addWidget()],
}),
],
primary: 'analyst',
})),Every member is a function. Call it where you list it, optionally passing overrides.
Reading Copy Link
| Tool | Method | What it does |
|---|---|---|
view_schema | viewSchema() | The data model: tables, fields, types, aggregations, relationships. Agents are told to call this before writing a query. |
view_report | viewReport() | Every page in the report, with its id, how much is on it, and which page is being viewed. The only tool that publishes page ids. |
view_page | viewPage() | One page: widget positions, layout config, page filters, and any layout or filter problems. |
view_widget | viewWidget() | One widget's config, size, filters and health issues. |
execute_query | executeQuery() | Runs a query against the data. Returns rows as a table for the model, capped so a large result cannot swamp the context window. |
Changing the Page Copy Link
| Tool | Method | What it does |
|---|---|---|
add_widget | addWidget() | Adds a widget of a given type at a grid position. Seeds the type only; configuration follows separately. |
position_widget | positionWidget() | Moves or resizes a widget. Omitted fields keep their current values. |
remove_widget | removeWidget() | Removes a widget from the page. |
configure_widget | configureWidget({ widgetType, widgetId }) | Configures one widget: data mapping, titles, formatting, type-specific options. |
configureWidget is bound per listing to a specific widget, because its schema is narrowed to that widget type's options. See Binding a Widget Tool.
Binding a Page Copy Link
No tool takes a page as an argument. A tool that acts on a page is bound to one when it is listed:
studio.addWidget({ pageId: 'sales' });Leave pageId out and the tool acts on whichever page is being viewed when the call runs, which is what a single-page dashboard wants. Set it and every call from that listing reads and writes that page, whichever page the user is on.
Bind it when an agent is built for a particular page: the ids to choose from come from view_report, so an orchestrator reads the report and passes the page to each agent it starts. A model cannot pick a page itself, which is deliberate. Nothing tells it which page the work belongs to, so the choice would be a guess, and a guess that named a real page would be carried out in silence on the wrong page.
Work on a page other than the one being viewed is saved to that page and appears when the user moves to it.
Calculated Fields Copy Link
| Tool | Method | What it does |
|---|---|---|
create_expression | createExpression() | Adds a calculated column to the schema, computed per row from an expression over its fields. |
update_expression | updateExpression() | Changes a calculated column's name, expression or format. Omitted parts are left as they are. |
delete_expression | deleteExpression() | Removes a calculated column from the schema. |
Only calculated columns added to the schema can be changed or removed. Fields that came with the data cannot. See Expressions for what an expression can contain.
Filters Copy Link
| Tool | Method | What it does |
|---|---|---|
add_page_filter | addPageFilter() | Appends a page-level filter, applying to every widget on the page. |
remove_page_filter | removePageFilter() | Removes a page filter by its index in the page's filter list. |
add_widget_filter | addWidgetFilter() | Adds a filter to one widget. |
remove_widget_filter | removeWidgetFilter() | Removes a widget filter by index. |
The remove tools take an index, so an agent is expected to call view_page or view_widget first to see the current order.
Harness Tools Copy Link
Some tools are owned by the harness rather than the API, because they need per-conversation state. They arrive in the config builder alongside studio:
// Your AgLlmAdapter - see Direct LLM Runner.
const adapter = myOpenAiAdapter({ endpoint: '/api/llm' });
createAiHarness(api, ({ tools }) => ({
agents: [
directLlmRunner({
id: 'lead',
adapter,
tools: () => [tools.plan.view(), tools.plan.update(), tools.delegateTo(['data', 'page'])],
}),
],
primary: 'lead',
}));| Tool | Builder | What it does |
|---|---|---|
view_plan | tools.plan.view() | The current plan, with per-item status. |
create_plan | tools.plan.create() | Creates a plan: a layout tree, widget entries, and optional page filters. Replaces any existing plan. |
update_plan | tools.plan.update() | Marks plan items done or failed as work progresses. |
clear_plan | tools.plan.clear() | Removes the active plan. Placed widgets remain. |
rename_thread | tools.renameThread() | Names the conversation the call runs in, which starts out unnamed. List it on the agent that fronts the conversation. |
delegate_to | tools.delegateTo([ids]) | Delegates to another agent, creating a child run. Its schema enumerates the named targets and their parameters. |
A plan is a durable artefact on the thread, so it survives across messages and is rendered in the panel. rename_thread calls are hidden from the message list, because the renamed conversation is the visible outcome. complete_task is added automatically to a delegated run and does not need listing.
Next Copy Link
- Custom Tools - your own actions
- Tools Overview - the model underneath, and running a tool without an agent