Async Crew Kickoff

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

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

Diagram

Native Async Flow

📋 Async Caller

🧠 praisonai.arun

⚡ AgentsGenerator.agenerate_crew_and_kickoff

🌉 async_scoped_bridge

🔧 FrameworkAdapter.arun

🚀 AgentTeam.astart

✅ Result

Each agenerate_crew_and_kickoff() run opens an async_scoped_bridge() 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

  1. FastAPI Route

    Native async execution — no worker threads, true cooperative multitasking:

    python
    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}
  2. Jupyter Notebook

    Works directly in async cells without blocking:

    python
    import praisonai
    
    result = await praisonai.arun(agent_file="agents.yaml")
    print(result)

How It Works

Diagram
ResultAgentTeamFrameworkAdapterWorkerAgentsGeneratorpraisonaiUserResultAgentTeamFrameworkAdapterWorkerAgentsGeneratorpraisonaiUserasync with async_scoped_bridge()(per-run isolated bridge)await arun(agent_file)agenerate_crew_and_kickoff()asyncio.to_thread(_aload_config)configasyncio.to_thread(_aprepare_for_run)prepared workflowarun(config, llm_config, topic)astart()native async resultresultresultresult

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.

What’s actually async

AdapterAsync pathNotes
praisonai (praisonaiagents)Native — AgentTeam.astart()True cooperative async
crewaiNative — crew.kickoff_async()Requires crewai ≥ 0.28; older installs transparently fall back to thread offload (PR #4909)
autogen / ag2Thread offload (default fallback)AutoGen v0.2 is sync-only; v0.4 / ag2 are stubs

Workflow mode

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


Configuration Options

OptionTypeDefaultDescription
agent_filestrRequiredPath to the agent YAML file
frameworkstrNoneFramework to use (auto-detected if None)
toolslistNoneClasses, plain functions or framework tool instances (CrewAI/LangChain BaseTool). Unrecognised entries are logged and skipped — see Passing tools
agent_yamlstrNoneDirect YAML content as string
cli_configdictNoneCLI configuration overrides (explicit keys win over **kwargs)
model / llmstrNoneSelects the LLM — same as the CLI’s --llm flag
sessionstrNoneResume a session — same as the CLI’s --resume flag
**kwargsany—Any other keyword argument is forwarded through cli_config to the generator, mirroring the CLI’s pass-through
strict_validationboolNoneRecognised loose key — forwarded via cli_config to scope YAML validation strictness per run. Falls back to PRAISONAI_VALIDATE_STRICT.
tool_timeout_workersintNoneRecognised 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 for the full alias table and precedence rules.


Common Patterns

FastAPI Background Task

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

Diagram

Simple kwarg

Need CLI-only key
or batch of values

How do you pass an option?

praisonai.arun
model=gpt-4o

praisonai.arun
cli_config={…}

_merge_cli_config

AgentsGenerator


Best Practices

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
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
Use asyncio.gather for concurrent crews

When running multiple crews, use asyncio.gather for parallel execution:

python
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")
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
import praisonai

# Native async for both praisonai and crewai (crewai ≥ 0.28)
result = await praisonai.arun(agent_file="agents.yaml", framework="crewai")
Handle errors gracefully in async contexts

Wrap async crew execution in try-catch blocks:

python
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)}

Related