Quick Start
What the Registry Controls
Registering through thepraisonai.framework_adapters entry-point group gives your adapter automatic visibility across three surfaces — no extra wiring required:
| Surface | Effect |
|---|---|
praisonai --framework <name> | Your adapter name appears in the argparse choices= list |
praisonai doctor config | agents.yaml framework: <name> passes validation |
| Install-hint messages | Your adapter’s install_hint attribute is shown when the framework module is missing |
How It Works
The framework adapter registry provides a central point for managing framework implementations:| Operation | Description | When Called |
|---|---|---|
| Discovery | Entry points auto-loaded on first registry access | Import time |
| Registration | Adapters registered by name | Plugin installation |
| Creation | Adapter instances created on demand | CLI framework selection |
| Availability | Framework dependencies checked | Before execution |
Auto-Discovery in the CLI
Once your adapter is registered (in-process viaregister() or via the praisonai.framework_adapters entry-point group), it appears automatically wherever framework names are listed or validated — no further wiring needed:
| Surface | What it shows / validates against |
|---|---|
praisonai --framework <name> argparse choices | list_framework_choices(include_unavailable=True) |
praisonai doctor agents.yaml framework: field check | list_framework_choices(include_unavailable=True) |
| Missing-framework error → install hint | adapter’s own install_hint (falls back to pip install 'praisonai-frameworks[<name>]') |
praisonai.framework_adapters.list_framework_choices(). When include_unavailable=False (the default) only adapters whose is_available() returns True are returned; the CLI uses include_unavailable=True so users see every registered framework and get a friendly error if its dependencies are missing.
Declaring an install hint
Surface a tailored install command for your adapter so users don’t see the generic fallback:myframework is selected but not installed, the CLI now raises:
install_hint, the wrapper falls back to pip install 'praisonai-frameworks[myframework]'.
Missing optional dependencies no longer leak
FrameworkAdapterRegistry.is_available() now catches ImportError raised when an adapter’s constructor touches a missing optional dependency, alongside the existing ValueError/TypeError. A plugin with unmet deps reports as unavailable rather than crashing the CLI list, the doctor check, or pick_default().
Helpers exposed at the package root
Configuration
Framework Adapter Registry API
Complete API reference for FrameworkAdapterRegistry
Registry Methods
| Method | Parameters | Description |
|---|---|---|
get_default_registry() | None | Module-level factory; lazy creates a single process-wide registry |
register(name, cls) | name: str, cls: Type[FrameworkAdapter] | Register adapter at runtime |
unregister(name) | name: str → bool | Remove adapter; returns True if found |
resolve(name) | name: str → Type | Get adapter class; raises ValueError if missing |
create(name, *args, **kwargs) | varies | Create an adapter instance; validates the run() signature (PR #2083) and raises TypeError if the four protocol kwargs are missing |
list_names() | None → list[str] | Sorted list of registered names |
list_registered() | None → list[str] | Backward-compat alias of list_names() |
is_available(name) | name: str → bool | Check adapter is registered AND its is_available() returns True |
Adapter Protocol
Framework adapters must implement theFrameworkAdapter protocol:
| Method / Attribute | Required | Description |
|---|---|---|
name | ✅ | Unique framework identifier |
install_hint | ❌ | String shown when the framework is missing (falls back to pip install 'praisonai-frameworks[<name>]') |
requires_tools_extra | ❌ | bool, default False. Set True if the adapter needs the tools extra. |
is_router | ❌ | bool, default False. Set True for router adapters that delegate to concrete siblings — skips run() protocol validation at registration time. |
SUPPORTS_WORKFLOW | ❌ | bool, default False. Set True (class-level) to accept native workflow YAML dispatch. See Capability Flags. |
SUPPORTS_RUNTIME_FEATURES | ❌ | bool, default False. Set True (class-level) to accept cli_backend / runtime config keys. See Capability Flags. |
is_available() | ✅ | Check framework dependencies |
resolve(*, config=None) | ❌ | Pick the concrete adapter variant from YAML config (keyword-only config; default returns self) |
resolve_alias() | ❌ | Return the concrete adapter name to dispatch to (string). Default returns self.name. Override when your adapter is a router that picks a sibling adapter at runtime by name. |
setup(*, framework_tag) | ❌ | Framework-specific pre-run hooks (default no-op) |
run(config, llm_config, topic, *, tools_dict, agent_callback, task_callback, cli_config) | ✅ | Execute framework logic (all four trailing kwargs are validated at registration time — see Troubleshooting) |
arun(config, llm_config, topic, *, tools_dict, agent_callback, task_callback, cli_config) | ❌ | Async execution path. When SUPPORTS_ASYNC is False (default), it offloads run() to this adapter’s bounded thread pool and logs a one-line warning. Override with a native async def for a fully async path. |
cleanup() | ❌ | Release resources after execution (default no-op; also shuts down the offload pool) |
SUPPORTS_ASYNC | ❌ | bool, default False. Set True when you override arun() with a native async def. When False, arun() offloads run() to the adapter’s bounded thread pool and logs a one-line warning. |
_THREAD_OFFLOAD_MAX_WORKERS | ❌ | int, default 4. Size of this adapter’s private ThreadPoolExecutor used by arun()’s sync-offload path. Override on the class or instance for high-concurrency workloads. |
Protocol Validation (PR #2083)
Since PR #2083,FrameworkAdapterRegistry.create() validates every adapter at construction time. If the run() method does not accept the four keyword-only parameters tools_dict, agent_callback, task_callback, and cli_config, instantiation fails with:
registry.is_available("<name>") == False instead of raising — so a broken plugin will simply disappear from the available-framework list rather than crashing the CLI.
resolve_alias() (PR #2086) and resolve(*, config=None) (PR #2070) serve different roles. Use resolve(*, config=...) when variant selection depends on YAML keys such as autogen_version. Use resolve_alias() when routing depends on environment or runtime state. Family adapters may combine both.arun() offloads run() to a bounded per-adapter thread pool, allowing sync adapters to be called from async contexts without blocking the event loop. Unlike asyncio.to_thread (which shares the process-wide executor), each adapter gets its own ThreadPoolExecutor sized by _THREAD_OFFLOAD_MAX_WORKERS (default 4), so one slow framework run cannot starve every other async caller.
Override arun() with a native async def and set SUPPORTS_ASYNC = True to skip the thread offload entirely and use the framework’s own async API.
Async capability flags
SetSUPPORTS_ASYNC = True when you provide a native async def arun(); leave it False to inherit the bounded thread-pool offload.
| Flag | Default | Consulted by | Effect when False / unchanged |
|---|---|---|---|
SUPPORTS_ASYNC | False | BaseFrameworkAdapter.arun() | Sync run() is offloaded to the adapter’s bounded thread pool and a one-line warning is logged |
_THREAD_OFFLOAD_MAX_WORKERS | 4 | _get_thread_pool() (offload path) | Caps concurrent offloaded run() calls; increase for high-concurrency sync adapters |
Only
PraisonAIAdapter sets SUPPORTS_ASYNC = True today — its arun() awaits the native async run path directly. The built-in CrewAIAdapter and AutoGenAdapter keep SUPPORTS_ASYNC = False and use the bounded thread-pool offload.Capability Flags
Capability flags are class-level booleans that tell PraisonAI which features your adapter can execute. Three flags gate distinct wrapper features. All default toFalse, so an adapter opts in only to what it can actually run.
| Flag | Type | Default | What it grants |
|---|---|---|---|
SUPPORTS_ASYNC | bool | False | Adapter has a native async run path; skips the bounded thread-pool offload in arun(). |
SUPPORTS_WORKFLOW | bool | False | Adapter can execute native workflow YAML. Read by validate_workflow_framework() and JobExecutor._framework_supports_native. |
SUPPORTS_RUNTIME_FEATURES | bool | False | Adapter supports cli_backend, runtime, models.*.runtime, and providers.*.runtime_default. Read by AgentsGenerator._validate_cli_backend_compatibility. |
PraisonAIAdapter sets both SUPPORTS_WORKFLOW and SUPPORTS_RUNTIME_FEATURES to True. Third-party adapters registered via the praisonai.framework_adapters entry-point group opt in by setting the flags on their subclass.
Each flag maps to the wrapper call site that reads it:
The default
False is intentional. An adapter that never sets the flags cannot accidentally accept a workflow YAML or cli_backend config it has no code to execute — the wrapper rejects the config with an actionable error instead of failing mid-run.Adding an Async Path
Sync-only adapter (default)
Leave
arun() unoverridden and SUPPORTS_ASYNC at its False default. The base class offloads run() to the adapter’s bounded thread pool automatically:Family Router Pattern — AutoGenFamilyAdapter as Canonical Example
A family adapter routes to a concrete sibling adapter based on environment or config. It overrides resolve_alias() to return an adapter name, then resolve() uses that name to look up and return the concrete instance.
resolve_alias(config)accepts optionalconfigdict — configautogen_versionkey wins overAUTOGEN_VERSIONenv varresolve()passesconfigtoresolve_alias(), then fetches the concrete adapter from the registryrun()has the full four keyword-only kwargs and raisesRuntimeError— the orchestrator callsresolve()first, then callsrun()on the returned concrete adapter
As of PR #2654, AutoGen v0.2 now wires YAML
tools: into the chat via register_for_llm + register_for_execution, and translates a per-call ToolTimeoutError into an LLM-readable string in _wrap_tool_for_execution. Custom adapters that run tools inside their own loop should follow the same _wrap_tool_for_execution pattern for timeout translation. See AutoGen v0.2 YAML Tools.Optional Adapter Hooks
After the existing “Adapter protocol” section, two new optional methods were added in PR #1763:resolve(*, config=None) — pick a concrete variant (PR #2070).
AutoGenAdapter.resolve: the orchestrator passes YAML config; the adapter instantiates siblings directly:
setup(*, framework_tag) — pre-run hook.
Orchestrator Pipeline (Post-#1763, updated for #2086)
The orchestrator (AgentsGenerator.generate_crew_and_kickoff) now follows this sequence:
Common Patterns
Override Built-in Adapter
Subprocess Framework Wrapper
Conditional Dependencies
Best Practices
Handle Missing Dependencies Gracefully
Handle Missing Dependencies Gracefully
Always implement defensive
is_available() checks:Avoid Import-Time Failures
Avoid Import-Time Failures
Don’t import heavy dependencies at module level:
Use Structured Logging
Use Structured Logging
Log framework events for debugging:
Implement Proper Resource Cleanup
Implement Proper Resource Cleanup
Always clean up resources in the
cleanup() method:Don't import heavy deps inside is_available
Don't import heavy deps inside is_available
Use cached availability checks to avoid multi-second imports on every probe:
Prefer native async for high-concurrency workloads
Prefer native async for high-concurrency workloads
The default
arun() offloads run() to the adapter’s bounded thread pool (_THREAD_OFFLOAD_MAX_WORKERS, default 4). For high-concurrency workloads, either raise the pool size or override arun() with a native async def (and set SUPPORTS_ASYNC = True) to avoid offload contention:Registry Helpers
Registry-level helpers for building CLIs and plugins:| Helper | Returns | Use case |
|---|---|---|
list_framework_choices(*, include_unavailable=False) | list[str] | Build the CLI --framework choice list (registered adapters; pass include_unavailable=True for the full list) |
list_available_frameworks() | list[str] | Filter to just those whose is_available() returns True |
get_install_hint(name) | str | Adapter-aware install hint string (e.g. pip install 'praisonai-frameworks[autogen]') |
framework_option_help() | str | Auto-generated --help text for the --framework flag |
Default Framework Selection
FrameworkAdapterRegistry.pick_default() selects the default framework when none is specified.
DEFAULT_PRIORITY is a class attribute on FrameworkAdapterRegistry:
pick_default() walks this tuple and returns the first available adapter. If none of the three built-ins are installed, it falls back to any other registered adapter — including entry-point plugins. If no adapter is available at all, it raises RuntimeError.
ag2 is intentionally omitted from DEFAULT_PRIORITY because the AG2 adapter is an unimplemented stub. Users can still select it explicitly via --framework ag2 or framework: ag2 in YAML, but it will not be chosen automatically — and calling it will raise NotImplementedError until an implementation lands. The invariant is enforced by the test_ag2_not_in_default_builtins regression test._entrypoint.run() and arun() now delegate to pick_default(). This means an entry-point adapter can become the default framework once the four built-ins are uninstalled:
The
praisonaiagents → praisonai rename is reflected in DEFAULT_PRIORITY — the tuple uses "praisonai", not "praisonaiagents". Any YAML or code that relied on the old probe tuple ("crewai", "praisonaiagents", "autogen", "ag2") now uses pick_default() instead.The static fallback list used by the CLI only when the adapter layer itself cannot be imported is ["ag2", "autogen", "crewai", "praisonai"] (alphabetical) — ag2 still appears in this install-hint list for discoverability, but is not in the runtime DEFAULT_PRIORITY tuple. This prevents pick_default() from routing to ag2 and hitting a NotImplementedError.Inject Your Own Registry (Multi-tenant / Tests)
If you don’t pass
adapter_registry, you get the process-default registry shared by all callers in the same Python process. Use a custom registry for tenant isolation, sandboxed tests, or registering one-off adapters that should not leak across runs.adapter_registry= kwarg on AgentsGenerator and AutoGenerator enables dependency injection for multi-tenant applications and testing:
AutoGenerator:
WorkflowAutoGenerator — its framework= kwarg is honoured end-to-end (see AutoAgents).
Troubleshooting
TypeError: missing keyword-only parameters ['agent_callback', 'cli_config', 'task_callback', 'tools_dict']
TypeError: missing keyword-only parameters ['agent_callback', 'cli_config', 'task_callback', 'tools_dict']
Your adapter’s Your code can still ignore the values — the signature just needs to accept them. The four parameters can be either
run() method is missing the four keyword-only parameters required by the FrameworkAdapter protocol. As of PR #2083, FrameworkAdapterRegistry.create() validates these at adapter construction time.Update your run() signature to:KEYWORD_ONLY (after *) or POSITIONAL_OR_KEYWORD; either form passes validation.My adapter disappeared from the available frameworks list
My adapter disappeared from the available frameworks list
If
praisonai --list-frameworks (or registry.list_names()) no longer shows your adapter, run registry.is_available("<your-adapter-name>"). Since PR #2083, is_available() catches TypeError from the new protocol validator and returns False rather than raising — so a malformed plugin is silently filtered out of the available list. Since PR #2415, is_available() also catches ImportError raised during adapter construction (e.g., when the adapter’s __init__ touches an optional dependency). Check the application logs for the error message above and fix the run() signature or ensure your dependencies are properly guarded.Sync adapter called from async context
Sync adapter called from async context
The default
arun() offloads sync run() calls to the adapter’s bounded thread pool and logs a one-line warning. This is safe for most workloads. If you need a fully async path, set SUPPORTS_ASYNC = True and override arun() with a native async def:Related
Registry Dependency Injection
Learn about injecting custom registries for multi-tenant isolation
CrewAI Integration
See how built-in framework adapters are implemented
Workflow YAML Framework Field
How framework: works in workflow YAML vs agents.yaml

