Skip to main content
Framework adapter plugins enable third-party developers to add new execution frameworks to PraisonAI without modifying core code.
The user installs a third-party adapter package; entry-point discovery registers it for CLI framework selection.
Deprecated: Direct imports of concrete built-in adapter classes from praisonai.framework_adapters (CrewAIAdapter, AutoGenAdapter, AutoGenV4Adapter, AG2Adapter) now emit DeprecationWarning. Use the registry (get_default_registry().create("crewai")) or register via the entry-point group instead.

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 registry.register() call needed:
After 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 the praisonai.framework_adapters entry-point group gives your adapter automatic visibility across three surfaces — no extra wiring required:
See Framework Availability — Dynamic Discovery for more details on how the registry feeds these surfaces.

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 via register() 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:
When myframework is selected but not installed, the CLI now raises:
If you omit 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 the FrameworkAdapter 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:
Adapters that fail validation also report 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.
Migrating from resolve_variant: If you authored a custom framework adapter against PR #1988 (resolve_variant(config, registry)), rename your method to resolve and drop the registry parameter. The orchestrator calls adapter.resolve(config=config). Accept config as keyword-only: *, config=None. Instantiate sibling adapters directly (e.g. AutoGenV4Adapter()) rather than calling registry.create(...).As of PR #2257 the no-op resolve_variant default has been removed from both the FrameworkAdapter Protocol and BaseFrameworkAdapter. Old adapters that still define resolve_variant will have it silently ignored — only resolve(*, config=...) and resolve_alias() are invoked.
Default 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

Set SUPPORTS_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 to False, 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.
Set capability flags as class-level attributes. Some call sites read the flag off the adapter class before an instance is fully configured, so instance-level assignment (e.g. inside __init__) can be bypassed.

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.
Key points:
  • resolve_alias(config) accepts optional config dict — config autogen_version key wins over AUTOGEN_VERSION env var
  • resolve() passes config to resolve_alias(), then fetches the concrete adapter from the registry
  • run() has the full four keyword-only kwargs and raises RuntimeError — the orchestrator calls resolve() first, then calls run() 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).
Worked example — 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

Always implement defensive is_available() checks:
Don’t import heavy dependencies at module level:
Log framework events for debugging:
Always clean up resources in the cleanup() method:
Use cached availability checks to avoid multi-second imports on every probe:
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.
The new adapter_registry= kwarg on AgentsGenerator and AutoGenerator enables dependency injection for multi-tenant applications and testing:
For AutoGenerator:
For workflow YAML, use WorkflowAutoGenerator — its framework= kwarg is honoured end-to-end (see AutoAgents).

Troubleshooting

Your adapter’s 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:
Your code can still ignore the values — the signature just needs to accept them. The four parameters can be either KEYWORD_ONLY (after *) or POSITIONAL_OR_KEYWORD; either form passes validation.
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.
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:

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