Summary
The tool approval path in Agent calls asyncio.run() from a synchronous method that is reachable from async execution contexts. This causes a hard RuntimeError when an async workflow triggers tool approval.
Problem
agent.py:5064-5067:
if hasattr(backend, 'request_approval_sync'):
decision = backend.request_approval_sync(request)
else:
decision = asyncio.run(backend.request_approval(request))
This is inside _check_tool_approval_sync(), which is called from the synchronous _execute_tool_impl(). However, _execute_tool_impl() is callable from async agent workflows — when an async agent executes a tool, it eventually calls into this sync path.
When an event loop is already running (which it always is in async workflows), asyncio.run() raises:
RuntimeError: asyncio.run() cannot be called from a running event loop
Why this matters
The philosophy states: "Multi-agent + async safe by default". Any approval backend that only implements the async request_approval() method (not the optional request_approval_sync()) will crash when used from an async agent workflow. This is a silent correctness trap — it works in sync-only usage and fails only in production async deployments.
The deeper pattern issue
This is not an isolated case. The codebase has a general pattern of sync methods that assume they're never called from an async context. The asyncio.run() fallback suggests awareness of the async path, but the fix is incomplete — it should detect a running loop and use loop.run_until_complete() or an async-native path instead.
Impact
- Hard crash in async multi-agent workflows when tool approval backends lack
request_approval_sync()
- Works fine in testing (sync), fails in production (async) — the worst kind of bug
- Violates: "Multi-agent + async safe by default"
Suggested Fix
- Detect running event loop before choosing sync vs async path:
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = None
if loop is not None:
# We're in an async context — schedule coroutine properly
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as pool:
decision = loop.run_in_executor(pool, lambda: asyncio.run(backend.request_approval(request)))
else:
decision = asyncio.run(backend.request_approval(request))
-
Or better: require all approval backends to implement request_approval_sync() and make it part of the protocol, removing the asyncio.run() fallback entirely.
-
Audit for other asyncio.run() calls in sync methods reachable from async contexts.
Files
src/praisonai-agents/praisonaiagents/agent/agent.py (lines 5064-5070)
Summary
The tool approval path in
Agentcallsasyncio.run()from a synchronous method that is reachable from async execution contexts. This causes a hardRuntimeErrorwhen an async workflow triggers tool approval.Problem
agent.py:5064-5067:This is inside
_check_tool_approval_sync(), which is called from the synchronous_execute_tool_impl(). However,_execute_tool_impl()is callable from async agent workflows — when an async agent executes a tool, it eventually calls into this sync path.When an event loop is already running (which it always is in async workflows),
asyncio.run()raises:Why this matters
The philosophy states: "Multi-agent + async safe by default". Any approval backend that only implements the async
request_approval()method (not the optionalrequest_approval_sync()) will crash when used from an async agent workflow. This is a silent correctness trap — it works in sync-only usage and fails only in production async deployments.The deeper pattern issue
This is not an isolated case. The codebase has a general pattern of sync methods that assume they're never called from an async context. The
asyncio.run()fallback suggests awareness of the async path, but the fix is incomplete — it should detect a running loop and useloop.run_until_complete()or an async-native path instead.Impact
request_approval_sync()Suggested Fix
Or better: require all approval backends to implement
request_approval_sync()and make it part of the protocol, removing theasyncio.run()fallback entirely.Audit for other
asyncio.run()calls in sync methods reachable from async contexts.Files
src/praisonai-agents/praisonaiagents/agent/agent.py(lines 5064-5070)