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

# YAML Configuration Reference

> Complete reference for agents.yaml and workflow.yaml configuration options

Complete reference for all configuration options in `agents.yaml` and `workflow.yaml` files.

## Quick Start

<Steps>
  <Step title="Load an agent from YAML">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent

    agent = Agent(name="yaml-runner", instructions="Behaviour defined in agents.yaml")
    agent.start("Run the workflow defined in YAML")
    ```
  </Step>

  <Step title="Run the YAML workflow">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai start agents.yaml
    ```

    The user maintains YAML config; the agent loads it and executes the requested workflow.
  </Step>
</Steps>

<Info>
  New `agents.yaml` files scaffolded by `praisonai --init` include a `# yaml-language-server: $schema=…` header on line 1, so any editor with a YAML language server (VS Code + Red Hat YAML, JetBrains, Neovim + `yaml-language-server`) gives you autocomplete and inline validation immediately. See [Editor Autocomplete](/docs/features/editor-autocomplete) to wire it into an existing file.
</Info>

<Tip>
  Want a minimal, runnable sequential team? Copy [`examples/yaml/teams/research-writer/`](https://github.com/MervinPraison/PraisonAI/tree/main/examples/yaml/teams/research-writer) — a two-agent (`researcher` → `writer`) YAML you run with `praisonai agents.yaml`.
</Tip>

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    subgraph "Configuration Files"
        A["📄 agents.yaml"]
        W["📄 workflow.yaml"]
    end
    
    subgraph "Shared Features"
        AG["👤 Agents"]
        ST["📋 Steps"]
        VR["📦 Variables"]
        TL["🔧 Tools"]
    end
    
    subgraph "Execution"
        EX["⚡ Execute"]
    end
    
    A --> AG
    A --> ST
    A --> VR
    A --> TL
    W --> AG
    W --> ST
    W --> VR
    W --> TL
    AG --> EX
    ST --> EX
    
    classDef config fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef feature fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef exec fill:#10B981,stroke:#7C90A0,color:#fff
    class A,W config
    class AG,ST,VR,TL feature
    class EX exec
```

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant YamlConfigurationReference

    User->>Agent: Request
    Agent->>YamlConfigurationReference: Process
    YamlConfigurationReference-->>Agent: Result
    Agent-->>User: Response
```

<Info>
  **Both files are fully compatible!** PraisonAI accepts both `agents.yaml` and `workflow.yaml` with the same features. The difference is primarily in naming conventions.
</Info>

## Quick Comparison

<Tabs>
  <Tab title="agents.yaml Style">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    framework: praisonai
    topic: "Research AI trends"

    roles:
      researcher:
        role: Research Analyst
        backstory: "Expert researcher"
        goal: Research topics
        tools:
          - tavily_search

      researcher:
        tasks:
          research_task:
            description: "Research {{topic}}"
            expected_output: "Research report"
    ```
  </Tab>

  <Tab title="workflow.yaml Style (Canonical)">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    name: Research Workflow
    input: "Research AI trends"

    agents:
      researcher:
        role: Research Analyst
        instructions: "Expert researcher"
        goal: Research topics
        tools:
          - tavily_search

    steps:
      - agent: researcher
        action: "Research {{input}}"
        expected_output: "Research report"
    ```
  </Tab>
</Tabs>

***

## Field Name Mapping

PraisonAI accepts both old and new field names. **Use canonical names for new projects.**

| Canonical (Recommended) | Alias (Also Works) | Purpose |
| - | - | - |
| `agents` | `roles` | Define agent personas |
| `instructions` | `backstory` | Agent behavior/persona |
| `action` | `description` | What the step does |
| `steps` | `tasks` (nested in roles) | Define work items |
| `name` | - | Workflow identifier |
| `input` | `topic` | Data passed INTO the workflow |

<Tip>
  **A-I-G-S Mnemonic** - Easy to remember:

  * **A**gents - Who does the work
  * **I**nstructions - How they behave
  * **G**oal - What they achieve
  * **S**teps - What they do
</Tip>

<Note>
  The validator automatically normalises these aliases — `agents`↔`roles`, `topic`↔`input`, and `stream`↔`streaming` are all accepted and converted to their canonical form. You can mix old and new names freely.

  `instructions` is normalised to `backstory` at **two points**: once at YAML load (`_normalize_yaml_config`) and once at schema validation — so downstream code that reads `backstory` works correctly regardless of which input shape you use.
</Note>

***

## List vs. Dict Shape

PraisonAI auto-normalises list-form `agents`, `roles`, and `tasks` into dict form on load — you don't need to convert legacy configs by hand.

<Tabs>
  <Tab title="List form (e.g. imported from CrewAI-style YAML)">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    agents:
      - name: researcher
        role: Research Analyst
        instructions: "Expert researcher"

    tasks:
      - name: research_task
        agent: researcher
        description: "Research {{topic}}"
    ```
  </Tab>

  <Tab title="Dict form (canonical — recommended for new projects)">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    agents:
      researcher:
        role: Research Analyst
        instructions: "Expert researcher"
        tasks:
          research_task:
            description: "Research {{topic}}"
    ```
  </Tab>
</Tabs>

<Note>
  When a top-level `tasks:` list is normalised, each task is attached to its named agent's `tasks:` map. A task whose `agent:` key doesn't match any defined agent is **logged as a warning and skipped** — the run continues rather than crashing.

  Duplicate agent/task names and unknown task→agent references are also caught during this normalisation on every `praisonai start` — both are warned about (and preserved with a `__dup_i` suffix) by default, or raise under `PRAISONAI_VALIDATE_STRICT=true`. See [Runtime YAML normalisation](/docs/docs/cli/validate#runtime-yaml-normalisation-normalize-yaml-config) for the full rules.
</Note>

***

## Empty `roles: {}` Is Treated as Missing

A YAML that carries an empty `roles: {}` alongside a populated `agents:` block (dict or list) and a list-form `tasks:` promotes the agents into roles and grafts the tasks — it does **not** run with zero agents.

This YAML now yields one role per agent with its tasks attached:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
framework: praisonai
topic: research renewable energy
roles: {}
agents:
  researcher:
    role: Renewable Energy Researcher
    goal: Find the latest solar breakthroughs
    backstory: A materials-science PhD.
tasks:
  - name: summarise
    agent: researcher
    description: Summarise the top 3 breakthroughs of 2026.
    expected_output: A 3-bullet summary.
```

<Note>
  `roles: {}`, `roles: null`, and a missing `roles:` all behave the same way: a populated `agents:` block is promoted into `roles:` and any list-form `tasks:` are grafted onto their agents. An empty `roles: {}` no longer shortcuts the promotion, so the run never silently ends with `Task completed.` and zero agents.

  Debugging a "0 agents" run? See [YAML Validation](/docs/features/yaml-validation).
</Note>

***

## Shorthand: `agents:` gets promoted to `roles:`

`agents.yaml` accepts two spellings of the same idea. The shorthand `agents:` bucket is merged into `roles:` before the workflow runs, so both forms produce the same result.

<Tabs>
  <Tab title="Canonical (roles:)">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    framework: praisonai
    roles:
      writer:
        role: "Writer"
        goal: "Draft clear content"
        tasks:
          draft:
            description: "Draft an intro for {{topic}}"
    ```
  </Tab>

  <Tab title="Shorthand (agents:) — same result">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    framework: praisonai
    agents:
      writer:
        role: "Writer"
        goal: "Draft clear content"
        tasks:
          draft:
            description: "Draft an intro for {{topic}}"
    ```
  </Tab>
</Tabs>

As of [PraisonAI PR #5307](https://github.com/MervinPraison/PraisonAI/pull/5307) the merge is **authoritative** — `_prepare_for_run` promotes the shorthand and then drops it so validators and the executor read a single source of truth:

* `agents:` is **deleted** after promotion, so downstream validators and the executor read the same map.
* When both spellings define the same key, the canonical `roles:` entry wins.
* `instructions:` inside an agent block is renamed to `backstory:` during promotion (existing behaviour, unchanged).

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "YAML normalization"
        Y1[📄 agents.yaml] --> P[⚙️ _prepare_for_run]
        P --> Merge[🔀 Merge agents: into roles:]
        Merge --> Drop[🗑️ Delete agents:]
        Drop --> Exec[▶️ Executor reads roles:]
    end

    classDef input fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff

    class Y1 input
    class P,Merge,Drop process
    class Exec result
```

<Note>
  Before PR #5307, `agents:` stayed populated after promotion. Validators saw a merged `{**roles, **agents}` view while the executor read only `roles:`, causing double-fired warnings, false-positive rejections of `cli_backend` / `tool_timeout`, and silently-dropped handoff targets on agents that never became roles. Deleting the shorthand fixes it — a single source of truth.
</Note>

**Precedence on conflict** — the canonical `roles:` entry wins and the `agents:` duplicate is ignored:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
framework: praisonai
roles:
  writer:
    role: "Senior Writer"          # ← wins
agents:
  writer:
    role: "Junior Writer"          # ← ignored (canonical roles: wins)
```

***

## Task Ordering in Roles-File YAML

Tasks in a roles-file YAML run in the order they are declared under each role and, across roles, in the order the roles appear.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "researcher"
        RT["research_task"]
    end
    subgraph "writer"
        ST["summary_task"]
    end
    RT --> ST

    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    class RT,ST process
```

The loader walks `roles:` top-to-bottom, and each role's `tasks:` top-to-bottom, appending every task to a single run list in that order.

<Warning>
  The optional top-level `dependencies:` block on a roles-file YAML is **documentary only** — the roles-file loader (`praisonai_adapter._build_agents_and_tasks`) does **not** consume it.

  To reorder execution on the roles-file path, **reorder the tasks in the YAML**. Do not add `dependencies:` and expect it to change execution order.

  `dependencies:` **is** honoured on the [workflow YAML path](/docs/docs/features/yaml-workflows) (`process: workflow`); the divergence is intentional and long-standing.
</Warning>

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
framework: praisonai
topic: "Latest AI research"

roles:
  researcher:
    role: Research Analyst
    goal: Gather findings on the topic
    backstory: Expert researcher.
    tasks:
      research_task:
        description: "Research {{topic}}"
        expected_output: "A list of key findings"

  writer:
    role: Technical Writer
    goal: Summarise the findings
    backstory: Clear, concise writer.
    tasks:
      summary_task:
        description: "Summarise the research findings"
        expected_output: "A short summary"

# documentary only — the roles-file loader does NOT read this block
dependencies:
  summary_task:
    - research_task
```

`research_task` runs first because `researcher` is declared before `writer`; `summary_task` runs second. The `dependencies:` block reflects that intent for readers but does not drive it.

Copy-paste starter: [`examples/yaml/teams/research-writer/`](https://github.com/MervinPraison/PraisonAI/tree/main/examples/yaml/teams/research-writer).

***

## Running a Roles-File YAML

A roles-file YAML runs as a positional argument to `praisonai`; from Python the equivalent is `praisonai.run(<path>)`.

<Tabs>
  <Tab title="CLI">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    export OPENAI_API_KEY=sk-...
    praisonai agents.yaml
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import praisonai

    praisonai.run("agents.yaml")
    ```
  </Tab>
</Tabs>

<Note>
  * `praisonai <path/to/agents.yaml>` — positional form, canonical for roles-file YAML. Dispatches to `AgentsGenerator.generate_crew_and_kickoff()`.
  * `praisonai run "<prompt>"` — modern per-prompt entry point (Typer). **Not** a YAML entry point; passing a `.yaml` path to `run` does **not** invoke the roles-file loader.
  * `praisonai.run("agents.yaml")` — Python API equivalent of the positional CLI form (same loader).
</Note>

See [CLI Reference](/docs/cli/cli-reference) for the full flag surface of the positional form, and [`praisonai run`](/docs/cli/run) for the per-prompt entry point.

***

## Automatic Field Validation

PraisonAI validates every field name in your `agents.yaml` before execution begins — unknown fields produce warnings and invalid configs abort immediately.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Y["📄 agents.yaml"] --> S["🔍 Schema Check"]
    S --> X["🔗 Cross-Ref Check"]
    X --> T["🔧 Tool Check"]
    T --> OK["✅ Run Workflow"]
    S --> ERR["❌ Abort (ValueError)"]
    X --> ERR
    T --> ERR

    style Y fill:#8B0000,stroke:#7C90A0,color:#fff
    style S fill:#189AB4,stroke:#7C90A0,color:#fff
    style X fill:#189AB4,stroke:#7C90A0,color:#fff
    style T fill:#189AB4,stroke:#7C90A0,color:#fff
    style OK fill:#10B981,stroke:#7C90A0,color:#fff
    style ERR fill:#EF4444,stroke:#7C90A0,color:#fff
```

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant Feature as YAML Configuration Reference

    User->>Agent: Request
    Agent->>Feature: Process request
    Feature-->>Agent: Result
    Agent-->>User: Response
```

<Warning>
  Configuration errors now **fail fast**. If any error is found (missing required field, bad cross-reference, unknown tool), the run aborts with a `ValueError` that lists every error at once — nothing runs with a broken config.

  Unknown-field **warnings** are still non-blocking: the unrecognised field is ignored and the workflow continues.
</Warning>

<Steps>
  <Step title="Create YAML with typo">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # agents.yaml
    framework: praisonai
    topic: "Summarize Python history"
    agents:
      researcher:
        role: Research Analyst
        goal: Provide a historical summary
        instrutions: "Focus ONLY on years 1989–2000."   # typo — warning
        backstory: Expert researcher.
    ```
  </Step>

  <Step title="Run workflow and see warning">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai start agents.yaml
    ```

    Output (unknown field → warning, workflow still runs):

    ```
    WARNING: Unknown agent field 'instrutions' in agent 'researcher'. This field will be ignored.
    ```
  </Step>

  <Step title="Validate before running">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai validate agents.yaml
    ```

    Catch all errors upfront without running the workflow.
  </Step>
</Steps>

### Fail-Fast Errors

These conditions abort the run immediately with an aggregated error message.

**Missing required field:**

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
agents:
  researcher:
    role: Research Analyst
    # goal is required but missing
    backstory: Expert researcher.
```

Result:

```
Configuration validation failed with 1 error(s):
  1. Agent 'researcher': missing required field 'goal'
```

**Bad cross-reference (task references undefined agent):**

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
agents:
  researcher:
    role: Research Analyst
    goal: Research topics
    backstory: Expert researcher.

tasks:
  write_report:
    description: Write a report
    agent: writer      # 'writer' is not defined!
```

Result:

```
Configuration validation failed with 1 error(s):
  1. Task 'write_report': references unknown agent 'writer' (not defined in agents/roles)
```

**Unknown tool:**

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
agents:
  researcher:
    role: Research Analyst
    goal: Research topics
    backstory: Expert researcher.
    tools:
      - nonexistent_tool
```

Result:

```
Configuration validation failed with 1 error(s):
  1. Unknown tool 'nonexistent_tool'. Ensure it's properly installed or defined in your configuration.
```

Optional tools with extra dependencies produce a **warning** instead:

```
WARNING: Tool 'BrowserTool' requires additional dependencies. Install with: pip install 'praisonai[tools]'
```

### Required Agent Fields

<Note>
  `role`, `goal`, and `backstory` are **schema-enforced required fields** for every agent. Omitting any of them aborts the run with a `ValueError`.
</Note>

The **editor** schema (used for [autocomplete](/docs/features/editor-autocomplete)) does *not* require these three fields — the runtime auto-fills `role`/`goal` from the agent key and maps `instructions` → `backstory`. The **strict validator** behind `praisonai validate` still requires them, so run `validate` before shipping even if your editor shows no errors.

### Recognized Fields

All recognized field names for agents in both `agents:` and `roles:` sections:

| Field | Type | Purpose |
| - | - | - |
| `role` | string | Agent's job title |
| `goal` | string | Agent's objective |
| `instructions` | string | Behavior instructions |
| `backstory` | string | Personality/background context |
| `tools` | array | List of tool names |
| `tasks` | object | Nested task definitions (legacy) |
| `llm` | string \| dict | Model to use |
| `function_calling_llm` | string \| dict | Model for tool calls |
| `allow_delegation` | bool | Allow task delegation |
| `max_iter` | int | Maximum iterations |
| `max_rpm` | int | Max requests per minute — auto-builds a `RateLimiter` when set; `max_rpm <= 0` raises `ValueError` |
| `max_execution_time` | int | Timeout in seconds |
| `verbose` | bool | Verbose output |
| `cache` | bool | Enable response caching |
| `system_template` | string | Custom system prompt |
| `prompt_template` | string | Custom prompt template |
| `response_template` | string | Custom response template |
| `tool_timeout` | int | Per-call tool execution timeout in seconds. Enforced at **two layers** when running `framework: praisonai`: (1) the wrapper wraps every tool callable in a timeout-enforcing shim that **raises `ToolTimeoutError`** (a `TimeoutError` subclass) on timeout — framework adapters translate it per framework — and (2) the SDK Agent's executor pool also honours the same value. Sync tools run in an instance-owned `ThreadPoolExecutor` on the wrapper layer; async tools use `asyncio.wait_for`. When agents declare different `tool_timeout` values, each agent's tools carry its own budget (per-agent wrap; shared tool objects). The shared-wrap fast path is used only when **every** agent declares `tool_timeout` **and** every declared value is identical. If any agent omits the field, the shared wrap is skipped and undeclared agents run with **no imposed timeout** (they do not inherit another agent's declared value). CLI `--tool-timeout` overrides all agents. See [Tool Timeouts](/docs/features/tool-timeout) for the precedence table. YAML `bool` values (`tool_timeout: yes`) are ignored. See [Concurrency → Effective Timeout Precedence](/docs/docs/features/concurrency#effective-timeout-precedence) and [Tool Configuration](/docs/configuration/tool-config) for the full contract. In Python this maps to `tool_config=ToolConfig(timeout=…)` — the standalone `tool_timeout=` kwarg on `Agent(...)` was removed. On `process: workflow` YAMLs an **explicit** `tool_timeout` (per-agent/role, or `--tool-timeout` different from the default 60) now **raises `ValueError`** — [PR #3963](https://github.com/MervinPraison/PraisonAI/pull/3963). Use `process: sequential`/`hierarchical` if you need it enforced. When a tool call exceeds the timeout the executor pool is recycled — the next call gets a fresh worker so a hung tool cannot progressively degrade throughput. See [PR #3960](https://github.com/MervinPraison/PraisonAI/pull/3960). |
| `tool_retry_policy` | object | Tool retry configuration with exponential backoff. See example below. |
| `planning_tools` | array | Tools for planning |
| `planning` | bool | Enable planning |
| `autonomy` | int (0-10) | Autonomy level. Mapped to core preset: 0=off, 1-3=suggest, 4-7=auto\_edit, 8-10=full\_auto. Also accepts `bool` / `str` preset / `dict` / `AutonomyConfig` (passed through unchanged). |
| `guardrails` | array | Guardrail functions |
| `streaming` | bool | Enable streaming |
| `stream` | bool | Alias for streaming |
| `approval` | bool | Require human approval |
| `skills` | array | Skill definitions |
| `runtime` | string / dict | Per-agent runtime override. Requires `framework: praisonai`. See [Runtime Selection](/docs/features/runtime-selection). |
| `cli_backend` | string / dict | **Deprecated** — use `runtime` or `models.<name>.runtime` instead. Still works through 2.0.0 with `DeprecationWarning`. Run `praisonai doctor fix --execute` (or `praisonai doctor runtime --fix --execute`) to migrate. See [Runtime Selection](/docs/features/runtime-selection). |
| `reflection` | bool/object | Reflection configuration |

<Note>
  **Framework note:** the agent-level fields `planning`, `reflection`, `guardrails`, `web`, `skills`, and `autonomy` are honored end-to-end on `framework: praisonai` as of the 2026-07-30 release ([PR #3517](https://github.com/MervinPraison/PraisonAI/pull/3517)). On older releases the wrapper accepted these keys in YAML but the native adapter silently dropped them — upgrade to the latest release for full parity.
</Note>

### Agent Behaviour Flags

Six per-agent keys map directly onto the matching `Agent(...)` kwargs. See [Agent Behaviour Flags in YAML](/docs/features/yaml-agent-flags) for the full mapping and [Autonomy](/docs/features/autonomy) for the level presets.

<Note>
  **`workflow.yaml` field wiring (PraisonAI [PR #5338](https://github.com/MervinPraison/PraisonAI/pull/5338)):** On the `workflow.yaml` / `YAMLWorkflowParser` path, the following per-agent fields now flow directly into the `Agent(...)` constructor and genuinely change agent behaviour: `planning`, `reasoning`, `allow_delegation`, `cache`, `max_iter`, `max_execution_time`, `reflect_llm` / `min_reflect` / `max_reflect`, and `system_template` / `prompt_template` / `response_template`. Older releases parsed these onto internal `_yaml_*` attributes that nothing read, so YAML `reasoning: true` or a custom `system_template:` was silently ignored. Upgrade to pick up the fix — no YAML changes required.

  **Backward-compatibility (PraisonAI [PR #5370](https://github.com/MervinPraison/PraisonAI/pull/5370)):** After PR #5338 wired `reflect_llm` / `min_reflect` / `max_reflect` directly into `Agent(...)`, the pre-consolidation attribute mirrors (`agent._yaml_reflect_llm`, `agent._yaml_min_reflect`, `agent._yaml_max_reflect`) briefly disappeared. PR #5370 restores them as an additive compatibility layer: the primary agent attributes (`agent.reflect_llm`, `agent.min_reflect`, `agent.max_reflect`) still carry the values through the constructor, and the `_yaml_*` mirrors are set right after construction so any downstream code or test that still reads the legacy names keeps working. **This is compatibility restoration, not a behaviour change** — end users writing agent-centric YAML need do nothing.
</Note>

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
roles:
  writer:
    role: "Writer"
    goal: "Draft and refine content"
    backstory: "Senior technical writer"
    planning: true          # enable planning
    reflection: true        # enable self-reflection
    guardrails: ["safe_output"]
    web: true               # enable web tools
    skills: ["research", "editing"]
    autonomy: 3             # -> suggest preset
```

The `autonomy` integer maps onto a core preset before it reaches `Agent(autonomy=…)`:

| YAML `autonomy:` value | Core `Agent(autonomy=…)` receives |
| - | - |
| `0` / omitted | off — nothing forwarded |
| `1` – `3` | `"suggest"` |
| `4` – `7` | `"auto_edit"` |
| `8` – `10` | `"full_auto"` |
| `bool` / `str` / `dict` / `AutonomyConfig` | passed through unchanged |

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
framework: praisonai
roles:
  coder:
    role: "Autonomous Coder"
    goal: "Complete coding tasks with minimal supervision"
    backstory: "Senior engineer"
    autonomy: 6            # -> "auto_edit" preset in core
    planning: true         # forwarded as-is
    reflection: true       # forwarded as-is
    web: true              # forwarded as-is
    skills: ["python", "testing"]
    guardrails: ["no_prod_writes"]
    tasks:
      write_tests:
        description: "Write pytest tests for {{topic}}"
        expected_output: "A file with pytest test cases"
```

Deep dives: [Planning](/docs/features/planning), [Reflection](/docs/features/reflection), [Guardrails](/docs/features/guardrails), [Web](/docs/features/web), [Skills](/docs/features/skills), [Autonomy](/docs/features/autonomy).

<Warning>
  **Behaviour change (PR #4477):** heterogeneous per-agent `tool_timeout` values are now honoured **per agent** instead of collapsing to the tightest value. If you set differing values across agents deliberately (e.g. 5s on a fast search agent and 60s on a slower research agent), each agent now keeps its own budget. A slow agent that previously inherited a fast agent's tighter budget (masking a real timeout) may now run longer. Uniform declarations are unaffected. (Earlier, PR #3176 had reversed the collapse from `max` to `min`.) PR #4468 additionally corrects a P1 edge case: a single declared `tool_timeout` on one agent no longer forces the other agents to inherit it via the shared-dict wrap.
</Warning>

<Warning>
  **`tool_timeout` is not enforced on `process: workflow` — and an explicit value now fails fast.** Per-tool timeouts apply only to `process: sequential` / `process: hierarchical`. On `process: workflow`, an **explicit** per-agent/role `tool_timeout` (or a **changed** `--tool-timeout` CLI value) now **raises** rather than silently doing nothing — pick `sequential` or `hierarchical` if you need per-tool timeouts. Leaving `tool_timeout` off, or the bare `--tool-timeout` default in place, is fine.

  Historical: PR [#3252](https://github.com/MervinPraison/PraisonAI/pull/3252) first added warning diagnostics; PR [#3963](https://github.com/MervinPraison/PraisonAI/pull/3963) upgraded them to fail-fast. See [tool\_timeout on workflow YAML](/docs/docs/features/yaml-workflows#tool_timeout-on-workflow-yaml).
</Warning>

### Unknown-Field Warnings

Unknown keys at the top level or in agent/role definitions produce warnings:

```
WARNING: Unknown agent field 'instrutions' in agent 'researcher'. This field will be ignored.
WARNING: Unknown agent field 'xyz_random_field' in agent 'test_agent'. This field will be ignored.
```

Warnings are **non-blocking** — the unrecognised field is ignored and the workflow continues. Only use `--strict`, `PRAISONAI_VALIDATE_STRICT=true`, or the per-run `strict_validation=True` override on `AgentsGenerator` (or `cli_config={"strict_validation": True}`) to promote them to errors.

<Tip>
  Set your log level to `WARNING` or below (`INFO`, `DEBUG`) to see these messages. They are emitted via the standard `praisonai` logger.
</Tip>

### Both Sections Covered

The validator inspects both `agents:` and `roles:` sections. The warning text changes from `agent 'X'` to `role 'X'` accordingly.

### Strict Mode

Promote all warnings to errors globally:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
export PRAISONAI_VALIDATE_STRICT=true
praisonai start agents.yaml
```

Strict mode also **raises** on duplicate agent/task `name` keys and unknown task→agent references. Without strict mode both are surfaced as loud warnings, and the second entry is preserved with a `__dup_i` suffix instead of being silently collapsed (as of [PR #3176](https://github.com/MervinPraison/PraisonAI/pull/3176)).

Or per-command:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai validate agents.yaml --strict
```

<AccordionGroup>
  <Accordion title="Run once after editing YAML" icon="play">
    Start the workflow once and grep logs for `Unknown field` to catch all typos at once.
  </Accordion>

  <Accordion title="Don't disable the warning" icon="shield-alert">
    Instead of disabling warnings, fix the typo or add a comment explaining the custom field. The validator helps catch configuration mistakes.
  </Accordion>

  <Accordion title="Custom fields are ignored, not preserved" icon="info">
    If you intentionally use a non-standard key, the parser will not pass it through to the agent. Only recognized fields are used.
  </Accordion>
</AccordionGroup>

***

## Root-Level Options

All options available at the root level of your YAML file.

<AccordionGroup>
  <Accordion title="Workflow Metadata" icon="info-circle">
    | Field | Type | Default | Description |
    | - | - | - | - |
    | `name` | string | "Workflow" | Workflow identifier |
    | `description` | string | "" | Workflow description |
    | `input` | string | "" | Data passed INTO workflow (use `{{input}}` in steps) |
    | `topic` | string | "" | Alias for `input` (legacy) |
    | `framework` | string | "praisonai" | Framework: `praisonai`, `crewai`, `autogen`, `langgraph`, `openai_agents`, `google_adk` |
    | `process` | string | "sequential" | Process type: `sequential`, `hierarchical`, `workflow` |
    | `manager_llm` | string | - | LLM for hierarchical process manager |
    | `run_on` | string | `None` | A shared remote sandbox that every step's shell/file tools bind to — maps directly to the Python `AgentFlow(run_on=…)` kwarg. One of: `docker`, `e2b`, `modal`, `daytona`, `flyio`, `tenki`, `local`. Omit for default local execution. Validated at load time — a typo fails immediately with the list of valid names. See [Shared Sandbox](/docs/features/shared-sandbox) and [Placement](/docs/features/placement). |
    | `type` | string | `null` | Configuration type discriminator. Accepted at the top level and ignored by the runner; declared on the schema so it never warns as an unknown field. |
    | `deploy` | object | `null` | Deployment configuration block. See [Deploy](/docs/features/deploy/overview). |
    | `dependencies` | object / array | `null` | Task dependency declarations. Honoured on the workflow YAML path (`process: workflow`); documentary-only on the roles-file loader (see [Task Ordering in Roles-File YAML](#task-ordering-in-roles-file-yaml)). |

    <Note>
      `type`, `deploy`, and `dependencies` are first-class schema fields as of [PraisonAI PR #4651](https://github.com/MervinPraison/PraisonAI/pull/4651). The validator derives its known-field list straight from the Pydantic schema, so these three no longer warn as "Unknown top-level field" — and any field added to the schema later teaches the validator with no second edit. See [YAML Validation](/docs/features/yaml-validation#unknown-field-warnings).
    </Note>
  </Accordion>

  <Accordion title="Workflow Settings" icon="gear">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    workflow:
      planning: true                    # Enable planning mode
      planning_llm: gpt-4o              # LLM for planning
      reasoning: true                   # Enable reasoning mode
      verbose: true                     # Verbose output
      default_llm: gpt-4o-mini          # Default LLM for all agents
      output: verbose                   # Output mode: silent, minimal, normal, verbose, debug
      memory_config:
        provider: chroma
        persist: true
    ```

    | Field | Type | Default | Description |
    | - | - | - | - |
    | `planning` | bool | false | Enable planning mode |
    | `planning_llm` | string | - | LLM for planning |
    | `reasoning` | bool | false | Enable reasoning mode |
    | `verbose` | bool | false | Verbose output |
    | `default_llm` | string | "gpt-4o-mini" | Default LLM for agents |
    | `output` | string | "normal" | Output mode preset |
    | `memory_config` | object | - | Memory configuration |
  </Accordion>

  <Accordion title="Context Management" icon="compress">
    Prevent token overflow errors with automatic context compaction.

    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Simple enable
    context: true

    # Detailed configuration
    context:
      auto_compact: true           # Enable auto-compaction
      compact_threshold: 0.8       # Trigger at 80% of context window
      strategy: smart              # smart | truncate | sliding_window | summarize | prune_tools
      tool_output_max: 10000       # Max tokens per tool output
    ```

    | Field | Type | Default | Description |
    | - | - | - | - |
    | `auto_compact` / `enabled` | bool | false | Enable auto-compaction |
    | `compact_threshold` / `threshold` | float | 0.8 | Trigger threshold (0-1) |
    | `strategy` | string | "smart" | Compaction strategy |
    | `tool_output_max` / `max_tool_output_tokens` | int | 10000 | Max tokens per tool |

    <Warning>
      **Always enable `context: true`** for workflows with search/crawl tools to prevent "context\_length\_exceeded" errors.
    </Warning>
  </Accordion>

  <Accordion title="Tool Retry Policy" icon="rotate">
    Configure automatic retry for failed tool calls with exponential backoff:

    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    agents:
      api_researcher:
        role: API Researcher
        instructions: "Research using external APIs"
        tools: [web_search, api_tool]
        tool_retry_policy:
          max_attempts: 4
          retry_on: [timeout, rate_limit, connection_error]
          backoff_factor: 2.0
          initial_delay_ms: 1000
          jitter: true
    ```

    | Field | Type | Default | Description |
    | - | - | - | - |
    | `max_attempts` | int | 3 | Total attempts including the first try |
    | `retry_on` | array | \["timeout", "rate\_limit", "connection\_error"] | Error types that trigger retry |
    | `backoff_factor` | float | 2.0 | Multiplier for delay between attempts |
    | `initial_delay_ms` | int | 1000 | Initial delay before first retry (ms) |
    | `jitter` | bool | false | Add randomized jitter to delays |

    For detailed configuration options, see [Tool Retry Policy](/docs/features/tool-retry-policy).
  </Accordion>

  <Accordion title="Variables" icon="code">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    variables:
      topic: AI trends
      max_results: 5
      categories:
        - Machine Learning
        - NLP
        - Computer Vision
    ```

    Use variables in steps with `{{variable_name}}` syntax. Substitutions are applied in this order:

    | Placeholder | Resolves to | Notes |
    | - | - | - |
    | `{{your_variable}}` | Value from `variables:` or an earlier step's `output_variable` | Substituted first |
    | `{{previous_output}}` | Output of the immediately previous step | Auto-appended as `"Context from previous step:"` if omitted and a previous output exists |
    | `{{step_name_output}}` | Output of a specific named step (set via `output_variable`) | Resolved in the same pass as `{{your_variable}}` |
    | `{{input}}` | Value of the top-level `input:` field | Substituted last; available in every step |
    | `{{item}}` | Current loop item | Set by `loop_over` steps |
    | `{{item.field}}` | Field in loop item | Set by `loop_over` steps |

    For date/time/UUID placeholders (`{{today}}`, `{{now}}`, `{{uuid}}`), see [Dynamic Variables](/docs/features/dynamic-variables) — that is a separate mechanism.
  </Accordion>

  <Accordion title="Runtime Selection" icon="play">
    Model-scoped runtime configuration. Requires `framework: praisonai`. See [Runtime Selection](/docs/features/runtime-selection).

    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    providers:
      anthropic:
        runtime_default: claude-code
      openai:
        runtime_default: codex-cli

    models:
      claude-3-sonnet:
        runtime: claude-code
      gpt-4o:
        runtime: praisonai
    ```

    | Field | Type | Description |
    | - | - | - |
    | `providers.<name>.runtime_default` | string | Provider-wide default runtime for all models from that provider |
    | `models.<name>.runtime` | string | Per-model runtime override (wins over provider default) |
    | `runtime` (agent-level) | string / dict | Per-agent override (wins over model and provider) |
  </Accordion>

  <Accordion title="Custom Models" icon="microchip">
    Define custom models for model routing.

    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    models:
      cheap-fast:
        provider: openai
        complexity: [simple]
        cost_per_1k: 0.0001
        capabilities: [text]
        context_window: 16000
      
      premium:
        provider: anthropic
        complexity: [complex, very_complex]
        cost_per_1k: 0.015
        capabilities: [text, vision, function-calling]
        context_window: 200000
        supports_tools: true
        strengths: [reasoning, analysis]
    ```

    | Field | Required | Description |
    | - | :-: | - |
    | `provider` | ✅ | `openai`, `anthropic`, `google`, `openrouter` |
    | `complexity` | ✅ | List: `simple`, `moderate`, `complex`, `very_complex` |
    | `cost_per_1k` | ✅ | Cost per 1,000 tokens in USD |
    | `capabilities` | ❌ | List: `text`, `vision`, `function-calling` |
    | `context_window` | ❌ | Max context window in tokens |
    | `supports_tools` | ❌ | Supports tool/function calling |
    | `strengths` | ❌ | List: `reasoning`, `code-generation`, etc. |
  </Accordion>

  <Accordion title="Callbacks" icon="bell">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    callbacks:
      on_workflow_start: log_start
      on_step_start: log_step_start
      on_step_complete: log_step_complete
      on_step_error: handle_error
      on_workflow_complete: log_complete
    ```

    On the `workflow.yaml` / `YAMLWorkflowParser` path, only these five lifecycle names reach the Workflow engine via `WorkflowHooksConfig`: `on_workflow_start`, `on_workflow_complete`, `on_step_start`, `on_step_complete`, `on_step_error`. Each referenced `func_name` must be registered on the parser **before** `parse_file(...)`:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents.workflows import YAMLWorkflowParser

    def log_start(*args, **kwargs):
        print("workflow started")

    parser = YAMLWorkflowParser()
    parser.register_callback("log_start", log_start)
    workflow = parser.parse_file("workflow.yaml")
    ```

    An unregistered name logs a warning and leaves the workflow with its default (no hooks). See [YAML Callbacks](/docs/features/yaml-workflows#yaml-callbacks).
  </Accordion>
</AccordionGroup>

***

## Agent Options

All options available for agent definitions.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Agent Definition"
        A["👤 Agent"]
    end
    
    subgraph "Core Fields"
        R["role"]
        G["goal"]
        I["instructions"]
    end
    
    subgraph "Configuration"
        L["llm"]
        T["tools"]
        M["memory"]
    end
    
    A --> R
    A --> G
    A --> I
    A --> L
    A --> T
    A --> M
    
    style A fill:#8B0000,stroke:#7C90A0,color:#fff
    style R fill:#189AB4,stroke:#7C90A0,color:#fff
    style G fill:#189AB4,stroke:#7C90A0,color:#fff
    style I fill:#189AB4,stroke:#7C90A0,color:#fff
    style L fill:#189AB4,stroke:#7C90A0,color:#fff
    style T fill:#189AB4,stroke:#7C90A0,color:#fff
    style M fill:#189AB4,stroke:#7C90A0,color:#fff
```

<AccordionGroup>
  <Accordion title="Core Fields" icon="user">
    | Field | Required | Default | Description |
    | - | :-: | - | - |
    | `role` | ✅ | - | Agent's job title |
    | `name` | ❌ | Agent ID | Display name |
    | `goal` | ❌ | "Complete the task" | Agent's objective |
    | `instructions` | ❌ | Generic | Direct behavior instructions (simple agents) |
    | `backstory` | ❌ | - | Personality/background context (advanced) |
  </Accordion>

  <Accordion title="LLM Configuration" icon="brain">
    | Field | Type | Default | Description |
    | - | - | - | - |
    | `llm` | string \| dict | `gpt-4o-mini` | Model to use |
    | `function_calling_llm` | string \| dict | Same as `llm` | Model for tool calls |
    | `reflect_llm` | string \| dict | Same as `llm` | Model for self-reflection |
    | `system_template` | string | - | Custom system prompt |
    | `prompt_template` | string | - | Custom prompt template |
    | `response_template` | string | - | Custom response template |

    Both `llm` and `function_calling_llm` accept a model name as a **string**, or a **dict** when you also need to override `base_url` / `api_key`. Both shapes work identically across every supported framework (`praisonai`, `crewai`, `autogen`, `autogen_v4`, `langgraph`, `openai_agents`, `google_adk`).

    <Tabs>
      <Tab title="String form (simplest)">
        ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
        agents:
          writer:
            role: Writer
            llm: gpt-4o-mini
            function_calling_llm: gpt-4o-mini
        ```
      </Tab>

      <Tab title="Dict form (when overriding endpoint / key)">
        ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
        agents:
          writer:
            role: Writer
            llm:
              model: gpt-4o-mini
              base_url: https://my-proxy.example.com/v1
              api_key: sk-...
            function_calling_llm:
              model: gpt-4o-mini
        ```
      </Tab>
    </Tabs>

    <Note>
      If `llm` is omitted, PraisonAI falls back to the `MODEL_NAME` environment variable, then to `openai/gpt-4o-mini`. This fallback is shared by every framework adapter.
    </Note>
  </Accordion>

  <Accordion title="Rate Limiting & Execution" icon="clock">
    | Field | Default | Description |
    | - | - | - |
    | `max_rpm` | Unlimited | Max requests per minute — auto-builds a `RateLimiter` when set; `max_rpm <= 0` raises `ValueError` |
    | `max_execution_time` | 300 | Timeout in seconds |
    | `max_iter` | 3 | Maximum iterations |
    | `min_reflect` | 0 | Minimum reflection iterations |
    | `max_reflect` | 3 | Maximum reflection iterations |
    | `cache` | true | Enable response caching |

    <Note>
      `cache` defaults to `True`. Only an **explicit** `cache: false` disables caching — omitting the key keeps the agent's default caching behaviour untouched.
    </Note>

    <Note>
      On the `workflow.yaml` path, `reflect_llm` / `min_reflect` / `max_reflect` wire into the Agent's reflection config: `agent.self_reflect` becomes `True`, and `agent.reflect_llm`, `agent.min_reflect`, and `agent.max_reflect` carry the YAML values. See [Reflection](/docs/features/reflection).
    </Note>
  </Accordion>

  <Accordion title="Advanced Features" icon="wand-magic-sparkles">
    | Field | Default | Description |
    | - | - | - |
    | `planning` | false | Enable agent-level planning |
    | `reasoning` | false | Enable reasoning mode |
    | `allow_delegation` | false | Allow task delegation |
    | `verbose` | false | Verbose output |
    | `tools` | \[] | List of tool names |
    | `web` | false | Enable web search capabilities |
    | `web_fetch` | false | Enable web content fetching |
    | `handoff` | - | Handoff configuration (see below) |

    <Note>
      On the `workflow.yaml` path, `reasoning: true` now maps to `Agent(output={'reasoning_steps': True})` under the hood. See [Reasoning](/docs/features/reasoning) and the Agent output config for details.
    </Note>
  </Accordion>

  <Accordion title="Handoff Configuration" icon="arrow-right">
    Configure agent handoff with nested options:

    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    agents:
      coordinator:
        role: Coordinator
        handoff:
          to: [writer, reviewer]      # List of target agents
          policy: summary             # Handoff policy: full, summary, none, last_n
          timeout: 60                 # Handoff timeout in seconds
          max_depth: 5                # Maximum handoff chain depth
          max_concurrent: 3           # Maximum concurrent handoffs
          detect_cycles: true         # Enable cycle detection
    ```

    | Field | Default | Description |
    | - | - | - |
    | `to` | \[] | List of agent names to handoff to |
    | `policy` | "full" | Handoff policy: `full`, `summary`, `none`, `last_n` |
    | `timeout` | 30 | Timeout for handoff in seconds |
    | `max_depth` | 10 | Maximum depth of handoff chain |
    | `max_concurrent` | 5 | Maximum concurrent handoffs |
    | `detect_cycles` | false | Enable handoff cycle detection |
  </Accordion>

  <Accordion title="Specialized Agent Types" icon="robot">
    Use the `agent:` field to specify specialized agent types:

    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    agents:
      image_creator:
        agent: ImageAgent          # Specialized type
        role: Image Generator
        llm: dall-e-3
        style: natural
      
      narrator:
        agent: AudioAgent
        role: Audio Narrator
        llm: tts-1
        voice: alloy
      
      video_maker:
        agent: VideoAgent
        role: Video Creator
        llm: openai/sora-2
      
      document_reader:
        agent: OCRAgent
        role: Document Reader
        llm: mistral/mistral-ocr-latest
      
      researcher:
        agent: DeepResearchAgent
        role: Deep Researcher
        llm: o3-deep-research
    ```

    | Agent Type | Purpose | Key Options |
    | - | - | - |
    | `ImageAgent` | Image generation | `style`, `llm` (dall-e-3) |
    | `AudioAgent` | TTS/STT | `voice`, `audio` config |
    | `VideoAgent` | Video generation | `video` config |
    | `OCRAgent` | Text extraction | `ocr` config |
    | `DeepResearchAgent` | Automated research | `instructions` |
  </Accordion>
</AccordionGroup>

***

## Step Options

All options available for step definitions.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    subgraph "Step Types"
        BS["Basic Step"]
        PS["Parallel Step"]
        RS["Route Step"]
        LS["Loop Step"]
        RPS["Repeat Step"]
        IS["Include Step"]
    end
    
    subgraph "Execution"
        EX["⚡ Execute"]
    end
    
    BS --> EX
    PS --> EX
    RS --> EX
    LS --> EX
    RPS --> EX
    IS --> EX
    
    style BS fill:#8B0000,stroke:#7C90A0,color:#fff
    style PS fill:#8B0000,stroke:#7C90A0,color:#fff
    style RS fill:#8B0000,stroke:#7C90A0,color:#fff
    style LS fill:#8B0000,stroke:#7C90A0,color:#fff
    style RPS fill:#8B0000,stroke:#7C90A0,color:#fff
    style IS fill:#8B0000,stroke:#7C90A0,color:#fff
    style EX fill:#189AB4,stroke:#7C90A0,color:#fff
```

<AccordionGroup>
  <Accordion title="Basic Step Fields" icon="list-check">
    | Field | Required | Default | Description |
    | - | :-: | - | - |
    | `agent` | ✅\* | - | Agent to execute (\*not needed for patterns) |
    | `action` | ❌ | `{{input}}` | What the step does |
    | ~~`description`~~ | ❌ | - | **Deprecated** - use `action` |
    | `name` | ❌ | Auto-generated | Step identifier |
    | `expected_output` | ❌ | - | Description of expected output |
  </Accordion>

  <Accordion title="Output Options" icon="file-export">
    | Field | Description |
    | - | - |
    | `output_file` | Save output to file path |
    | `create_directory` | Create output directory if needed (set automatically for `framework: praisonai` tasks whose `output_file` has a parent directory) |
    | `output_json` | JSON schema for structured output |
    | `output_pydantic` | Pydantic model name from tools.py |
    | `output_variable` | Store output in named variable |

    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    steps:
      - agent: researcher
        action: "Find topics"
        output_json:
          type: array
          items:
            type: object
            properties:
              title: { type: string }
              url: { type: string }
        output_variable: topics
    ```
  </Accordion>

  <Accordion title="Context & Dependencies" icon="link">
    | Field | Description |
    | - | - |
    | `context` | List of dependent step names |

    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    steps:
      - name: research_step
        agent: researcher
        action: "Research {{input}}"
      
      - name: writing_step
        agent: writer
        action: "Write based on: {{previous_output}}"
        context:
          - research_step    # Explicit dependency
    ```
  </Accordion>

  <Accordion title="Execution Control" icon="play">
    | Field | Default | Description |
    | - | - | - |
    | `async_execution` | false | Run asynchronously |
    | `max_retries` | 3 | Maximum retry attempts |
    | `guardrail` | - | Guardrail function name |
    | `callback` | - | Callback function name |
  </Accordion>
</AccordionGroup>

***

## Workflow Patterns

Advanced workflow patterns available in both `agents.yaml` and `workflow.yaml`.

<CardGroup cols={2}>
  <Card title="Parallel" icon="arrows-split-up-and-left">
    Execute multiple agents concurrently
  </Card>

  <Card title="Route" icon="route">
    Classify and route to specialized agents
  </Card>

  <Card title="Loop" icon="repeat">
    Iterate over a list of items
  </Card>

  <Card title="Repeat" icon="rotate">
    Repeat until condition is met
  </Card>

  <Card title="Include" icon="file-import">
    Include modular recipes
  </Card>
</CardGroup>

<Tabs>
  <Tab title="Parallel">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    steps:
      - name: parallel_research
        parallel:
          - agent: market_analyst
            action: "Research market trends"
          - agent: tech_analyst
            action: "Research technology"
      
      - agent: aggregator
        action: "Combine findings: {{previous_output}}"
    ```
  </Tab>

  <Tab title="Route">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    steps:
      - agent: classifier
        action: "Classify: {{input}}"
      
      - name: routing
        route:
          technical: [tech_expert]
          creative: [creative_expert]
          default: [general_agent]
    ```
  </Tab>

  <Tab title="Loop">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    variables:
      topics:
        - Machine Learning
        - NLP
        - Computer Vision

    steps:
      - agent: researcher
        action: "Research {{item}}"
        loop:
          over: topics
          parallel: true      # Optional: run in parallel
          max_workers: 4      # Optional: limit workers
    ```
  </Tab>

  <Tab title="Multi-Step Loop">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    steps:
      - loop:
          over: topics
          parallel: true
        steps:
          - agent: researcher
            action: "Research {{item}}"
          - agent: writer
            action: "Write about: {{previous_output}}"
          - agent: publisher
            action: "Publish: {{previous_output}}"
    ```
  </Tab>

  <Tab title="Repeat">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    steps:
      - agent: writer
        action: "Write article about {{topic}}"
        repeat:
          until: "approved"
          max_iterations: 3
    ```
  </Tab>

  <Tab title="Include">
    ```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    steps:
      - include: ai-topic-gatherer
      
      - include:
          recipe: wordpress-publisher
          input: "{{previous_output}}"
      
      - agent: writer
        action: "Summarize: {{previous_output}}"
    ```
  </Tab>
</Tabs>

### Loop Options

| Field | Required | Default | Description |
| - | :-: | - | - |
| `over` | ✅\* | - | Variable name to iterate |
| `from_csv` | ❌ | - | CSV file path to iterate |
| `from_file` | ❌ | - | File path to iterate lines |
| `var_name` | ❌ | "item" | Variable name for current item |
| `parallel` | ❌ | false | Execute iterations in parallel |
| `max_workers` | ❌ | - | Limit parallel workers |
| `output_variable` | ❌ | - | Store all outputs in variable |

### Repeat Options

| Field | Required | Default | Description |
| - | :-: | - | - |
| `until` | ✅ | - | Condition string to match in output |
| `max_iterations` | ❌ | 5 | Maximum iterations |

### Include Options

| Field | Required | Default | Description |
| - | :-: | - | - |
| `recipe` | ✅ | - | Recipe name or path |
| `input` | ❌ | `{{previous_output}}` | Input for included recipe |

***

## Feature Compatibility Matrix

What works where:

| Feature | agents.yaml | workflow\.yaml | Notes |
| - | :-: | :-: | - |
| **Agent Definition** | ✅ | ✅ | Use `agents:` (canonical) or `roles:` |
| **Steps/Tasks** | ✅ | ✅ | Use `steps:` (canonical) |
| **Workflow Patterns** | ✅ | ✅ | `parallel`, `route`, `loop`, `repeat` |
| **Include Recipes** | ✅ | ✅ | `include:` in steps |
| **Variables** | ✅ | ✅ | `variables:` section |
| **Context Management** | ✅ | ✅ | `context:` section |
| **Planning Mode** | ✅ | ✅ | `workflow.planning: true` |
| **Reasoning Mode** | ✅ | ✅ | `workflow.reasoning: true` |
| **Memory Config** | ✅ | ✅ | `workflow.memory_config:` |
| **Custom Models** | ✅ | ✅ | `models:` section |
| **Callbacks** | ✅ | ✅ | `callbacks:` section |
| **Specialized Agents** | ✅ | ✅ | `agent: ImageAgent`, etc. |
| **Handoff Configuration** | ✅ | ✅ | `handoff:` in agent definition |
| **Web Search** | ✅ | ✅ | `web: true` in agent definition |
| **Web Content Fetching** | ✅ | ✅ | `web_fetch: true` in agent definition |
| **Structured Output** | ✅ | ✅ | `output_json`, `output_pydantic` |

<Check>
  **Full Feature Parity!** Both file formats support all features. The only difference is naming conventions.
</Check>

<Note>
  **Framework LLM Configuration Parity:** The `llm` and `function_calling_llm` configuration shapes (string and dict forms) work identically across all supported frameworks (`praisonai`, `crewai`, `autogen`, `autogen_v4`, `langgraph`, `openai_agents`, `google_adk`). You can switch between frameworks without changing your LLM configuration syntax.
</Note>

***

## What's NOT Possible

<Warning>
  These limitations apply to both `agents.yaml` and `workflow.yaml`:
</Warning>

| Limitation | Workaround |
| - | - |
| **Nested loops** | Use multi-step loop with sequential steps |
| **Conditional branching mid-step** | Use `route:` pattern instead |
| **Dynamic agent creation** | Pre-define all agents in `agents:` section |
| **Cross-workflow state** | Use `include:` with explicit input passing |
| **Real-time streaming in loops** | Streaming works per-step, not across loop |

***

## Migration Guide

### From agents.yaml to workflow\.yaml

<Steps>
  <Step title="Rename container">
    `roles:` → `agents:`
  </Step>

  <Step title="Rename agent fields">
    `backstory:` → `instructions:`
  </Step>

  <Step title="Extract tasks to steps">
    Move nested `tasks:` to top-level `steps:`
  </Step>

  <Step title="Rename step fields">
    `description:` → `action:`
  </Step>

  <Step title="Update input reference">
    `topic:` → `input:` (optional but recommended)
  </Step>
</Steps>

<CodeGroup>
  ```yaml Before (agents.yaml style) theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  framework: praisonai
  topic: "Research AI"

  roles:
    researcher:
      role: Analyst
      backstory: "Expert researcher"
      goal: Research
      tasks:
        research_task:
          description: "Research {{topic}}"
  ```

  ```yaml After (workflow.yaml style) theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  name: Research Workflow
  input: "Research AI"

  agents:
    researcher:
      role: Analyst
      instructions: "Expert researcher"
      goal: Research

  steps:
    - agent: researcher
      action: "Research {{input}}"
  ```
</CodeGroup>

***

## Validation

Validate your YAML configuration before running:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai validate agents.yaml
```

Output shows:

* ✅ Valid configuration
* ⚠️ Non-blocking warnings (unknown fields, optional tool deps)
* ❌ Errors that would abort execution

Scan an entire directory:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai validate check . --strict --json
```

See the [Validate CLI page](/docs/cli/validate) for all flags, JSON output format, and CI integration examples.

`praisonai workflow validate` is the workflow-specific variant and remains available for backwards compatibility.

***

## Best Practices

<AccordionGroup>
  <Accordion title="Use Canonical Names">
    `agents`, `instructions`, `action`, `steps`, `input`
  </Accordion>

  <Accordion title="Enable Context Management">
    `context: true` for tool-heavy workflows
  </Accordion>

  <Accordion title="Define Expected Output">
    Always specify `expected_output` for clarity
  </Accordion>

  <Accordion title="Use Variables">
    Centralize reusable values in `variables:`
  </Accordion>
</AccordionGroup>

<Tip>
  Run `praisonai validate <file.yaml>` to check for all schema errors, cross-reference problems, and unknown tool references before starting a workflow. Use `praisonai validate schema` to print all recognised fields and their types.
</Tip>

## Related

<CardGroup cols={2}>
  <Card icon="file-code" href="/docs/features/config-file">
    Set project-wide agent defaults in a config file.
  </Card>

  <Card icon="terminal" href="/docs/features/cli">
    Run and validate YAML configs from the command line.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.