---
product: "AG Studio"
title: "Agent Configuration"
description: "Learn how to change the built-in agents - their instructions, their tools and who they delegate to - or declare a team of your own."
framework: react
version: "3.0.0"
related:
    - title: "Agent Overview"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/react/ai-agents/"
    - title: "Built-in Agents"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/react/ai-builtin-agents/"
    - title: "Agent Context"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/react/ai-context/"
llms: "https://www.ag-grid.com/studio/archive/3.0.0/llms.txt"
---

# Agent Configuration

The built-in agents are a starting point, not a fixed team. You can change what they say, what they can do and who they hand work to, all while keeping Studio's harness and chat panel.

To own the run loop instead, see [Client Tool Runner](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-client-tool-runner/).

## Declare Your Own Team

`builtIn` is a convenience. Declare agents directly and you control every part:

```ts
// Your AgLlmAdapter - see Direct LLM Runner.
const adapter = myOpenAiAdapter({ endpoint: '/api/llm' });

ai: ({ api }) =>
    createAiHarness(api, ({ tools: { studio } }) => ({
        agents: [
            directLlmRunner({
                id: 'analyst',
                adapter,
                instructions: () => 'You build sales dashboards. Prefer bar charts over pie charts.',
                tools: () => [
                    studio.viewSchema(),
                    studio.viewPage(),
                    studio.executeQuery(),
                    studio.addWidget(),
                ],
            }),
        ],
        primary: 'analyst',
    })),
```

The builder hands you the Studio tools on `tools.studio`, so there is no need to call `api.getAiTools()` yourself. Each tool is a function: call it where you list it. Instructions and tools are callbacks, re-resolved every run, and `primary` names the agent a new thread starts from.

The example below declares one agent of its own - a chart builder with its own instructions and a narrow tool set - in place of the default team.

#### Configure Agents

```tsx
"use client";

import React, {
  useCallback,
  useMemo,
  useRef,
  useState,
  StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgStudio, AgStudioProvider, AgStudioRef } from "ag-studio-react";
import {
  AgAiAgentRunner,
  AgAiHarnessSetup,
  AgAiModel,
  AgAiPromptStarter,
  AgDataEngine,
  AgDataSourcesDefinition,
  AgLlmAdapter,
  AgReportState,
  AgStudioAiModule,
  AgStudioApi,
  AgStudioMode,
  AgStudioProperties,
  createAiHarness,
  directLlmRunner,
  enableStudioDevValidations,
} from "ag-studio";
import { getGhcnCitiesData } from "./shared/ghcnCities/data.tsx";
import { ghcnCitiesReportState } from "./shared/ghcnCities/state.tsx";
import { openaiAdapter } from "./shared/openaiAdapter.tsx";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

export const AI_API_URL = "https://ai-api.ag-grid.com/api/openai/v1";

export const AI_API_TOKEN = "";

const adapter = openaiAdapter({
  endpoint: AI_API_URL,
  key: AI_API_TOKEN,
});

/**
 * The suggestions a new conversation opens on, worded for the one agent this example declares. The
 * last asks for a chart it does not build, so the reader sees a narrowed agent decline and offer the
 * closest bar chart instead.
 */
const PROMPT_STARTERS: AgAiPromptStarter[] = [
  {
    label: "Chart average highs",
    prompt: "Chart average high temperature by city.",
  },
  {
    label: "Compare climate bands",
    prompt: "Chart average high and average low temperature by climate band.",
  },
  {
    label: "Ask for a line chart",
    prompt: "Chart average temperature by year as a line chart.",
  },
];

/**
 * The models offered beside the send button. Each `id` reaches the adapter as declared here and is
 * passed straight on to the provider, so these are real model ids. The first is the one a new
 * conversation starts on.
 */
const MODELS: AgAiModel[] = [
  { id: "gpt-5.6-terra", label: "GPT-5.6 Terra" },
  { id: "gpt-5.6-sol", label: "GPT-5.6 Sol" },
  { id: "gpt-5.6-luna", label: "GPT-5.6 Luna" },
];

// The single widget this agent builds into. configure_widget's config schema is fixed to one widget
// type, so the agent adds a 'bar-chart-grouped' widget under this id and then maps data onto it.
const CHART_WIDGET_ID = "bar-chart";

/**
 * A self-contained custom agent: it explores the data and builds a bar chart, running its own loop on
 * the given LLM adapter. Its tools are pulled from `api.getAiTools()` and re-listed each run; `schema`
 * returns `s.undefined()` because a primary agent takes no delegation parameters.
 */
const createChartBuilder: (
  api: AgStudioApi,
  assistant: AgLlmAdapter,
) => AgAiAgentRunner = (api: AgStudioApi, assistant: AgLlmAdapter) => {
  const studio = api.getAiTools();
  return directLlmRunner({
    adapter: assistant,
    id: "chart-builder",
    description: "Explores the data and builds bar charts.",
    schema: (s) => s.undefined(),
    tools: () => [
      studio.viewSchema(),
      studio.executeQuery(),
      studio.viewPage(),
      studio.viewWidget(),
      studio.addWidget(),
      studio.configureWidget({
        widgetType: "bar-chart-grouped",
        widgetId: CHART_WIDGET_ID,
      }),
      studio.positionWidget(),
      studio.addPageFilter(),
      studio.removePageFilter(),
      studio.addWidgetFilter(),
      studio.removeWidgetFilter(),
    ],
    instructions: () => {
      const { tables } = api.getAiContext().schema();
      return `You are a bar-chart specialist for this dashboard. You explore the user's data and build bar charts.

Voice: always reply in British English, in a warm and concise tone. Open each reply with a one-sentence summary of the action you are about to take, then keep any explanation to a sentence or two.

What you do:
- Explore the data with view_schema and execute_query before building anything.
- Build a grouped bar chart in the '${CHART_WIDGET_ID}' widget: add it with add_widget (type 'bar-chart-grouped'), place it with position_widget, then map the data with configure_widget. You only build grouped bar charts; if asked for any other chart type, explain that you build bar charts only and offer the closest bar-chart alternative.
- Manage filters with add_page_filter / remove_page_filter and add_widget_filter / remove_widget_filter.

Available tables: ${tables.map((t) => t.name).join(", ")}.`;
    },
  });
};

const StudioExample = () => {
  const studioRef = useRef<AgStudioRef>(null);
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const studioStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [data, setData] = useState<AgDataSource>(
    getGhcnCitiesData("https://www.ag-grid.com/studio/archive/3.0.0/example-assets"),
  );
  const initialState = useMemo<AgReportState>(() => {
    return ghcnCitiesReportState;
  }, []);
  const ai = useMemo<AgAiHarnessSetup>(() => {
    return ({ api }) =>
      createAiHarness(api, () => ({
        agents: [createChartBuilder(api, adapter)],
        primary: "chart-builder",
        promptStarters: PROMPT_STARTERS,
        models: MODELS,
      }));
  }, []);

  const setPage = useCallback((pageId: string) => {
    studioRef.current!.api?.setState({
      ...studioRef.current!.api.getState(),
      selectedPageId: pageId,
    });
  }, []);

  return (
    <div style={containerStyle}>
      <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
        <div className="example-controls">
          <div className="controls-row">
            <button onClick={() => setPage("temperature")}>Temperature</button>
            <button onClick={() => setPage("precipitation")}>
              Precipitation
            </button>
            <button onClick={() => setPage("blank")}>Blank</button>
          </div>
        </div>

        <AgStudio
          ref={studioRef}
          style={studioStyle}
          className="my-studio-container"
          data={data}
          mode={"edit"}
          initialState={initialState}
          ai={ai}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <AgStudioProvider modules={[AgStudioAiModule]}>
      <StudioExample />
    </AgStudioProvider>
  </StrictMode>,
);
```

[Live example: Configure Agents](https://www.ag-grid.com/studio/archive/3.0.0/examples/ai-custom-agents/ai-custom-agents-example/reactFunctionalTs/)

## Change Instructions

Instructions are a function, so build them from live context rather than hardcoding what the data looks like:

```ts
// Your AgLlmAdapter - see Direct LLM Runner.
const adapter = myOpenAiAdapter({ endpoint: '/api/llm' });

const studio = api.getAiTools();

directLlmRunner({
    id: 'analyst',
    adapter,
    instructions: () => {
        const { tables } = api.getAiContext().schema();
        const names = tables.map((table) => table.name).join(', ');
        return [
            'You build dashboards for the finance team.',
            `Available data: ${names}.`,
            'Currency is GBP. Never mix currencies in one chart.',
            'When asked for a trend, prefer a line chart over a bar chart.',
        ].join('\n');
    },
    tools: () => [studio.viewSchema(), studio.executeQuery()],
});
```

See [Agent Context](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-context/) for everything available to build instructions from.

## Change the Tool Set

The simplest control over what an agent can do is which tools you list. An agent with no mutating tools cannot change the dashboard, whatever the model decides:

```ts
// A read-only analyst: it can look and answer, but not touch the page.
tools: () => [studio.viewSchema(), studio.viewPage(), studio.executeQuery()],
```

Every Studio tool takes the same optional overrides, applied to that listing only:

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` |  | Advertise the tool under a different name. |
| `description` | `string` |  | Advertise a different description to this agent. |

```ts
tools: () => [
    studio.addWidget({ description: 'Add a widget. Prefer charts; only use a grid when asked for a table.' }),
    // Renamed to avoid a clash with a tool of your own
    studio.executeQuery({ name: 'query_dashboard_data' }),
],
```

A `description` here applies to one agent's listing. To change a description everywhere, use the `aiText` property instead, which is keyed by the name the tool advertises:

```ts
aiText: {
    'tools.add_widget.description': 'Add a widget to the grid. Position is 0-indexed.',
},
```

### Binding a Widget Tool

`configureWidget` is the one tool that takes parameters rather than only overrides. Its schema is narrowed to a single widget type's options, so it has to be told which widget it configures:

```ts
tools: () => [studio.configureWidget({ widgetType: 'grid', widgetId: 'main' })],
```

The built-in widget agent resolves those two values from its delegation parameters, which is how one agent configures whichever widget the lead hands it.

## Add Tools of Your Own

Anything from [`api.defineAiTool`](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-custom-tools/) sits alongside the built-ins in the same array:

```ts
tools: () => [studio.viewSchema(), studio.executeQuery(), setThemeTool, notifyTeamTool],
```

## Change Delegation

`tools.delegateTo` builds the delegation tool for a set of target ids. Give an agent a narrower set and it can only hand off to those:

```ts
// Your AgLlmAdapter - see Direct LLM Runner.
const adapter = myOpenAiAdapter({ endpoint: '/api/llm' });

createAiHarness(api, ({ tools }) => ({
    agents: [
        directLlmRunner({
            id: 'lead',
            adapter,
            instructions: () => 'Coordinate. Delegate data questions; never answer them yourself.',
            tools: () => [tools.studio.viewSchema(), tools.delegateTo(['data'])],
        }),
        directLlmRunner({
            id: 'data',
            adapter,
            description: 'Answers questions about the data by running queries.',
            instructions: () => 'You are a data analyst. Answer with numbers, not adjectives.',
            tools: () => [tools.studio.executeQuery(), tools.studio.viewSchema()],
        }),
    ],
    primary: 'lead',
}));
```

A delegate target needs a `description`, which is what the delegating model reads to decide whether to hand off. Give it delegation parameters with `schema`, and the delegating agent must supply them:

```ts
directLlmRunner<{ region: string }>({
    id: 'regional-analyst',
    adapter,
    description: 'Answers questions about one region.',
    schema: (s) => s.object({ region: s.string() }),
    instructions: ({ region }) => `You only discuss the ${region} region.`,
    tools: () => [studio.executeQuery()],
});
```

`delegate_to`'s schema advertises those parameters, resolved per turn.

> **Warning**
>
> An agent that takes no delegation parameters must declare `schema: (s) => s.undefined()`, not an empty object. A primary agent is run with `undefined` parameters, and an object schema rejects that, so the agent fails to start.

## Mix Built-ins With Your Own

Spread the defaults and add to them. Keep `primary` pointing at whichever agent should front the conversation:

```ts
// Your AgLlmAdapter - see Direct LLM Runner.
const adapter = myOpenAiAdapter({ endpoint: '/api/llm' });

const studio = api.getAiTools();

// An agent of your own, built as in Declare Your Own Team above.
const auditAgent = directLlmRunner({
    id: 'audit',
    adapter,
    description: 'Reviews a page for missing or misleading widgets.',
    instructions: () => 'You review dashboards and report what is missing.',
    tools: () => [studio.viewPage(), studio.viewWidget()],
});

createAiHarness(api, ({ builtIn }) => ({
    agents: [...Object.values(builtIn).map((definition) => directLlmRunner({ ...definition, adapter })), auditAgent],
    primary: 'lead',
}));
```

The built-in lead delegates to a fixed set of ids, so it will not discover `auditAgent` on its own. To have work delegated to it, declare your own lead with a wider `delegateTo`.

## Next

- [Custom Tools](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-custom-tools/) - adding actions of your own
- [Client Tool Runner](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-client-tool-runner/) - owning the loop
- [Built-in Agents](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-builtin-agents/) - what you are starting from
