How to Fix 'async event loop error during development' in CrewAI (Python)

By Cyprian AaronsUpdated 2026-04-21
async-event-loop-error-during-developmentcrewaipython

What this error usually means

If you’re seeing async event loop error during development in CrewAI, you’re almost always dealing with an event loop conflict, not a CrewAI-specific bug. It usually shows up when you call async code from the wrong place, run notebooks or hot-reload servers, or mix asyncio.run() with an already running loop.

In practice, the stack trace often includes one of these:

  • RuntimeError: asyncio.run() cannot be called from a running event loop
  • RuntimeError: This event loop is already running
  • RuntimeError: Task got Future attached to a different loop

The Most Common Cause

The #1 cause is wrapping CrewAI async execution inside asyncio.run() when you’re already inside an active loop.

This happens a lot in:

  • Jupyter notebooks
  • FastAPI endpoints
  • Streamlit apps
  • VS Code debug sessions with autoreload

Broken pattern vs fixed pattern

BrokenFixed
Calls asyncio.run() from inside another async contextUses await inside async functions
Creates tasks on one loop and runs them on anotherKeeps execution on the same loop
# broken.py
import asyncio
from crewai import Agent, Task, Crew, Process

agent = Agent(
    role="Researcher",
    goal="Research company data",
    backstory="You are a careful analyst."
)

task = Task(
    description="Summarize the latest quarterly filing.",
    agent=agent
)

crew = Crew(
    agents=[agent],
    tasks=[task],
    process=Process.sequential
)

async def handler():
    # WRONG: if handler is already running in an event loop,
    # this will raise:
    # RuntimeError: asyncio.run() cannot be called from a running event loop
    result = asyncio.run(crew.kickoff())
    return result
# fixed.py
from crewai import Agent, Task, Crew, Process

agent = Agent(
    role="Researcher",
    goal="Research company data",
    backstory="You are a careful analyst."
)

task = Task(
    description="Summarize the latest quarterly filing.",
    agent=agent
)

crew = Crew(
    agents=[agent],
    tasks=[task],
    process=Process.sequential
)

async def handler():
    # RIGHT: stay inside the current event loop
    result = await crew.kickoff()
    return result

If you’re in plain Python script code, use asyncio.run() only at the top level:

import asyncio

async def main():
    result = await crew.kickoff()
    print(result)

if __name__ == "__main__":
    asyncio.run(main())

Other Possible Causes

1. Running CrewAI inside Jupyter without patching the loop

Jupyter already runs an event loop. If you call asyncio.run() there, it breaks immediately.

# broken in notebook cell
import asyncio
result = asyncio.run(crew.kickoff())

Fix it by awaiting directly:

# correct in notebook cell
result = await crew.kickoff()

If your notebook environment is older and doesn’t support top-level await, use nest_asyncio as a workaround:

import nest_asyncio
nest_asyncio.apply()

2. Mixing sync and async CrewAI APIs incorrectly

CrewAI has sync-style usage patterns and async usage patterns. If you call async methods from sync code without managing the loop correctly, you’ll hit runtime errors.

# broken
def run_crew():
    return crew.kickoff_async()  # returns coroutine, not result

output = run_crew()

Fix:

import asyncio

def run_crew():
    return asyncio.run(crew.kickoff())

output = run_crew()

Or keep everything async:

async def run_crew():
    return await crew.kickoff()

3. Creating objects on one thread/loop and executing on another

This shows up when background threads or reloaders are involved. You’ll often see:

  • Task got Future attached to a different loop
  • attached to a different event loop
# broken pattern: create in one context, execute in another thread/loop
crew = Crew(agents=[agent], tasks=[task], process=Process.sequential)

If that object is later used inside another thread’s event loop, it can fail.

Fix by creating and running the crew in the same async context:

async def run():
    agent = Agent(...)
    task = Task(...)
    crew = Crew(agents=[agent], tasks=[task], process=Process.sequential)
    return await crew.kickoff()

4. Hot reload / dev server spawning duplicate loops

FastAPI with reload, Streamlit reruns, and some debugger setups can create multiple loops or restart state mid-flight.

uvicorn app:app --reload

If your startup code creates long-lived async resources at import time, move them into lifespan/startup hooks.

@app.on_event("startup")
async def startup():
    app.state.crew = build_crew()

Avoid this:

# bad: module-level execution during import/reload
crew = build_crew()
result = asyncio.run(crew.kickoff())

How to Debug It

  1. Read the exact exception text

    • If you see asyncio.run() cannot be called from a running event loop, you’re nesting loops.
    • If you see Future attached to a different loop, your objects are crossing contexts.
    • If you see plain This event loop is already running, you’re likely in Jupyter or an async server.
  2. Find where asyncio.run() is called

    • Search your codebase for asyncio.run(.
    • If it appears inside an endpoint, notebook cell, callback, or worker function that’s already async-aware, replace it with await.
  3. Check whether your caller is already async

    • In FastAPI:
      @app.get("/run")
      async def run():
          return await crew.kickoff()
      
    • In sync scripts:
      if __name__ == "__main__":
          asyncio.run(main())
      
  4. Move object creation closer to execution

    • Build Agent, Task, and Crew inside the same function that calls kickoff.
    • This avoids cross-loop contamination during reloads or threaded execution.

Prevention

  • Keep one rule: await inside async functions, asyncio.run() only at the top level.
  • Don’t create CrewAI runtime objects at import time if you’re using notebooks, reloaders, or web servers.
  • Add a small integration test that runs your CrewAI flow in the same environment as production entrypoints:
    • notebook cell if you develop there,
    • FastAPI endpoint if that’s your app,
    • CLI script if that’s how it ships.

If you want to stop chasing this class of bug entirely, standardize on one execution model per project: either fully synchronous entrypoints with one top-level asyncio.run(), or fully async request handlers with direct await.


Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides