---
product: "AG Studio"
title: "Agent Overview"
description: "Learn what an agent is in AG Studio, and how the three runners divide the work between your code and Studio."
framework: react
version: "3.0.0"
related:
    - title: "Built-in Agents"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/react/ai-builtin-agents/"
    - title: "Agent Configuration"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/react/ai-custom-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 Overview

An agent runs a conversation to completion. Given a run's input and history, it works - calling a model, calling tools, possibly delegating - and emits a stream of events describing what happened.

An agent is a **definition** - who it is, what it may do - plus a **run** that answers for it. The definition is inert on its own, so the same one can be driven by any of the three runners below.

## The Contract

Every agent, however built, is this:

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` |  | Stable identifier; referenced by AgAiThreadSummary.agentId. |
| `name` | `string` |  | Name a reader sees for this agent - in the chat panel's roster, and on the step where another agent hands work to it. Defaults to AgAiAgentDefinition.id, which is rarely what a reader wants to read. |
| `description` | `string` |  | Short description shown to a delegating agent when this agent is a delegation target, so it can decide when to hand off. |
| `schema` | `Function` |  | Builds the shape validating this agent's delegation parameters. Return `s.undefined()` for a param-less agent, or e.g. `s.object({ region: s.string() })` for one invoked with parameters. A delegating agent's `delegate_to` tool advertises these so it can supply typed params. |
| `instructions` | `Function` |  | System instructions for a run. Re-resolved as the run progresses rather than read once, so a prompt built from the current schema or widget catalogue stays current. |
| `tools` | `Function` |  | The tools this agent has. Re-resolved as the run progresses, because a tool's advertised schema reads live dashboard state and may be uncallable on any given turn. Who executes them depends on the loop: directLlmRunner and clientToolRunner both run them for you, while an agent with its own `run` executes them itself through AgAiTool.execute. |
| `defaultToolCalls` | `Function` |  | Tools called automatically before the first turn; their results seed the conversation so the agent starts with that context. `args` are the tool's full call arguments, exactly as its schema expects them. Only run by a loop Studio owns. |
| `maxTurns` | `number` |  | How many rounds this agent may take before the run is stopped, as a backstop against one that never settles. Defaults to 100, which suits a conversational agent; raise it for one that works through a long list, lower it for one expected to answer in a couple of rounds. Only a loop Studio owns counts rounds, so an agent with its own `run` decides for itself. |

`id` names it. `description` is shown to a *delegating* agent so it can decide when to hand off. `schema` declares the parameters it can be delegated with. `instructions` and `tools` are callbacks, re-resolved as a run progresses. `maxTurns` caps how many rounds Studio will run before it stops.

A definition plus a `run` is an `AgAiAgentRunner`, which is what the harness takes. The runner supplies the `run`.

## The Three Runners

There are three things you can bring, so there are three runners, divided by who answers each turn and who executes the tools.

### Direct LLM Runner

You bring a model. Studio owns everything else: the turn loop, the conversation, the prompt, and executing the agent's tools.

```ts
directLlmRunner({
    id: 'analyst',
    adapter: myAdapter,
    instructions: () => 'You build dashboards for a team of climate analysts.',
    tools: () => [studio.viewSchema(), studio.executeQuery(), studio.addWidget()],
});
```

Start here. "Direct" refers to the absence of an intermediate agent, not to Studio calling a provider itself: every call goes through the [adapter](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-direct-llm-runner/#the-adapter) you supply. See [Direct LLM Runner](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-direct-llm-runner/).

### Client Tool Runner

You bring a request. Studio calls it once per round, executes every tool call your answer left unresolved, appends the results, and calls you again until nothing is outstanding.

```ts
clientToolRunner({
    id: 'analyst',
    run: (input, ctx) => myServer.stream(input, { signal: ctx.signal }),
    tools: () => [studio.addWidget(), studio.configureWidget({ widgetType, widgetId })],
});
```

Use it when the turns come from somewhere that cannot reach into the page. See [Client Tool Runner](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-client-tool-runner/).

### Custom Runner

You bring a whole loop, one that already resolves its own tool calls. There is no factory for it, because Studio adds nothing:

```ts
{
    id: 'analyst',
    tools: () => [studio.addWidget()],
    run: myOwnLoop,
}
```

The run boundaries, the round loop and the telemetry are all yours to emit. See [Custom Runner](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-custom-runner/), which also covers wrapping an agent SDK.

### Which To Use

|  | You bring | Studio runs the rounds | Studio executes tools |
| --- | --- | --- | --- |
| `directLlmRunner` | A model | Yes | Yes |
| `clientToolRunner` | A request | Yes | Any your answer left open |
| `{ ...def, run }` | A loop | No | No |

One rule covers the first two: Studio executes every tool call the turn did not resolve itself. A model never resolves one; a request whose far side owns some tools resolves those and leaves the rest.

## AG-UI Compatibility

Whatever the runner, an agent answers in events, and that vocabulary is [AG-UI](https://docs.ag-ui.com). Studio's `AgAiEvent` declarations mirror the AG-UI wire shapes: `RUN_STARTED`, `TEXT_MESSAGE_CONTENT`, `TOOL_CALL_START` and the rest carry the same discriminator and the same fields. An AG-UI-native agent or server therefore drives a [Client Tool Runner](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-client-tool-runner/) or a [Custom Runner](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-custom-runner/) with no translation layer in between, and an [Adapter](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-direct-llm-runner/#stream-events) emits the content half of the same vocabulary.

Those declarations are hand-authored rather than imported. AG Studio ships no third-party runtime dependencies, so there is no `@ag-ui/*` package to install and nothing to keep in step at install time.

> **Note**
>
> The two are structurally compatible, not guaranteed identical. AG-UI can add or change an event that Studio's types have not yet followed, so treat `AgAiEvent` as what Studio reads and check it against the protocol when you rely on a newer event. An event Studio does not declare can still travel as `RAW`, which carries the source event untouched, or as `CUSTOM`, which carries a name and a value of your own.

## Agents Are Stateless

An agent holds no conversation state. Instructions, tools and delegation parameters are all resolved per run, from callbacks:

```ts
directLlmRunner({
    id: 'analyst',
    adapter: myAdapter,
    // Re-read on every run, so a new field or widget shows up without rebuilding the agent.
    instructions: () => `Available data:\n${describe(api.getAiContext().schema())}`,
    tools: () => [studio.viewSchema(), studio.executeQuery()],
});
```

> **Warning**
>
> Do not snapshot instructions or tools into a variable at construction time. A static tool array freezes live enums: a widget added after construction will not appear in a tool's schema, and a delegate list built once will not include agents registered later.

## Rosters and Delegation

The harness holds the agents. One is `primary`: the agent a new thread starts from, and the only one the user picks directly. The rest are delegate-only, reached through `delegate_to`.

```ts
createAiHarness(api, ({ tools }) => ({
    agents: [lead, dataAgent, widgetAgent],
    primary: 'lead',
}));
```

A delegating agent lists `tools.delegateTo(['data', 'widget'])`. That tool's schema enumerates each target's `schema` parameters, resolved per turn, and the tool is withheld entirely on a turn where no target resolves. Calling it starts a **child run** in its own thread, which reports back through a `complete_task` tool that Studio appends automatically. The chat panel renders a child run inline, expandable.

## Next

- [Built-in Agents](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-builtin-agents/) - the team Studio ships
- [Agent Configuration](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-custom-agents/) - reshaping them
- [Agent Context](https://www.ag-grid.com/studio/archive/3.0.0/react/ai-context/) - what an agent reads before it acts
