Quick Start
1
From a sync tool
Use The user runs sync code that needs async I/O; the bridge executes coroutines without nested event loops.
run_coroutine_from_any_context to call async code from a sync tool:2
From an async tool
Use
run_sync_in_executor to call blocking code from an async tool without blocking the event loop:3
Detecting the context
Use
is_async_context to create dual-mode helpers:How It Works
The bridge probes for a running event loop usingasyncio.get_running_loop(). If no loop exists, it safely creates one with asyncio.run(). If a loop is already running, it raises RuntimeError to prevent deadlocks.
Configuration Options
Common Patterns
Reusing async SDKs from sync tools
Offloading blocking calls from async tools
Context-aware dual-mode helper
Best Practices
Prefer await when you're already async
Prefer await when you're already async
Calling
run_coroutine_from_any_context inside an async def raises RuntimeError by design. If you’re in a coroutine, use await instead:Don't wrap everything
Don't wrap everything
Only wrap at the true sync/async boundary. Avoid creating unnecessary bridge calls in the middle of your call stack:
Set a sensible timeout
Set a sensible timeout
The default 300 seconds is large for most use cases. Tighten for latency-critical tools:
Check is_async_context() for dual-mode helpers
Check is_async_context() for dual-mode helpers
When building utilities that work in both sync and async contexts, check the context first:
Used by
The following synchronous APIs route throughrun_sync() and therefore honour PRAISONAI_RUN_SYNC_TIMEOUT consistently:
praisonai.bots.WebhookApproval.request_approval_sync()praisonai.bots.HTTPApproval.request_approval_sync()praisonai.integrations.get_available_integrations()praisonai._run_praisonai(added PR #1681) — boots the InteractiveRuntime on the persistent background loop. If you callPraisonAI.run()from inside a running event loop, you now get a clearRuntimeErrorinstead of a silent deadlock.- All ~77 wrapper-side
run_synccall sites (gateway, a2u, mcp_server, scheduler) — see PR #1583 for the full list.
The wrapper-layer bridge (
praisonai._async_bridge) creates its
background loop lazily on the first run_sync() call. Pure imports
do not allocate a loop or thread. Calling the module-level shutdown() before any
run_sync() is a safe no-op — it only affects the shared default bridge, not any AsyncBridge() instances you create yourself.Troubleshooting
RuntimeError: run_coroutine_from_any_context() cannot be called from async context
You’re trying to use the bridge inside a coroutine. Useawait instead:
asyncio.run() cannot be called from a running event loop
This error used to leak from SDK internals before the async bridge was implemented. If you see this on current versions, upgrade to the latest release.
Test reference:
praisonai/tests/unit/test_async_bridge.py::TestBridgeIntegration::test_timeout_cancels_coroutine_and_runs_finally — quote this in the page so users can verify the behaviour locally.
PermissionError in approval system
The approval system now fails fast in async contexts. Configure a non-console backend:Wrapper Bridge (praisonai._async_bridge)
The wrapper layer provides a module-level run_sync() for CLI scripts and single-tenant servers, plus a public AsyncBridge class when you need an isolated loop per tenant or service.
- Module-level (default)
- Per-instance AsyncBridge
When to use a per-instance bridge
API Reference:
Environment:
PRAISONAI_RUN_SYNC_TIMEOUT: Default timeout in seconds (300)
- CLI approval protocol (ACP/LSP tools)
- Interactive runtime start/stop operations
- Deployment scheduler
- Gateway operations
Per-Session Scoped Bridge
Servers and gateways that handle multiple concurrent sessions need each session to run on its own loop+thread binding.current_bridge() and scoped_bridge() provide ContextVar-backed per-session isolation so sessions never share a bridge accidentally.
When to use scoped bridges
Usescoped_bridge() inside any request handler that may run concurrently with other handlers — for example a FastAPI endpoint, a Starlette WebSocket handler, or a custom bot session dispatcher.
scoped_bridge() context manager
ContextVar so nested scopes work correctly in async tasks and threads — each concurrent task sees only its own bridge.
Scope-owned bridge (preferred)
scoped_bridge() creates a fresh bridge and tears it down with permanent=True when the with block ends. Internal code that calls module-level run_sync() inside the block uses the scoped bridge via contextvars — no import changes required.
current_bridge() for introspection
current_bridge() returns the bridge bound to the current async task, or None when no scope is active. Use it to inspect which bridge is in use without passing it explicitly through call stacks.
Multi-session server example
API Reference
Related
Async Agents Guide
Thread Safety & Concurrency

