> ## 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 --> S[🌉 async_scoped_bridge]
        S --> D[🔧 FrameworkAdapter.arun]
        D --> E[🚀 AgentTeam.astart]
        E --> F[✅ Result]
    end
    
    classDef agent fill:#8B0000,color:#fff
    classDef tool fill:#189AB4,color:#fff
    classDef bridge fill:#6366F1,color:#fff
    
    class A agent
    class B,C,D,E,F tool
    class S bridge
```

Each `agenerate_crew_and_kickoff()` run opens an [`async_scoped_bridge()`](/docs/features/async-bridge#async-scoped-bridge-async-safe-context-manager) around the adapter call, so concurrent tenants each get their own per-run bridge and a stuck coroutine in one cannot park the shared loop for the others.

## 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
    Note over AgentsGenerator,FrameworkAdapter: async with async_scoped_bridge()<br/>(per-run isolated bridge)
    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).

  **PR #5228 (September 2026)** extended sync-path scope isolation to `process: workflow`. `_run_yaml_workflow` now wraps `workflow.start(...)` in `scoped_bridge()`, so a stuck workflow-tool coroutine cannot park the shared default loop for concurrent `run_sync` callers. The async workflow path already runs on the caller's loop and is unchanged. See [Async Bridge → Per-run auto-scoping](/docs/features/async-bridge#per-run-auto-scoping-sequential-hierarchical-workflow).
</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 |
| `strict_validation` | `bool` | `None` | Recognised loose key — forwarded via `cli_config` to scope YAML validation strictness per run. Falls back to `PRAISONAI_VALIDATE_STRICT`. |
| `tool_timeout_workers` | `int` | `None` | Recognised loose key — forwarded via `cli_config` to size the sync-tool timeout pool per run. Falls back to `PRAISONAI_TOOL_TIMEOUT_WORKERS` (default `32`). |

`strict_validation` and `tool_timeout_workers` are recognised loose keys — `**kwargs` forwards them through `cli_config` for per-run multi-tenant isolation.

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>


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