Quick Start
integration.as_tool() returns a sync callable that is safe to call from both sync and async agent runtimes. When registering with a native async agent, prefer integration.as_async_tool() — it returns an async callable that skips the worker-thread hop.1
Attach Claude Code as an agent tool
2
One-shot call via the integration API
3
Continue a previous Claude Code session
4
Stream events live
timeout argument is enforced end-to-end: stream_async(timeout=...) applies a monotonic deadline to every stdout read and the final drain, so a stalled claude/gemini/codex/cursor subprocess raises TimeoutError: Stream timed out after {timeout}s: {cmd} instead of hanging forever.5
Per-call output format
6
Register as a native async tool
as_async_tool() returns a coroutine tool named <cli>_atool (for example claude_atool). It is functionally equivalent to as_tool(), but with no thread hop — measurable when the agent invokes the CLI many times in one turn.How It Works
The integration builds CLI arguments dynamically per call, with no instance state mutated during execution.Stateless Session Management
Because state is no longer shared on the instance, a single
ClaudeCodeIntegration is safe to call concurrently from multiple tasks / threads.CLI: Manager Delegation (Default)
PraisonAI CLI offers two execution modes for external agents, providing flexibility between automated reasoning and direct execution.
Usage Examples:
Configuration Options
Registry
TheExternalAgentRegistry manages all available CLI integrations with a thread-safe registry pattern with lazy module-level default. You can construct your own ExternalAgentRegistry() for test isolation or multi-tenant runs:
As of PraisonAI PR #1849,
ExternalAgentRegistry.create() raises ValueError for unknown integrations (parent registry contract). Use try_create() for the previous “return None on failure” behaviour. The module-level helper create_integration(name, **kwargs) already calls try_create() under the hood and still returns None on failure — no migration needed for code that uses it.get_registry(), register_integration(), create_integration(), and get_available_integrations() are still exported and still work — they now delegate to get_default_registry(). Backward-compat code does not need to change.
Invalidating the availability cache
BaseCLIIntegration caches the result of shutil.which(cli_command) for each CLI. If a CLI is installed/uninstalled mid-run (e.g. in tests, or after a setup step), invalidate the cache:
Logging for embedders
The registry logs through the named loggerpraisonai.integrations.registry — it never configures the root logger, so importing or calling the registry will not install a handler on an embedding application’s root logger.
Two warnings route through this logger:
Silence or redirect them:
Registering the integration as a tool
Both methods wrap the same underlyingintegration.execute(query) coroutine — pick the one that matches how your agent is invoked.
<cli> is the integration’s cli_command: claude_tool/claude_atool, gemini_tool/gemini_atool, codex_tool/codex_atool, and cursor-agent_tool/cursor-agent_atool.
Register a custom external agent
Register your own external agent once and it becomes reachable from every surface —--external-agent, the praisonai_code CLI handler, and the UI toggles — with no PraisonAI code changes.
As of PraisonAI PR #4156,
--external-agent choices, the CLI handler’s INTEGRATIONS, and the UI’s EXTERNAL_AGENTS all read the same ExternalAgentRegistry. A registered agent shows up in all three automatically.1
Ship as a pip plugin (recommended)
Publish your integration class to the After
praisonai.external_agents entry-point group:pip install your-plugin, aider appears in praisonai --external-agent choices, in the praisonai_code CLI handler, and as a UI toggle — no PraisonAI code changes.Built-ins always win on name collisions. Since PraisonAI PR #4159 — and now enforced in the base
PluginRegistry for every registry via PR #4176 — a plugin whose entry-point name matches a shipped built-in (claude, gemini, codex, cursor) is skipped and the shipped integration keeps the name (a DEBUG line notes the collision). Matching is case-insensitive. Pick a distinct name (e.g. aider, acme-claude) for your plugin, or use runtime register(...) (next step) for a deliberate override. See Plugin Precedence.2
Or register at runtime
register() raises ValueError if the class does not inherit BaseCLIIntegration.Presentation metadata
Two optional class attributes drive help text, UI labels, and install hints on every surface:
Built-ins declare them too:
Single source of truth
Two registry helpers back every surface that lists external agents:claude, gemini, codex, or cursor.
A listed name is always a usable name. list_external_agents() is derived from external_agent_catalog(), so a plugin that fails to import disappears from both surfaces at once. A broken plugin is never advertised as a --external-agent choice and then rejected by the handler — if the name shows up in --help, in ExternalAgentsHandler().list_integrations(), or as a UI toggle, it loads.
Which entry-point group?
Two entry-point groups exist and are easy to confuse — pick by outcome:
Use
praisonai.external_agents for a selectable --external-agent short name and a UI toggle. Use praisonai.integrations only to make a class importable from praisonai.integrations.
Both entry-point groups apply the same precedence: built-ins always win on case-insensitive name collisions; plugins can only add new names. As of PR #4176 this guard is enforced once in the base
PluginRegistry, so it holds identically for every registry — see Plugin Precedence.Prerequisites
Theclaude CLI must be available on PATH or the Claude Code SDK must be installed if use_sdk=True. The integration performs a cached, thread-safe shutil.which(...) check via is_available().
Decision Flow
Best Practices
Always pass continue_session=True explicitly
Always pass continue_session=True explicitly
Don’t rely on instance state for session continuation. Always be explicit about when you want to continue a previous session.
Set output_format per call or once in constructor
Set output_format per call or once in constructor
Do not mutate the
integration.output_format attribute between calls. Use the per-call parameter or set it once during initialization.Remove reset_session() calls
Remove reset_session() calls
The
reset_session() method is now a no-op. Remove these calls from your code as they serve no purpose.Safe for concurrent use
Safe for concurrent use
A single
ClaudeCodeIntegration instance can be shared across concurrent tasks safely since no instance state is mutated during calls.Troubleshooting
My plugin doesn't appear in --external-agent
My plugin doesn't appear in --external-agent
The plugin failed to import, so it was dropped from the catalog and never offered as a choice. The reason is logged as Fix the import error the log reports (a missing dependency, a bad entry-point path, a syntax error in the plugin), then re-run — the name reappears once the class loads.
Skipping external agent '<name>': failed to load (<error>) on logger praisonai.integrations.registry.See the exact error:--external-agent says the wrapper is too old
--external-agent says the wrapper is too old
You see:This means your
praisonai-code is newer than your praisonai wrapper — a mixed-version install. Upgrade the wrapper:Using from PraisonAI UI
Related
Persistence & Concurrency
Learn about thread-safe persistence features
Agent Tools
Using integrations as agent tools

