> For the complete documentation index, see [llms.txt](/llms.txt). Every page on this site is also available as markdown at `<path>.md`.

# Agents

The harness exposes four agent roles, each constructed with a dedicated `define_*` helper that
returns a frozen [`AgentSpec`](/docs/reference/runtime#flowai_harness.runtime.AgentSpec).

```python
from flowai_harness import (
    define_coordinator,
    define_executor,
    define_planner,
    define_specialist,
)

coordinator = define_coordinator(
    name="scenario_coordinator",
    model="claude-sonnet-4-6",
    routes=["scenario_planner", "scenario_executor"],
    approval={"plans": "always", "tools": "never"},
    prompt="Route plan-building to the planner and execution to the executor.",
)

planner = define_planner(
    name="scenario_planner",
    model="claude-sonnet-4-6",
    plan=scenario_plan,
    prompt="You produce typed scenario plans.",
)

executor = define_executor(
    name="scenario_executor",
    model="claude-sonnet-4-6",
    plan=scenario_plan,
    tools=[search_products],
    prompt="You execute approved scenario plans action by action.",
)

specialist = define_specialist(
    name="product_insights",
    model="claude-haiku-4-5",
    tools=[search_products],
    prompt="You answer focused product questions.",
)
```

## The four roles

- **Coordinator** — the top-level orchestrator. Receives user prompts, decides which subordinate
  agent should handle each step, and surfaces approval gates. A runtime can contain at most one
  coordinator and it must declare at least one `routes` target. Stateful by default.
- **Planner** — produces structured plan instances constrained by a [`PlanSpec`](/docs/concepts/plans).
  Stateful by default, but can be opted out with `stateful=False`.
- **Executor** — consumes an approved plan and runs tools to enact each action. Carries the same
  `plan` schema as the planner so its inputs are typed. Stateless by default, but can opt in with
  `stateful=True`.
- **Specialist** — a focused, directly-invokable helper that the runtime invokes through
  [`Runtime.run_specialist(...)`](/docs/concepts/runtime). Specialists are stateless by default, can opt in with
  `stateful=True`, and do not appear in coordinator routing unless you add them explicitly.

## Required and optional fields

| Helper | Required | Optional |
| --- | --- | --- |
| `define_coordinator` | `name`, `model`, `prompt`, `routes` | `approval`, `tool_approvals`, `stateful`, `max_turns`, `tools`, `toolkits` |
| `define_planner` | `name`, `model`, `prompt`, `plan` | `approval`, `tool_approvals`, `stateful`, `max_turns`, `tools`, `toolkits` |
| `define_executor` | `name`, `model`, `prompt`, `plan` | `approval`, `tool_approvals`, `stateful`, `max_turns`, `tools`, `toolkits` |
| `define_specialist` | `name`, `model`, `prompt` | `approval`, `tool_approvals`, `stateful`, `max_turns`, `tools`, `toolkits` |

`prompt` accepts either a plain string or a [`LayeredPrompt`](/docs/concepts/prompts) returned by
`layered_prompt(...)`. When you pass a `LayeredPrompt`, its deterministic cache key is attached
to the agent for the runtime's prompt cache.

Use `max_turns` to raise or lower one agent's LLM/tool-loop budget without changing the rest of
the runtime. Omit it to keep the interpreter default.

## Role-provided tools

Role constructors provide the built-in tools required for that role to work. Passing
`toolkits=[...]` adds extra built-in toolkits; it does not remove the role-required tools.

| Role | Built-in tools provided by default |
| --- | --- |
| Coordinator with `routes=[...]` | `call_agent` |
| Planner with `plan=...` | `storePlan`, `getPlan` |
| Executor with `plan=...` | `getPlan`, `executePlan`, `resolveRef`, `glimpseRef` |
| Specialist | none |

For example, adding catalog access to a planner keeps plan authoring available:

```python
planner = define_planner(
    name="scenario_planner",
    model="claude-sonnet-4-6",
    plan=scenario_plan,
    toolkits=["catalog"],
    prompt="Use the catalog, then store a typed scenario plan.",
)
```

The effective prompt-visible tools are the planner tools (`storePlan`, `getPlan`) plus the
catalog toolkit tools. Planner agents do not receive `executePlan` by default, and executor
agents do not receive `storePlan` by default. Executors also receive the reference helpers by
default so they can inspect typed handles before or around plan execution when needed.

## Model selection

`model` accepts a string, a mapping, or an explicit
[`ModelSpec`](/docs/reference/runtime#flowai_harness.runtime.ModelSpec) value:

```python
define_planner(name="scenario_planner", model="claude-sonnet-4-6", ...)
define_planner(name="scenario_planner", model={"id": "claude-sonnet-4-6", "provider": "anthropic"}, ...)
```

Per-agent `model` selects the model identifier; provider transport configuration (API key
environment variables, base URLs, retry policy) is set once on the runtime via
[`define_runtime(..., providers=...)`](/docs/concepts/runtime), not per agent.

## Approval policies

Approval policy is hierarchical:

1. The runtime has a default floor: plans require approval, tools do not.
2. A coordinator `approval={...}` becomes the runtime floor unless `define_runtime(..., approval_policies=...)` is supplied explicitly.
3. Any agent can pass `approval={...}` to override the plan/tool defaults for that agent.
4. Any agent can pass `tool_approvals={tool_name: rule}` to override one tool under that agent.
5. A Python `define_tool(..., approval=...)` also becomes a tool override, scoped only to agents that bind that tool.

```python
define_coordinator(
    name="scenario_coordinator",
    model="claude-sonnet-4-6",
    routes=["scenario_planner", "scenario_executor"],
    approval={"plans": "always", "tools": "never"},
    prompt="...",
)

executor = define_executor(
    name="scenario_executor",
    model="claude-sonnet-4-6",
    plan=scenario_plan,
    approval={"plans": "never", "tools": "never"},
    tool_approvals={"execute_query": "always"},
    prompt="...",
)
```

Allowed values for each channel:

- `"always"` — every action of this kind requires explicit approval.
- `"never"` — no approval gate.
- `"default"` — leave the runtime default for this channel unchanged.
- `{"kind": "dynamic", "value": "<predicate-id>"}` — call a registered Python predicate.

<Callout type="info" title="Approval is enforced in Rust">

The Python `approval` field is metadata only. The Rust runtime (`flowai-runtime`) owns the
gate: it pauses the loop, emits an `approval_required` event, and waits for
`runtime.respond_to_approval(...)` before continuing.

</Callout>

## See also

- [Plans](/docs/concepts/plans) — the schema attached to planners and executors.
- [Tools](/docs/concepts/tools) — handlers wired into executors and specialists.
- [Prompts](/docs/concepts/prompts) — the recommended way to build the `prompt` argument.
- [`AgentSpec` reference](/docs/reference/runtime#flowai_harness.runtime.AgentSpec)
