Quick Start
1
Programmatic Registration
Register a framework adapter directly in your code:
2
Entry-Point Plugin (installable)
Create a pip-installable plugin — no manual After
registry.register() call needed:pip install, the adapter appears in praisonai --framework choices and praisonai doctor automatically — no manual registry.register() call needed.What the Registry Controls
Registering through thepraisonai.framework_adapters entry-point group gives your adapter automatic visibility across three surfaces — no extra wiring required:
How It Works
The framework adapter registry provides a central point for managing framework implementations: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:
The single source of truth is
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().
Adapter Resolution Errors
When the requested framework can’t be resolved — the entry-point package isn’t installed, the adapter dependency is missing, the framework name is typo’d, or the generator is running in minimal/mock mode without an_adapter_registry — PraisonAI logs a Could not resolve framework adapter for <framework>: <error> warning and falls back to native execution. Previously this was swallowed silently. If you see this warning: install the plugin, correct the framework name, or drop the --framework flag to run natively.
Helpers exposed at the package root
Configuration
Framework Adapter Registry API
Complete API reference for FrameworkAdapterRegistry
Registry Methods
Adapter Protocol
Framework adapters must implement theFrameworkAdapter protocol:
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.
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.
The built-in
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
1
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:2
Native async adapter
Override
arun() with a real async def and set SUPPORTS_ASYNC = True to use the framework’s native async API: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: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

