> ## 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.

# Async Crew Kickoff

> Run PraisonAI crews with native async execution — no thread offload required

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
import asyncio

agent = Agent(name="async-agent", instructions="Process tasks asynchronously.")

async def main():
    result = await agent.astart("Run this task asynchronously.")
    print(result)

asyncio.run(main())
```

Run PraisonAI crews with native async execution from FastAPI, Jupyter, Discord bots, and other event loop contexts.
The user awaits `agent.astart()` from an async app; the crew kickoff runs natively on the event loop without thread offload.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Native Async Flow"
        A[📋 Async Caller] --> B[🧠 praisonai.arun]
        B --> C[⚡ AgentsGenerator.agenerate_crew_and_kickoff]
        C --> D[🔧 FrameworkAdapter.arun]
        D --> E[🚀 AgentTeam.astart]
        E --> F[✅ Result]
    end
    
    classDef agent fill:#8B0000,color:#fff
    classDef tool fill:#189AB4,color:#fff
    
    class A agent
    class B,C,D,E,F tool
```

## Quick Start

<Steps>
  <Step title="FastAPI Route">
    Native async execution — no worker threads, true cooperative multitasking:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from fastapi import FastAPI
    import praisonai

    app = FastAPI()

    @app.post("/run")
    async def run_crew():
        result = await praisonai.arun(agent_file="agents.yaml")
        return {"result": result}
    ```
  </Step>

  <Step title="Jupyter Notebook">
    Works directly in async cells without blocking:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import praisonai

    result = await praisonai.arun(agent_file="agents.yaml")
    print(result)
    ```
  </Step>
</Steps>

***

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant praisonai
    participant AgentsGenerator
    participant Worker
    participant FrameworkAdapter
    participant AgentTeam
    participant Result
    
    User->>praisonai: await arun(agent_file)
    praisonai->>AgentsGenerator: agenerate_crew_and_kickoff()
    AgentsGenerator->>Worker: asyncio.to_thread(_aload_config)
    Worker-->>AgentsGenerator: config
    AgentsGenerator->>Worker: asyncio.to_thread(_aprepare_for_run)
    Worker-->>AgentsGenerator: prepared workflow
    AgentsGenerator->>FrameworkAdapter: arun(config, llm_config, topic)
    FrameworkAdapter->>AgentTeam: astart() 
    AgentTeam-->>FrameworkAdapter: native async result
    FrameworkAdapter-->>AgentsGenerator: result
    AgentsGenerator-->>praisonai: result
    praisonai-->>User: result
```

### Non-blocking startup

Config loading, adapter setup, and workflow preparation run in a worker thread via `asyncio.to_thread`, so a slow disk read or heavy adapter import never stalls the event loop.

The `_aload_config` and `_aprepare_for_run` helpers wrap the blocking `open()` / `yaml.safe_load` / adapter setup calls, keeping the loop free to serve other requests while startup work runs off-thread.

<Info>
  **Sync/Async Parity:** As of PR #1870, sync and async kickoff paths share the same prep logic (AutoGen version selection, AgentOps init, cli\_backend validation), so behavior is identical between `generate_crew_and_kickoff()` and `agenerate_crew_and_kickoff()`.
</Info>

<Info>
  **Extended in [PR #2738](https://github.com/MervinPraison/PraisonAI/pull/2738):** Config validation, merge, and dump logic live in a single `_build_yaml_workflow` builder shared by both paths, so sync and async behavior can no longer drift apart.
</Info>

<Info>
  **PR #3252 (July 2026)** extended parity to the `process: workflow` path — observability, `cli_backend` validation, and `tool_timeout` diagnostics now fire on workflow YAML for both sync and async, matching the sequential / hierarchical treatment.

  As of [PR #3963](https://github.com/MervinPraison/PraisonAI/pull/3963), an **explicit** `tool_timeout` on `process: workflow` now **raises** on both paths rather than logging a warning — pick `sequential` or `hierarchical` if you need per-tool timeouts. See [tool\_timeout on workflow YAML](/docs/docs/features/yaml-workflows#tool_timeout-on-workflow-yaml).
</Info>

### What's actually async

| Adapter                       | Async path                        | Notes                                                                                                                                               |
| ----------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `praisonai` (praisonaiagents) | Native — `AgentTeam.astart()`     | True cooperative async                                                                                                                              |
| `crewai`                      | Native — `crew.kickoff_async()`   | Requires crewai ≥ 0.28; older installs transparently fall back to thread offload ([PR #4909](https://github.com/MervinPraison/PraisonAI/pull/4909)) |
| `autogen` / `ag2`             | Thread offload (default fallback) | AutoGen v0.2 is sync-only; v0.4 / ag2 are stubs                                                                                                     |

<Info>
  **PR #4909 (Sep 2026)** — `framework: crewai` gained a native async path. `CrewAIAdapter.arun()` now awaits `crew.kickoff_async()` directly (available since crewai 0.28) instead of pinning a worker thread. If an older crewai without `kickoff_async` is installed, the adapter transparently falls back to the base thread-offload path, so behaviour degrades instead of breaking.
</Info>

### Workflow mode

YAML files with `process: workflow` also run natively async via `YAMLWorkflowParser` + `workflow.astart()` — no extra configuration needed.

***

## Configuration Options

| Option          | Type   | Default  | Description                                                                                                     |
| --------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------- |
| `agent_file`    | `str`  | Required | Path to the agent YAML file                                                                                     |
| `framework`     | `str`  | `None`   | Framework to use (auto-detected if None)                                                                        |
| `tools`         | `list` | `None`   | Additional tools to make available                                                                              |
| `agent_yaml`    | `str`  | `None`   | Direct YAML content as string                                                                                   |
| `cli_config`    | `dict` | `None`   | CLI configuration overrides (explicit keys win over `**kwargs`)                                                 |
| `model` / `llm` | `str`  | `None`   | Selects the LLM — same as the CLI's `--llm` flag                                                                |
| `session`       | `str`  | `None`   | Resume a session — same as the CLI's `--resume` flag                                                            |
| `**kwargs`      | any    | —        | Any other keyword argument is forwarded through `cli_config` to the generator, mirroring the CLI's pass-through |

See [Advanced CLI options from Python](/docs/developers/wrapper#advanced-cli-options-from-python) for the full alias table and precedence rules.

***

## Common Patterns

### FastAPI Background Task

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import asyncio
import praisonai
from fastapi import FastAPI, BackgroundTasks

app = FastAPI()

async def run_crew_background():
    result = await praisonai.arun(agent_file="agents.yaml")
    # Store result in database, send notification, etc.
    print(f"Background crew completed: {result}")

@app.post("/start-crew")
async def start_crew(background_tasks: BackgroundTasks):
    background_tasks.add_task(run_crew_background)
    return {"message": "Crew started"}
```

### Concurrent Crew Execution

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import asyncio
import praisonai

async def run_multiple_crews():
    # Run all crews concurrently with true async
    results = await asyncio.gather(
        praisonai.arun(agent_file="research.yaml"),
        praisonai.arun(agent_file="analysis.yaml"),
        praisonai.arun(agent_file="summary.yaml")
    )
    
    return results
```

### CLI options from an async handler

`arun()` (and `run()`) accept any extra keyword argument the CLI accepts. `model=`/`llm=` and `session=` get friendly Python aliases; everything else is forwarded through the same `cli_config` bridge the CLI already uses.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import praisonai

async def handler():
    # model= is aliased to --llm; session= is aliased to --resume
    result = await praisonai.arun(
        "agents.yaml",
        model="gpt-4o",
        session="chat-42",
    )
    return result
```

An explicit `cli_config=` still wins over loose kwargs — pass either, not both, for the same key.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Start{How do you pass an option?} -->|Simple kwarg| Loose["praisonai.arun<br/>model=gpt-4o"]
    Start -->|Need CLI-only key<br/>or batch of values| Explicit["praisonai.arun<br/>cli_config={...}"]
    Loose --> Merge[_merge_cli_config]
    Explicit --> Merge
    Merge --> Gen[AgentsGenerator]

    classDef question fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef choice fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef merge fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef gen fill:#10B981,stroke:#7C90A0,color:#fff

    class Start question
    class Loose,Explicit choice
    class Merge merge
    class Gen gen
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="Cancellation and timeouts propagate">
    Under native async, `asyncio.CancelledError` and `asyncio.wait_for` now actually cancel SDK work instead of being trapped behind a worker thread:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import asyncio
    import praisonai

    async def cancelable_crew():
        try:
            # This will properly cancel if timeout is reached
            result = await asyncio.wait_for(
                praisonai.arun(agent_file="agents.yaml"),
                timeout=30.0
            )
            return result
        except asyncio.TimeoutError:
            print("Crew execution timed out and was cancelled")
            return None
    ```
  </Accordion>

  <Accordion title="Use asyncio.gather for concurrent crews">
    When running multiple crews, use `asyncio.gather` for parallel execution:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import asyncio
    import praisonai

    # ✅ Concurrent execution with native async
    crew1_task = praisonai.arun(agent_file="crew1.yaml")
    crew2_task = praisonai.arun(agent_file="crew2.yaml")
    results = await asyncio.gather(crew1_task, crew2_task)

    # ❌ Sequential execution (slower)
    result1 = await praisonai.arun(agent_file="crew1.yaml")
    result2 = await praisonai.arun(agent_file="crew2.yaml")
    ```
  </Accordion>

  <Accordion title="Framework detection works transparently">
    Both `praisonai` and `crewai` use native async — `AgentTeam.astart()` and `crew.kickoff_async()` respectively. AutoGen v0.2 still falls back to the bounded thread-offload pool because its `initiate_chats` is sync-only.

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import praisonai

    # Native async for both praisonai and crewai (crewai ≥ 0.28)
    result = await praisonai.arun(agent_file="agents.yaml", framework="crewai")
    ```
  </Accordion>

  <Accordion title="Handle errors gracefully in async contexts">
    Wrap async crew execution in try-catch blocks:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import asyncio
    import logging
    import praisonai

    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger(__name__)

    async def safe_crew_run():
        try:
            result = await praisonai.arun(agent_file="agents.yaml")
            return {"success": True, "result": result}
        except Exception as e:
            logger.error(f"Crew execution failed: {e}")
            return {"success": False, "error": str(e)}
    ```
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="YAML Template Variables" icon="code" href="/docs/features/yaml-template-variables">
    Use {topic} placeholders safely alongside JSON literals
  </Card>

  <Card title="Framework Adapter Plugins" icon="puzzle-piece" href="/docs/features/framework-adapter-plugins">
    Custom framework adapters with async support
  </Card>
</CardGroup>
