Skip to main content

Include praisonai package in your project

Option 0: One-liner (simplest)

That’s it. praisonai.run() reuses the same framework auto-detection and LLM endpoint resolution the praisonai CLI uses, so anything that works on the command line works here.

What it does

1
Auto-detects the first installed framework in this order: crewai → praisonaiagents → autogen. Pass framework="..." to override — ag2 and langgraph must be requested explicitly.
2
Reads OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL_NAME (and the standard PraisonAI key/config files) — same as the CLI.
3
Returns the final task output as a string.

Async variant for FastAPI / Jupyter

arun() offloads the synchronous work to a thread so it never blocks the event loop.

Optional parameters

If no framework is installed, run() raises RuntimeError listing available adapters. Install one with pip install praisonaiagents (recommended for new projects). ag2 and langgraph are not in the auto-detect chain; pass framework="ag2" or framework="langgraph" explicitly to select them. Note that ag2 is an unimplemented stub and will raise NotImplementedError when called.

Advanced CLI options from Python

run() and arun() accept any extra keyword argument the CLI accepts. Two loose names get a friendly Python alias; everything else is forwarded through the same cli_config bridge the CLI already uses.
Prior to release v4.6.163 the Python API accepted only the five documented parameters and silently dropped any other kwarg. If you’re on an older version, upgrade before relying on model= / session=.

When to use PraisonAI(...) instead

Reach for the full PraisonAI class (Option 1 below) when you need:
  • Streaming / approval-system integration
  • Custom backends or the gradio UI
  • The auto="..." natural-language-to-YAML mode
  • Repeated runs against the same generator without re-parsing YAML

Option 1: Using RAW YAML

Option 2: Using separate agents.yaml file

Note: Please create agents.yaml file before hand. If you only need to run an agents.yaml once and want zero ceremony, see Option 0: One-liner above.

Other options

Lifecycle / cleanup

Each AgentsGenerator lazily creates a bounded thread-pool to run synchronous tools under per-call timeouts. The pool is owned by the instance, not the process — concurrent sessions in a multi-tenant runtime never share workers. When you’re done with a generator, release the pool with .close() or use it as a context manager:
close() is idempotent — calling it twice is safe.
Sync runs are now auto-scoped. generate_crew_and_kickoff() wraps its adapter.run(...) call in scoped_bridge() automatically, so each sync run isolates its run_sync-driven work onto its own loop+thread. Multi-tenant sync embedders no longer need to add their own scoped_bridge() around generate_crew_and_kickoff — a stuck coroutine in one tenant’s run cannot park the shared loop for another. The same isolation now covers the YAML workflow sync entry (_run_yaml_workflow / praisonai workflow run) — workflow.start(...) runs under its own scoped_bridge() too. agenerate_crew_and_kickoff() and the async workflow path are intentionally not wrapped (they already run on the caller’s loop). See Async Bridge → Per-run auto-scoping.

Cross-generator tool isolation

Multiple AgentsGenerator instances can wrap the same shared framework tool object (common with CrewAI / LangChain tools registered once and reused across tenants) without interfering with each other. Each generator installs its own per-instance _TimeoutBoundTool proxy — a lightweight wrapper that delegates schema attributes (name, description, args_schema, …) to the shared inner tool but routes _run / run through its own executor. The shared inner object is never mutated in place, so shutting down one generator’s pool cannot break another generator’s calls on the same tool. The proxy is built as an instance of a dynamic subclass of the wrapped tool’s class, so isinstance(proxy, BaseTool) (and any framework-specific base class check) keeps returning True. This preserves the isinstance-based dispatch that praisonaiagents.tool_execution, CrewAI, and LangChain executors rely on to call .run — without it, a BaseTool plugin tool with tool_timeout set would silently execute nothing.
cli_config templates stay clean. As of PR #5228, the generator’s per-run _tool_timeout_wrap / _agent_tool_wrap_resolver closures are not stamped onto self.cli_config any more. They live on a private self._run_ctx and are merged into the adapter’s cli_config kwarg only at dispatch, via AgentsGenerator._dispatch_cli_config(). Multi-tenant embedders passing a shared cli_config template (per-tenant, per-request, or captured from vars(args)) no longer see it mutated after a run — it never comes back holding instance-bound lambdas. Adapters still read _tool_timeout_wrap / _agent_tool_wrap_resolver off the cli_config kwarg they receive, so no adapter signature changes.

Injecting a shared executor (multi-tenant)

If you already manage a thread-pool (e.g. one per tenant), inject it via tool_timeout_executor=. The generator will use the pool but never shut it down — ownership stays with you.
Each owned pool is named praisonai-tool-timeout-<hexid> for easy enumeration in top / py-spy / thread dumps.

tool_timeout_executor parameter

strict_validation and tool_timeout_workers are also recognised keys inside cli_config, so run() / arun() forward them through the same **kwargs bridge.
Env vars still work. Set the per-run override only when you need multi-tenant isolation.
Leaked tool-timeout pools accumulate threads named praisonai-tool-timeout-<hexid> across instances. Use with AgentsGenerator(...) as gen: or call gen.close() to release them. Check top / py-spy if you suspect thread accumulation.
Passing tool_timeout_executor= means close() won’t shut it down. You manage the lifecycle — call your_pool.shutdown() when you’re done with it.
Don’t share AgentsGenerator instances across tenants. Share the executor instead — inject the same pool via tool_timeout_executor= into each per-tenant generator.Sharing an AgentsGenerator across tenants is still discouraged (per-instance timeout executor lifecycles are per-instance), but sharing the underlying tool objects across generators is now safe — the per-generator proxy guarantees that A’s timeout wrapper cannot leak into B’s calls, and closing A’s pool cannot break B’s calls on the same tool.
With the CrewAI framework, context=[...] lookup keys on the task name. Two roles defining the same task name now raise ValueError — pick names like <role>_<verb> for clarity.

Logging in scripts

If you want PraisonAI to configure your application’s logging, call configure_cli_logging() once at startup:
If you want PraisonAI to leave your application’s logging untouched, just don’t call configure_cli_logging — only namespaced praisonai.* loggers will be used.

C9 Architecture: Four-Tier Package Model (Developer Reference)

This section covers the internal four-tier package architecture introduced in C9 (PR #2633). It is relevant to contributors and maintainers, not end-users.

Sibling: praisonai-bot

As of C9 (merged 2026-07-03), bots, gateway, and channel CLI live in a new sibling PyPI package praisonai-bot (module praisonai_bot). The praisonai.bots, praisonai.gateway, and praisonai.daemon paths remain as alias_package shims and are the stable public API.

praisonai-bot Migration Guide

Install options, CLI reference, backward-compat guarantees, and channel extension guide

Built-in Channels

Telegram, Discord, Slack, WhatsApp, Linear, Email, AgentMail

Four-Tier Model

PraisonAI uses a strict four-tier package model with a one-way dependency rule: Invariant: Both praisonai-code and praisonai-bot declare no PyPI dependency on praisonai. All cross-tier access goes through _wrapper_bridge only.

alias_package shim: lazy vs eager

The praisonai.bots / praisonai.gateway / praisonai.daemon alias_package shims default to an eager subtree walk at shim-install time. This preserves old_name.sub is new_name.sub — the module-identity guarantee that keeps unittest.mock.patch("old.sub.attr") and isinstance checks working after the C9 migration, even when the running code imports the moved package under its new name. Set PRAISONAI_SHIM_LAZY=1 to opt out of the eager walk (pure _AliasFinder lazy resolution). The trade-off: faster shim install and no eager pull of optional heavy deps — but mock.patch against the old dotted path can miss when code imports via the new name. As of PR #4261, a failed eager import is no longer silently swallowed — it logs a warning with traceback (shim: eager import of %s failed; will be resolved lazily) and is left for _AliasFinder to resolve lazily. None placeholders in sys.modules are skipped instead of poisoning the old dotted alias. The warning is a useful debugging aid when a downstream AttributeError on the alias would otherwise have no traceback pointing back at the shim.
Keep eager registration whenever anything in the process relies on mock.patch against the old dotted path (praisonai.bots.*, praisonai.gateway.*, praisonai.daemon.*) or on old_name.sub is new_name.sub identity.
Use it only when no mock.patch targets the old dotted path and minimal-container startup speed matters more than mock-patch parity. See Security Environment Variables → PRAISONAI_SHIM_LAZY.

C8 Architecture: Wrapper–Code Boundary (Historical Reference)

The following section documents the C8 three-tier model that preceded C9. The three-tier diagram and metrics are preserved for historical reference.

Three-Tier Model (pre-C9 / C8)

Before C9, PraisonAI used a three-tier package model: Invariant: praisonai-code/pyproject.toml declares no PyPI dependency on praisonai. All praisonai-code → praisonai wrapper access goes through praisonai_code._wrapper_bridge only.

C8 Metrics (post-C8, merged 2026-07-02)

_wrapper_bridge — The Only Cross-Tier Path

praisonai_code._wrapper_bridge is the sole permitted mechanism for praisonai-code to call into praisonai. It lazy-loads the wrapper at runtime and never causes an import-time error when the wrapper is absent.
Direct import praisonai or from praisonai import … inside praisonai-code is forbidden and enforced by scripts/check_c7_imports.sh.

_WRAPPER_RESIDENT_COMMANDS

Commands that live in the praisonai wrapper but are registered in the praisonai-code CLI router. The variable was renamed from _WRAPPER_COMMANDS in C8.1 (backward-compat alias retained). get_command() lazy-loads these via the absolute praisonai.cli.commands.* path through the bridge. C8.2 Repatriated Commands (moved from praisonai-code back to praisonai):

C8.3 Repatriated Features

The following cli/features/* modules moved from praisonai-code to praisonai/cli/features/: recipe, templates, deploy, recipe_optimizer, persistence, eval, agent_scheduler, acp, registry, sandbox_cli, ollama, workflow, tui/app, interactive/async_tui, interactive/core Also repatriated: commands/recipe, context, mcp, validate.

Protocols & Adapters (C8.5)

C8.5 introduces typed protocol contracts to enforce the tier boundary. The SessionStoreProtocol (session persistence) already ships in praisonaiagents:
TemplateStoreProtocol and ServeHandlerProtocol are defined in the C8.5 design but their physical extraction is deferred (same milestone as the PraisonAI class split). The praisonai.adapters module lazily re-exports existing adapter classes (AutoReader, ChromaVectorStore, BasicRetriever, FusionRetriever, LLMReranker, and registration helpers) to preserve backward compatibility. Registration into the core-SDK singletons is now automatic at the wrapper entry points (praisonai.run, praisonai.arun, and every praisonai CLI command that runs an agent file) — register_default_adapters() is called for you before the agent starts, so YAML declarations like retriever: fusion and reranker: llm resolve out of the box (PraisonAI 4.7.9+). Library-mode Python callers importing praisonai.adapters.* directly still call register_default_adapters() themselves (idempotent + lock-guarded, so a subsequent auto-call is a no-op). Multi-tenant hosts can pre-register custom adapters under built-in names — the wrapper defaults preserve any existing entry (register-only-if-absent) and never overwrite.

Writing a custom managed backend

ManagedBackendBase provides the shared async stream() producer-thread + sentinel-queue pump. AnthropicManagedAgent and LocalManagedAgent both inherit from it; a third managed backend (E2B, Modal, Fly.io, …) implements two hooks instead of copying the queue loop.
ManagedBackendBase lives in praisonai.integrations._managed_base — the leading underscore marks it as an extension point rather than a stable public import path. Cite the class name and subclass it; if a top-level praisonai.integrations re-export lands later, switch to that.
_ensure_session runs before the first _iter_events call. A backend that snapshots session state should populate agent_id / environment_id inside _ensure_session — LocalManagedAgent._ensure_session() creates the inner agent first for exactly this reason, so the first persisted snapshot is complete rather than orphaned.
Do NOT override async def stream(self, prompt, **kwargs) — the base class owns it, including the run_in_executor hop over queue.get. Overriding it re-introduces the duplication this refactor removed, and future protocol fixes will bypass your backend.

C8.4 Legacy Structure

  • praisonai/cli/legacy/inbuilt_tools.py, framework_run.py — extraction targets for lazy loaders.
  • praisonai-code/cli/legacy/prompt_dispatch.py — standalone-safe helpers.
  • main.py still contains the PraisonAI class body; wrapper access is normalised via the bridge. The physical 7k-line split is deferred (out of scope of C8).

Developer Tooling (C8)

Deferred Work (C8)

The physical extraction of the PraisonAI class (~7k lines in main.py) to praisonai/cli/legacy/praison_class.py was explicitly deferred from C8. The docs/concepts/architecture.mdx page (HUMAN-ONLY) also needs a maintainer update to reflect the C8 metrics.