How to Fix 'async event loop error when scaling' in AutoGen (Python)
What this error usually means
If you’re seeing async event loop error when scaling in AutoGen, you’re almost always mixing sync and async execution in a way that breaks Python’s event loop. It tends to show up when you move from a single notebook test to parallel agents, background workers, FastAPI, or any setup where asyncio is already running.
The common symptom is some variation of:
- •
RuntimeError: This event loop is already running - •
RuntimeError: asyncio.run() cannot be called from a running event loop - •
RuntimeError: There is no current event loop in thread '...'
The Most Common Cause
The #1 cause is calling asyncio.run() inside code that is already running inside an event loop. In AutoGen, this often happens when you wrap AssistantAgent.a_generate_reply(), UserProxyAgent.a_initiate_chat(), or another async AutoGen method inside a sync helper and then call that helper from FastAPI, Jupyter, or another async runtime.
Here’s the broken pattern:
import asyncio
from autogen import AssistantAgent, UserProxyAgent
assistant = AssistantAgent(
name="assistant",
llm_config={"config_list": [{"model": "gpt-4o-mini", "api_key": "YOUR_KEY"}]},
)
user = UserProxyAgent(name="user")
def run_chat():
# ❌ Wrong: asyncio.run() inside a function that may be called from an active loop
return asyncio.run(user.a_initiate_chat(assistant, message="Summarize this document"))
# Called from FastAPI / notebook / async worker
result = run_chat()
print(result)
And here’s the fixed pattern:
import asyncio
from autogen import AssistantAgent, UserProxyAgent
assistant = AssistantAgent(
name="assistant",
llm_config={"config_list": [{"model": "gpt-4o-mini", "api_key": "YOUR_KEY"}]},
)
user = UserProxyAgent(name="user")
async def run_chat():
# ✅ Correct: await the AutoGen async API directly
return await user.a_initiate_chat(assistant, message="Summarize this document")
# If you're in an async context:
result = await run_chat()
print(result)
If you need a sync entrypoint for a CLI script, keep it at the top level only:
if __name__ == "__main__":
result = asyncio.run(run_chat())
print(result)
The rule is simple: use await inside async code, and reserve asyncio.run() for the outermost process boundary.
Other Possible Causes
1) Mixing sync AutoGen APIs with async ones
AutoGen exposes both sync and async methods. If one agent path uses .initiate_chat() while another uses .a_initiate_chat(), you can end up with inconsistent execution and loop errors under load.
# Broken
user.initiate_chat(assistant, message="Hello")
await assistant.a_generate_reply(messages=[...])
# Fixed
await user.a_initiate_chat(assistant, message="Hello")
reply = await assistant.a_generate_reply(messages=[...])
Pick one model per request path. In production services, use async end-to-end.
2) Running AutoGen inside Jupyter without proper handling
Jupyter already has an active event loop. Calling asyncio.run() there will fail immediately.
# Broken in notebook
asyncio.run(user.a_initiate_chat(assistant, message="Test"))
# Fixed in notebook
await user.a_initiate_chat(assistant, message="Test")
If your notebook code calls a sync wrapper that internally uses asyncio.run(), it will fail the same way. That wrapper needs to be removed or made async.
3) Starting tasks on the wrong thread
If you scale by pushing work into threads and then create or access the event loop there, Python may raise:
- •
RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-0_0'
# Broken
from concurrent.futures import ThreadPoolExecutor
def worker():
return asyncio.get_event_loop().run_until_complete(
user.a_initiate_chat(assistant, message="Hi")
)
# Fixed
def worker(loop):
coro = user.a_initiate_chat(assistant, message="Hi")
return asyncio.run_coroutine_threadsafe(coro, loop).result()
Better yet, avoid threads for orchestration unless you need them. Use one event loop and schedule concurrent chats with asyncio.gather().
4) Reusing one agent/client across many concurrent requests without isolation
When scaling requests horizontally inside one process, shared mutable state can trigger weird failures that look like event loop issues. This happens if you reuse conversation state or callback handlers across tasks.
# Risky pattern
shared_user = UserProxyAgent(name="user")
shared_assistant = AssistantAgent(name="assistant", llm_config=...)
# Safer pattern
async def handle_request(prompt: str):
user = UserProxyAgent(name="user")
assistant = AssistantAgent(name="assistant", llm_config=...)
return await user.a_initiate_chat(assistant, message=prompt)
If your app needs shared config, share config objects. Don’t share per-conversation runtime state unless you’ve designed for it explicitly.
How to Debug It
- •
Find the first place
asyncio.run()appears- •Search your codebase for
asyncio.run(. - •If it’s inside a request handler, notebook cell chain, or helper called by async code, that’s likely the bug.
- •Search your codebase for
- •
Check whether your call stack is already async
- •If you are in FastAPI (
async defendpoint), Jupyter, Quart, or an async worker, do not start a new loop. - •Replace sync wrappers with direct
await.
- •If you are in FastAPI (
- •
Confirm which AutoGen method you’re calling
- •Look for mixed usage like
.initiate_chat()plus.a_generate_reply(). - •Standardize on either all-sync or all-async per execution path.
- •Look for mixed usage like
- •
Print the exact exception and thread context
- •The useful clues are usually:
- •
This event loop is already running - •
cannot be called from a running event loop - •
no current event loop in thread
- •
- •Log
threading.current_thread().nameand whether your function is declared withasync def.
- •The useful clues are usually:
Example diagnostic snippet:
import threading
print("thread:", threading.current_thread().name)
print("is_async_fn:", hasattr(run_chat, "__await__"))
Prevention
- •Use one execution model per service:
- •async API handlers → only
await - •CLI scripts → top-level
asyncio.run()
- •async API handlers → only
- •Keep AutoGen orchestration inside dedicated async functions:
- •no hidden
asyncio.run()wrappers - •no sync helper that secretly starts loops
- •no hidden
- •In scaled systems:
- •create per-request conversation objects when possible
- •use
asyncio.gather()for concurrency instead of threads unless threads are required
If you standardize on async all the way down and keep loop creation at the process boundary only, this class of AutoGen scaling errors usually disappears fast.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
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