> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instructorphp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 14 agent templates

## Introduction

Agent templates let you define agents as data rather than PHP code. Instead of writing a class that constructs an `AgentBuilder` with hardcoded capabilities and tools, you describe the agent's identity, instructions, tool access, and resource budget in a definition file. At runtime, a factory turns that definition into a working `AgentLoop` and `AgentState`.

This separation between definition and instantiation makes it possible to manage agents through configuration files, version them alongside your prompts, and let non-developers create or adjust agents without touching PHP. It is also the foundation of the subagent system -- when a parent agent spawns a child, it looks up the child's `AgentDefinition` in a registry and builds a loop from it on the fly.

## AgentDefinition

`AgentDefinition` is the core data object that describes an agent. It is a `final readonly` class with the following fields:

| Field          | Type                      | Required | Description                                                                                                                         |
| -------------- | ------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `name`         | `string`                  | Yes      | Unique identifier used to look up the agent in registries                                                                           |
| `description`  | `string`                  | Yes      | Human-readable summary of what the agent does. Also shown in tool schemas when the agent is available as a subagent.                |
| `systemPrompt` | `string`                  | Yes      | The system prompt that instructs the agent's behavior                                                                               |
| `label`        | `string\|null`            | No       | Display name (defaults to `name` if omitted)                                                                                        |
| `llmConfig`    | `LLMConfig\|string\|null` | No       | LLM configuration. Pass a string like `'anthropic'` for just the driver name, or a full `LLMConfig` object for model-level control. |
| `capabilities` | `NameList`                | No       | Named capabilities to activate (looked up in `AgentCapabilityRegistry`)                                                             |
| `tools`        | `NameList\|null`          | No       | Allow-list of tool names. `null` means inherit all available tools.                                                                 |
| `toolsDeny`    | `NameList\|null`          | No       | Deny-list of tool names to exclude from the inherited or allowed set                                                                |
| `skills`       | `NameList\|null`          | No       | Named skills to inject into the agent's context                                                                                     |
| `budget`       | `ExecutionBudget\|null`   | No       | Resource limits: max steps, tokens, seconds, cost, and deadline                                                                     |
| `metadata`     | `Metadata\|null`          | No       | Arbitrary key-value data merged into the agent's state                                                                              |

### Creating Definitions in PHP

```php theme={null}
use Cognesy\Agents\Collections\NameList;
use Cognesy\Agents\Data\ExecutionBudget;
use Cognesy\Agents\Template\Data\AgentDefinition;
use Cognesy\Polyglot\Inference\Config\LLMConfig;

$definition = new AgentDefinition(
    name: 'researcher',
    description: 'Searches for information on a topic and summarizes findings',
    systemPrompt: 'You are a research assistant. Find and summarize information accurately.',
    label: 'Research Agent',
    llmConfig: LLMConfig::fromArray([
        'driver' => 'anthropic',
        'model' => 'claude-sonnet-4-20250514',
    ]),
    budget: new ExecutionBudget(maxSteps: 10, maxTokens: 8000),
    tools: new NameList('bash', 'read_file'),
    toolsDeny: new NameList('write_file'),
    capabilities: new NameList('use_bash'),
);
// @doctest id="51ac"
```

### Tool Visibility Rules

The `tools` and `toolsDeny` fields work together to control which tools the agent can access:

* **`tools: null`** (the default) -- the agent inherits all tools available in its context. For subagents, this means all tools the parent has.
* **`tools: new NameList('read_file', 'bash')`** -- only these named tools are allowed. Any other tools are excluded.
* **`toolsDeny: new NameList('write_file')`** -- these tools are removed from whatever set the agent would otherwise have, whether inherited or explicitly allowed.

The deny list is applied after the allow list. If you set `tools` to allow `read_file` and `write_file`, and `toolsDeny` to deny `write_file`, the agent will only have access to `read_file`.

### ExecutionBudget

The `ExecutionBudget` class defines resource limits for a single agent execution. All fields are optional -- `null` means unlimited.

```php theme={null}
use Cognesy\Agents\Data\ExecutionBudget;

$budget = new ExecutionBudget(
    maxSteps: 10,         // maximum number of agent loop iterations
    maxTokens: 8000,      // total token usage across all steps
    maxSeconds: 60.0,     // wall-clock time limit
    maxCost: 0.50,        // maximum cost in dollars
    deadline: new DateTimeImmutable('2025-12-31'),
);
// @doctest id="43ee"
```

When an `AgentDefinition` declares a budget, it is translated into `UseGuards` during loop instantiation.

## Definition Files

Agent definitions can be stored in markdown, YAML, or JSON files. Each format maps directly to the `AgentDefinition` fields.

### Markdown Format

Markdown definitions use YAML front matter for structured fields and the document body for the system prompt. This is the most readable format for agents with long or complex system prompts.

```markdown theme={null}
---
name: researcher
description: Searches for information on a topic
label: Research Agent
llmConfig:
  driver: anthropic
  model: claude-sonnet-4-20250514
budget:
  maxSteps: 10
  maxTokens: 8000
tools:
  - bash
  - read_file
toolsDeny:
  - write_file
capabilities:
  - use_bash
metadata:
  domain: research
  version: "2.0"
---

You are a research assistant. Your job is to find and summarize information accurately.

When given a topic, use the available tools to gather evidence, then synthesize your findings
into a clear, well-structured summary. Always cite the sources you used.
// @doctest id="ba2c"
```

The document body (everything after the front matter) becomes the `systemPrompt` field.

### YAML Format

```yaml theme={null}
name: researcher
description: Searches for information on a topic
systemPrompt: |
  You are a research assistant. Find and summarize information accurately.
  Always cite the sources you used.
llmConfig:
  driver: anthropic
budget:
  maxSteps: 10
  maxTokens: 8000
tools:
  - bash
  - read_file
# @doctest id="6311"
```

### JSON Format

```json theme={null}
{
  "name": "researcher",
  "description": "Searches for information on a topic",
  "systemPrompt": "You are a research assistant. Find and summarize information accurately.",
  "llmConfig": {
    "driver": "anthropic"
  },
  "budget": {
    "maxSteps": 10,
    "maxTokens": 8000
  },
  "tools": ["bash", "read_file"]
}
// @doctest id="fd8e"
```

All three formats produce identical `AgentDefinition` objects when loaded.

## Loading Definitions

### AgentDefinitionLoader

The `AgentDefinitionLoader` class parses a single file into an `AgentDefinition`. It selects the appropriate parser based on the file extension.

```php theme={null}
use Cognesy\Agents\Template\AgentDefinitionLoader;

$loader = new AgentDefinitionLoader();
$definition = $loader->loadFile('/path/to/researcher.md');
// @doctest id="90b5"
```

Supported extensions: `.md`, `.json`, `.yaml`, `.yml`. The loader throws a `RuntimeException` if the file cannot be read and an `InvalidArgumentException` for unsupported extensions.

You can also supply custom parsers by passing an array to the constructor:

```php theme={null}
use Cognesy\Agents\Template\Parsers\MarkdownDefinitionParser;
use Cognesy\Agents\Template\Parsers\JsonDefinitionParser;
use Cognesy\Agents\Template\Parsers\YamlDefinitionParser;

$loader = new AgentDefinitionLoader([
    'md' => new MarkdownDefinitionParser(),
    'json' => new JsonDefinitionParser(),
    'yaml' => new YamlDefinitionParser(),
    'yml' => new YamlDefinitionParser(),
]);
// @doctest id="3d2a"
```

### AgentDefinitionRegistry

The `AgentDefinitionRegistry` is a named collection of agent definitions. It supports programmatic registration, file loading, directory scanning, and auto-discovery.

```php theme={null}
use Cognesy\Agents\Template\AgentDefinitionRegistry;

$registry = new AgentDefinitionRegistry();
// @doctest id="47f8"
```

#### Programmatic Registration

```php theme={null}
$registry->register($definition);
$registry->registerMany($def1, $def2, $def3);
// @doctest id="444d"
```

#### Loading from Files

```php theme={null}
// Load a single file
$registry->loadFromFile('/agents/researcher.md');

// Load all definition files from a directory
$registry->loadFromDirectory('/agents');

// Load recursively, scanning subdirectories
$registry->loadFromDirectory('/agents', recursive: true);
// @doctest id="6c88"
```

During directory scans, files that fail to parse are skipped rather than causing exceptions. The errors are collected and can be inspected afterward:

```php theme={null}
$errors = $registry->errors();
// Returns: ['path/to/broken.md' => 'Error message', ...]
// @doctest id="30b3"
```

#### Auto-Discovery

The `autoDiscover()` method scans up to three standard locations for agent definition files:

```php theme={null}
$registry->autoDiscover(
    projectPath: '/my/project',      // scans /my/project/.claude/agents
    packagePath: '/package/agents',   // scans this directory directly
    userPath: '/user/agents',         // scans this directory directly
);
// @doctest id="f46a"
```

Paths are scanned in order: `userPath`, `packagePath`, then `projectPath/.claude/agents`. Later registrations overwrite earlier ones with the same name, so user-level definitions take precedence over package defaults.

#### Querying the Registry

```php theme={null}
$definition = $registry->get('researcher');    // throws AgentNotFoundException if missing
$exists = $registry->has('researcher');        // returns bool
$names = $registry->names();                   // returns ['researcher', 'reviewer', ...]
$count = $registry->count();                   // returns int
$all = $registry->all();                       // returns ['name' => AgentDefinition, ...]
// @doctest id="6785"
```

## Instantiation Factories

Once you have an `AgentDefinition`, two factory classes turn it into runnable components: one for the initial `AgentState`, and one for the `AgentLoop` that executes it.

### DefinitionStateFactory

Creates an `AgentState` pre-configured with the definition's system prompt, metadata, and LLM config. It implements the `CanInstantiateAgentState` contract.

```php theme={null}
use Cognesy\Agents\Template\Factory\DefinitionStateFactory;

$factory = new DefinitionStateFactory();
$state = $factory->instantiateAgentState($definition);
// @doctest id="67e3"
```

You can also pass a seed state to merge the definition's settings onto an existing state:

```php theme={null}
$existingState = AgentState::empty()->withUserMessage('Start here');
$state = $factory->instantiateAgentState($definition, seed: $existingState);
// @doctest id="c977"
```

The factory applies settings in this order: system prompt, metadata merge, then LLM config. Each step is skipped if the corresponding field in the definition is empty or null.

### DefinitionLoopFactory

Creates a fully configured `AgentLoop` from a definition. This factory implements `CanInstantiateAgentLoop` and is used internally by `SendMessage` and other session actions.

```php theme={null}
use Cognesy\Agents\Capability\AgentCapabilityRegistry;
use Cognesy\Agents\Capability\Bash\UseBash;
use Cognesy\Agents\Template\Factory\DefinitionLoopFactory;

$capabilities = new AgentCapabilityRegistry();
$capabilities->register('use_bash', new UseBash());

$factory = new DefinitionLoopFactory($capabilities);
$loop = $factory->instantiateAgentLoop($definition);
// @doctest id="07ba"
```

The factory builds the loop by applying the definition's fields in order:

1. **LLM config** -- if the definition specifies an `llmConfig`, a `ToolCallingDriver` is created with that config.
2. **Guards** -- if the definition declares a non-empty budget, `UseGuards` is applied with the budget's limits.
3. **Capabilities** -- each named capability in the definition is resolved from the `AgentCapabilityRegistry` and applied to the builder.
4. **Tools** -- if the definition references named tools, they are resolved from the tool registry and added via `UseTools`.

#### Providing a Tool Registry

When the definition references tools by name (via `tools` or `toolsDeny`), you must provide a tool registry that implements `CanManageTools`:

```php theme={null}
use Cognesy\Agents\Tool\ToolRegistry;

$tools = new ToolRegistry();
$tools->register($searchTool);
$tools->register($readFileTool);

$factory = new DefinitionLoopFactory(
    capabilities: $capabilities,
    tools: $tools,
);
// @doctest id="6b49"
```

If a definition references tools and no registry is provided, `DefinitionLoopFactory` throws an `InvalidArgumentException`. Unknown tool names also cause an exception, listing which tools could not be found.

#### Event Propagation

Pass an event handler to propagate events from instantiated loops to a parent dispatcher:

```php theme={null}
use Cognesy\Events\Dispatchers\EventDispatcher;

$events = new EventDispatcher('session');
$factory = new DefinitionLoopFactory($capabilities, $tools, $events);
// @doctest id="f4e3"
```

## AgentCapabilityRegistry

The `AgentCapabilityRegistry` maps string names to capability instances. It is the bridge between definition files (which reference capabilities by name) and the PHP capability classes that implement them.

```php theme={null}
use Cognesy\Agents\Capability\AgentCapabilityRegistry;
use Cognesy\Agents\Capability\Bash\UseBash;
use Cognesy\Agents\Capability\File\UseFileTools;

$capabilities = new AgentCapabilityRegistry();

// Register a pre-built instance
$capabilities->register('use_bash', new UseBash());

// Register a factory for lazy instantiation
$capabilities->registerFactory('use_file_tools', fn() => new UseFileTools('/my/project'));

// Query the registry
$capabilities->has('use_bash');     // true
$capabilities->get('use_bash');     // returns the UseBash instance
$capabilities->names();             // ['use_bash', 'use_file_tools']
$capabilities->count();             // 2
// @doctest id="c48a"
```

Factory-registered capabilities are instantiated on first access and cached for subsequent lookups. If the factory does not return a `CanProvideAgentCapability`, an `InvalidArgumentException` is thrown.

### Opt-in Composer manifest discovery

Installed packages can expose zero-configuration capabilities and tools through
their `composer.json`:

```json theme={null}
{
  "extra": {
    "cognesy-agents": {
      "capabilities": {
        "my-capability": "Vendor\\Package\\UseMyCapability"
      },
      "tools": {
        "my-tool": "Vendor\\Package\\MyTool"
      }
    }
  }
}
// @doctest id="b9cb"
```

Discovery is deliberately explicit. Enabling it lets installed third-party
packages register executable classes, so applications should opt in only for a
trusted dependency set:

```php theme={null}
use Cognesy\Agents\Capability\AgentCapabilityRegistry;
use Cognesy\Agents\Discovery\CapabilityDiscovery;
use Cognesy\Agents\Tool\ToolRegistry;

$capabilities = new AgentCapabilityRegistry();
$tools = new ToolRegistry();

$result = CapabilityDiscovery::discover($capabilities, $tools);
// @doctest id="7092"
```

Discovery parses metadata and registers lazy factories; it does not instantiate
contributed classes. Malformed manifest declarations are returned by
`$result->errors()`. Missing classes, wrong interfaces, and constructors
requiring arguments fail only when that specific registry entry is resolved.
Root-package mappings override vendor mappings. For configured capabilities or
tools, register an application factory directly instead of using the manifest.

## Serializing and safely persisting definitions

`AgentDefinitionSerializer` renders the same canonical definition as Markdown,
YAML, or JSON. Canonicalization normalizes fallback labels and empty optional
values so parsing a serialized definition preserves every meaningful field.

```php theme={null}
use Cognesy\Agents\Template\AgentDefinitionSerializer;

$serializer = new AgentDefinitionSerializer();
$markdown = $serializer->toMarkdown($definition);
$yaml = $serializer->toYaml($definition);
$json = $serializer->toJson($definition);
// @doctest id="8223"
```

Use `FileAgentDefinitionStore` for persistence policy. It accepts an existing
writable root, derives the filename from a validated agent name, writes
atomically, and refuses overwrite unless it is explicitly requested. It never
accepts a caller-provided path.

`UseAgentDefinitions` exposes `list_agents` and `read_agent` by default. Supply
a store explicitly to add `write_agent`; this is the only mode that grants the
agent filesystem mutation authority:

```php theme={null}
use Cognesy\Agents\Capability\Definitions\UseAgentDefinitions;
use Cognesy\Agents\Template\AgentDefinitionValidator;
use Cognesy\Agents\Template\FileAgentDefinitionStore;

$validator = new AgentDefinitionValidator($capabilities, $tools);
$readOnly = new UseAgentDefinitions($definitions, $validator);

$writable = new UseAgentDefinitions(
    $definitions,
    $validator,
    new FileAgentDefinitionStore('/dedicated/agent-definitions'),
);
// @doctest id="91fa"
```

`write_agent` validates the complete definition before touching disk, refuses
replacement by default, and refreshes the definition registry after a successful
save. It does not mutate or reload an already-running agent loop.

## Using with Subagents

The `AgentDefinitionRegistry` implements `CanManageAgentDefinitions`, making it the standard provider for the `UseSubagents` capability. When a parent agent calls `spawn_subagent`, the subagent system looks up the named definition in this registry and builds a child loop from it.

```php theme={null}
use Cognesy\Agents\Builder\AgentBuilder;
use Cognesy\Agents\Capability\Subagent\UseSubagents;
use Cognesy\Agents\Template\AgentDefinitionRegistry;

$registry = new AgentDefinitionRegistry();
$registry->loadFromDirectory('/agents');

$agent = AgentBuilder::base()
    ->withCapability(new UseSubagents(provider: $registry))
    ->build();
// @doctest id="edb1"
```

The subagent tool's schema automatically includes the list of available agents and their descriptions, so the LLM knows which subagents it can delegate to. See [Subagents](15-subagents) for the full delegation model.

## Serialization

`AgentDefinition` supports round-trip serialization via `toArray()` and `fromArray()`:

```php theme={null}
$array = $definition->toArray();
$restored = AgentDefinition::fromArray($array);
// @doctest id="14ab"
```

This is used internally by the session persistence layer to store agent definitions alongside session state. The `fromArray()` method also accepts `title` as an alias for `label` to support legacy formats.

## Related

* [AgentBuilder & Capabilities](13-agent-builder)
* [Subagents](15-subagents)
* [Session Runtime](16-session-runtime)
