Context
PraisonAI PR MervinPraison/PraisonAI#1521 (merged 2026-04-23, Phase 1 of issue #1519) introduces a CLI Backend Protocol that lets any Agent delegate an entire conversation turn to an external CLI tool (Claude Code, and future codex-cli / gemini-cli) instead of calling an LLM directly.
This is a new programmatic feature exposed through the Python SDK and is distinct from the existing praisonai --external-agent claude CLI flag documented at docs/cli/claude-cli.mdx. A new documentation page must be created.
Decision: Create New Content (not an update)
I searched docs/ in PraisonAIDocs — there is no existing documentation for:
- The
cli_backend= parameter on Agent
CliBackendProtocol, CliBackendConfig, CliSessionBinding, CliBackendResult, CliBackendDelta
- The CLI backend registry (
register_cli_backend, resolve_cli_backend, list_cli_backends)
praisonai.Agent wrapper vs praisonaiagents.Agent core behavior for CLI backends
docs/cli/claude-cli.mdx is unrelated (covers the --external-agent CLI flag, a manager-delegation pattern, not Agent-level backend delegation).
What to Document
Target Location
Per AGENTS.md folder rules:
- DO NOT create in
docs/concepts/ (human-approved only)
- CREATE in
docs/features/cli-backend.mdx
- Add to
docs.json under the Features group only
Suggested Frontmatter
---
title: "CLI Backend"
sidebarTitle: "CLI Backend"
description: "Delegate Agent turns to external CLI tools like Claude Code"
icon: "terminal"
---
Feature Summary (user-facing)
CLI Backend lets you plug an external CLI coding assistant (like Claude Code) into a PraisonAI Agent so that every turn runs through that CLI instead of the built-in LLM. The agent keeps its name, role, instructions, tools-wrapper behaviour, and session continuity — only the "brain" swaps out.
Simplest example (the one to put at the top of the page, agent-centric per AGENTS.md):
from praisonai import Agent
agent = Agent(
name="Coder",
instructions="You are a senior Python engineer",
cli_backend="claude-code"
)
agent.start("Refactor auth.py to use dependency injection")
Why users care:
- Use Claude Code's file-editing / tool-use capabilities inside an Agent workflow
- Keep PraisonAI's Task/Process/memory/knowledge orchestration
- Swap backend per agent (some agents local-LLM, one agent Claude Code)
- Session continuity:
agent_id is used as the CLI session id, so multi-turn chats resume
SDK Ground Truth (verified against source)
1. New Agent(cli_backend=...) parameter
File: praisonai-package/src/praisonai-agents/praisonaiagents/agent/agent.py:555 and docstring at :653-659.
| Accepted value |
Behaviour |
str e.g. "claude-code" |
Resolved via wrapper registry. Only works when importing from praisonai, not praisonaiagents |
CliBackendProtocol instance |
Used directly |
callable returning a protocol instance |
Invoked once at Agent init |
None (default) |
Standard LLM execution |
Two-import behaviour (critical for users — must be explained):
# Works: string resolution happens in the wrapper
from praisonai import Agent
agent = Agent(cli_backend="claude-code")
# Also works: resolve the backend yourself
from praisonaiagents import Agent
from praisonai.cli_backends import resolve_cli_backend
agent = Agent(cli_backend=resolve_cli_backend("claude-code"))
Using a string with the core praisonaiagents.Agent raises TypeError with a message pointing users at the two fixes above (source: agent.py:4729-4734).
2. New public exports in praisonaiagents
From praisonai-agents/praisonaiagents/__init__.py:205-211:
CliBackendProtocol — runtime-checkable typing.Protocol
CliBackendConfig — dataclass (declarative config, mirrors OpenClaw's shape)
CliSessionBinding — session state container
CliBackendResult — result of .execute()
CliBackendDelta — streamed delta from .stream()
3. New public exports in praisonai
From praisonai/praisonai/cli_backends/__init__.py:
register_cli_backend(backend_id, factory) — register a new backend
resolve_cli_backend(backend_id, overrides=None) — resolve to instance, optional config overrides
list_cli_backends() — list registered IDs
ClaudeCodeBackend — built-in Claude Code implementation
Built-ins auto-register on import: currently only "claude-code".
4. CliBackendConfig fields (full table — copy verbatim into doc)
From praisonaiagents/cli_backend/protocols.py:13-59.
| Field |
Type |
Default |
Purpose |
command |
str |
(required) |
CLI executable, e.g. "claude" |
args |
List[str] |
[] |
Base args |
resume_args |
Optional[List[str]] |
None |
Args for resume; supports "{session_id}" placeholder |
session_arg |
Optional[str] |
None |
Flag for session id, e.g. "--session-id" |
session_mode |
str |
"none" |
"always" | "existing" | "none" |
session_id_fields |
List[str] |
[] |
Fields that bind a session |
output |
str |
"text" |
"text" | "json" | "jsonl" |
input |
str |
"arg" |
"arg" | "stdin" |
max_prompt_arg_chars |
Optional[int] |
None |
Switch to stdin above this length |
model_arg |
Optional[str] |
None |
e.g. "--model" |
model_aliases |
Dict[str, str] |
{} |
Short → full id (e.g. "opus" → "claude-opus-4-5") |
system_prompt_arg |
Optional[str] |
None |
e.g. "--append-system-prompt" |
system_prompt_when |
str |
"always" |
"first" | "always" | "never" |
system_prompt_mode |
str |
"append" |
"append" | "replace" |
image_arg |
Optional[str] |
None |
e.g. "--image" |
image_mode |
str |
"repeat" |
"repeat" | "list" |
clear_env |
List[str] |
[] |
Env vars to strip before launching CLI |
env |
Dict[str, str] |
{} |
Env vars to set |
live_session |
Optional[str] |
None |
e.g. "claude-stdio" |
bundle_mcp |
bool |
False |
Pass MCP servers through to the CLI |
bundle_mcp_mode |
Optional[str] |
None |
e.g. "claude-config-file" |
serialize |
bool |
False |
Queue calls to avoid concurrent conflicts |
no_output_timeout_ms |
Optional[int] |
None |
Kill if no bytes for this long |
timeout_ms |
int |
300_000 |
Overall timeout (5 min default) |
5. Built-in ClaudeCodeBackend defaults (from praisonai/cli_backends/claude.py:24-73)
command="claude"
args=["-p", "--output-format", "stream-json", "--include-partial-messages", "--verbose", "--setting-sources", "user", "--permission-mode", "bypassPermissions"]
output="jsonl", input="stdin", live_session="claude-stdio"
session_arg="--session-id", session_mode="always"
model_aliases={"opus":"claude-opus-4-5","sonnet":"claude-sonnet-4-5","haiku":"claude-haiku-3-5"}
system_prompt_arg="--append-system-prompt", system_prompt_when="first"
image_arg="--image"
clear_env=[ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, ANTHROPIC_OAUTH_TOKEN, CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, CLAUDE_CONFIG_DIR, CLAUDE_CODE_OAUTH_TOKEN, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_RESOURCE_ATTRIBUTES, GOOGLE_APPLICATION_CREDENTIALS, AWS_PROFILE, AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN] — document this: env is sanitised before spawning Claude, so ambient credentials won't leak
bundle_mcp=True, bundle_mcp_mode="claude-config-file", serialize=True, timeout_ms=300_000
6. How the Agent wires it
From agent.py:1926-1928 and _chat_via_cli_backend at :4738-4807:
- Session id =
agent._session_id if set, else f"agent-{agent.agent_id}"
- System prompt built from
system_prompt > backstory + role + goal
- Images extracted from
attachments (filtered to .png/.jpg/.jpeg/.gif/.bmp/.webp)
- Calls
backend.execute(prompt=..., session=..., images=..., system_prompt=...)
- Errors raise
RuntimeError with agent name + session id in message
Required Page Structure (per AGENTS.md)
Follow the exact page template in AGENTS.md §2. Sections, in order:
- Hero one-liner — "CLI Backend delegates Agent turns to an external CLI tool like Claude Code."
- Hero Mermaid diagram —
graph LR showing: Agent → CLI Backend → external CLI (claude) → Result → Agent. Use the standard color scheme (#8B0000 agent, #189AB4 backend, #10B981 result).
- Quick Start (
<Steps>):
- Step 1: minimal string usage with
from praisonai import Agent
- Step 2: with a custom
CliBackendConfig (e.g. override timeout_ms and model_aliases)
- How It Works — sequence diagram (User → Agent → CliBackend → claude CLI subprocess → Agent → User), plus short table of the 4 steps (build command, spawn subprocess with sanitised env, parse jsonl output, return
CliBackendResult).
- Which option when? diagram —
graph TB decision tree for choosing between cli_backend="claude-code" (simple), cli_backend=resolve_cli_backend("claude-code", overrides={...}) (tuned), cli_backend=MyBackend() (custom). Required per AGENTS.md §6.1 for multi-option pages.
- Configuration Options — the full
CliBackendConfig table from §4 above, plus a ClaudeCodeBackend defaults block from §5.
- Common Patterns (3 patterns, each ~10 lines):
- Pattern A — Swap backend per agent in a team: one
Agent uses llm="gpt-4o", another uses cli_backend="claude-code", wired together with PraisonAIAgents / Task.
- Pattern B — Tune timeout and model aliases: use
resolve_cli_backend("claude-code", overrides={"timeout_ms": 900_000}).
- Pattern C — Register a custom CLI backend: define a class implementing
.config, async execute, async stream; register_cli_backend("my-cli", lambda: MyBackend()); use cli_backend="my-cli".
- User interaction flow (required per AGENTS.md §1.1 item 11) — short paragraph + mini sequence diagram showing what the user sees when they run
agent.start("...") with cli_backend="claude-code" (subprocess spawn, streaming text, final result).
- Best Practices (
<AccordionGroup>, 4 items):
- Prefer
from praisonai import Agent when passing a string backend id
- Bump
timeout_ms for long coding tasks (default 5 min)
- Remember Claude CLI must be installed and authenticated (link to
docs/cli/claude-cli.mdx install section)
clear_env strips ambient ANTHROPIC_API_KEY etc. — expect Claude to use its own auth profile
- Related (
<CardGroup cols={2}>):
docs/cli/claude-cli.mdx (CLI flag version)
docs/concepts/agents.mdx
Cross-references to Update
docs/cli/claude-cli.mdx — add a callout at the top: "For programmatic use inside a Python Agent, see docs/features/cli-backend.mdx."
docs/concepts/agents.mdx — DO NOT EDIT (human-approved folder). Instead, note in the new page that it extends the Agent class.
docs.json — add docs/features/cli-backend under the Features group, NOT under Concepts.
docs/features/DOCS_PARITY.md — flip CLI backend / external CLI integration from ❌/⚠️ to ✅ if listed.
Writing Style Reminders (from AGENTS.md)
- Non-developer friendly; first code block must be ≤ 5 lines and make the reader think "is it really this easy?"
- Progressive disclosure: simplest form first, config later, custom backend last
- Mermaid diagram for every distinct concept on the page
- Active voice, no "In this section we will…"
- All code examples must run verbatim (imports included, no
your-api-key-here placeholders)
Acceptance Criteria
Source Files to Re-read Before Writing
praisonai-package/src/praisonai-agents/praisonaiagents/cli_backend/protocols.py — full config/protocol surface
praisonai-package/src/praisonai-agents/praisonaiagents/agent/agent.py lines 552–660, 1926–1935, 4694–4810 — how the Agent uses the backend
praisonai-package/src/praisonai/praisonai/cli_backends/claude.py — default ClaudeCodeBackend config
praisonai-package/src/praisonai/praisonai/cli_backends/registry.py — registry API
praisonai-package/src/praisonai/praisonai/agent.py — wrapper Agent, string resolution
Always re-read these before making any documentation change (SDK-first cycle, AGENTS.md §1.2).
Context
PraisonAI PR MervinPraison/PraisonAI#1521 (merged 2026-04-23, Phase 1 of issue #1519) introduces a CLI Backend Protocol that lets any
Agentdelegate an entire conversation turn to an external CLI tool (Claude Code, and futurecodex-cli/gemini-cli) instead of calling an LLM directly.This is a new programmatic feature exposed through the Python SDK and is distinct from the existing
praisonai --external-agent claudeCLI flag documented atdocs/cli/claude-cli.mdx. A new documentation page must be created.Decision: Create New Content (not an update)
I searched
docs/in PraisonAIDocs — there is no existing documentation for:cli_backend=parameter onAgentCliBackendProtocol,CliBackendConfig,CliSessionBinding,CliBackendResult,CliBackendDeltaregister_cli_backend,resolve_cli_backend,list_cli_backends)praisonai.Agentwrapper vspraisonaiagents.Agentcore behavior for CLI backendsdocs/cli/claude-cli.mdxis unrelated (covers the--external-agentCLI flag, a manager-delegation pattern, not Agent-level backend delegation).What to Document
Target Location
Per
AGENTS.mdfolder rules:docs/concepts/(human-approved only)docs/features/cli-backend.mdxdocs.jsonunder the Features group onlySuggested Frontmatter
Feature Summary (user-facing)
CLI Backend lets you plug an external CLI coding assistant (like Claude Code) into a PraisonAI
Agentso that every turn runs through that CLI instead of the built-in LLM. The agent keeps its name, role, instructions, tools-wrapper behaviour, and session continuity — only the "brain" swaps out.Simplest example (the one to put at the top of the page, agent-centric per AGENTS.md):
Why users care:
agent_idis used as the CLI session id, so multi-turn chats resumeSDK Ground Truth (verified against source)
1. New
Agent(cli_backend=...)parameterFile:
praisonai-package/src/praisonai-agents/praisonaiagents/agent/agent.py:555and docstring at:653-659.stre.g."claude-code"praisonai, notpraisonaiagentsCliBackendProtocolinstancecallablereturning a protocol instanceNone(default)Two-import behaviour (critical for users — must be explained):
Using a string with the core
praisonaiagents.AgentraisesTypeErrorwith a message pointing users at the two fixes above (source:agent.py:4729-4734).2. New public exports in
praisonaiagentsFrom
praisonai-agents/praisonaiagents/__init__.py:205-211:CliBackendProtocol— runtime-checkabletyping.ProtocolCliBackendConfig— dataclass (declarative config, mirrors OpenClaw's shape)CliSessionBinding— session state containerCliBackendResult— result of.execute()CliBackendDelta— streamed delta from.stream()3. New public exports in
praisonaiFrom
praisonai/praisonai/cli_backends/__init__.py:register_cli_backend(backend_id, factory)— register a new backendresolve_cli_backend(backend_id, overrides=None)— resolve to instance, optional config overrideslist_cli_backends()— list registered IDsClaudeCodeBackend— built-in Claude Code implementationBuilt-ins auto-register on import: currently only
"claude-code".4.
CliBackendConfigfields (full table — copy verbatim into doc)From
praisonaiagents/cli_backend/protocols.py:13-59.commandstr"claude"argsList[str][]resume_argsOptional[List[str]]None"{session_id}"placeholdersession_argOptional[str]None"--session-id"session_modestr"none""always" | "existing" | "none"session_id_fieldsList[str][]outputstr"text""text" | "json" | "jsonl"inputstr"arg""arg" | "stdin"max_prompt_arg_charsOptional[int]Nonemodel_argOptional[str]None"--model"model_aliasesDict[str, str]{}"opus"→"claude-opus-4-5")system_prompt_argOptional[str]None"--append-system-prompt"system_prompt_whenstr"always""first" | "always" | "never"system_prompt_modestr"append""append" | "replace"image_argOptional[str]None"--image"image_modestr"repeat""repeat" | "list"clear_envList[str][]envDict[str, str]{}live_sessionOptional[str]None"claude-stdio"bundle_mcpboolFalsebundle_mcp_modeOptional[str]None"claude-config-file"serializeboolFalseno_output_timeout_msOptional[int]Nonetimeout_msint300_0005. Built-in
ClaudeCodeBackenddefaults (frompraisonai/cli_backends/claude.py:24-73)command="claude"args=["-p", "--output-format", "stream-json", "--include-partial-messages", "--verbose", "--setting-sources", "user", "--permission-mode", "bypassPermissions"]output="jsonl",input="stdin",live_session="claude-stdio"session_arg="--session-id",session_mode="always"model_aliases={"opus":"claude-opus-4-5","sonnet":"claude-sonnet-4-5","haiku":"claude-haiku-3-5"}system_prompt_arg="--append-system-prompt",system_prompt_when="first"image_arg="--image"clear_env=[ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, ANTHROPIC_OAUTH_TOKEN, CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, CLAUDE_CONFIG_DIR, CLAUDE_CODE_OAUTH_TOKEN, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_RESOURCE_ATTRIBUTES, GOOGLE_APPLICATION_CREDENTIALS, AWS_PROFILE, AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN]— document this: env is sanitised before spawning Claude, so ambient credentials won't leakbundle_mcp=True,bundle_mcp_mode="claude-config-file",serialize=True,timeout_ms=300_0006. How the Agent wires it
From
agent.py:1926-1928and_chat_via_cli_backendat:4738-4807:agent._session_idif set, elsef"agent-{agent.agent_id}"system_prompt>backstory + role + goalattachments(filtered to.png/.jpg/.jpeg/.gif/.bmp/.webp)backend.execute(prompt=..., session=..., images=..., system_prompt=...)RuntimeErrorwith agent name + session id in messageRequired Page Structure (per AGENTS.md)
Follow the exact page template in AGENTS.md §2. Sections, in order:
graph LRshowing: Agent → CLI Backend → external CLI (claude) → Result → Agent. Use the standard color scheme (#8B0000 agent, #189AB4 backend, #10B981 result).<Steps>):from praisonai import AgentCliBackendConfig(e.g. overridetimeout_msandmodel_aliases)CliBackendResult).graph TBdecision tree for choosing betweencli_backend="claude-code"(simple),cli_backend=resolve_cli_backend("claude-code", overrides={...})(tuned),cli_backend=MyBackend()(custom). Required per AGENTS.md §6.1 for multi-option pages.CliBackendConfigtable from §4 above, plus aClaudeCodeBackenddefaults block from §5.Agentusesllm="gpt-4o", another usescli_backend="claude-code", wired together withPraisonAIAgents/Task.resolve_cli_backend("claude-code", overrides={"timeout_ms": 900_000})..config,async execute,async stream;register_cli_backend("my-cli", lambda: MyBackend()); usecli_backend="my-cli".agent.start("...")withcli_backend="claude-code"(subprocess spawn, streaming text, final result).<AccordionGroup>, 4 items):from praisonai import Agentwhen passing a string backend idtimeout_msfor long coding tasks (default 5 min)docs/cli/claude-cli.mdxinstall section)clear_envstrips ambientANTHROPIC_API_KEYetc. — expect Claude to use its own auth profile<CardGroup cols={2}>):docs/cli/claude-cli.mdx(CLI flag version)docs/concepts/agents.mdxCross-references to Update
docs/cli/claude-cli.mdx— add a callout at the top: "For programmatic use inside a Python Agent, seedocs/features/cli-backend.mdx."docs/concepts/agents.mdx— DO NOT EDIT (human-approved folder). Instead, note in the new page that it extends the Agent class.docs.json— adddocs/features/cli-backendunder the Features group, NOT under Concepts.docs/features/DOCS_PARITY.md— flip CLI backend / external CLI integration from ❌/Writing Style Reminders (from AGENTS.md)
your-api-key-hereplaceholders)Acceptance Criteria
docs/features/cli-backend.mdxexists and follows the AGENTS.md template exactlyfrom praisonai import Agentand is ≤ 6 linesCliBackendConfigfield table present (24 fields, matching source)ClaudeCodeBackenddefault config block present, including theclear_envlistdocs.jsonupdated — entry under Features, NOT Conceptsdocs/cli/claude-cli.mdxdocs/concepts/Source Files to Re-read Before Writing
praisonai-package/src/praisonai-agents/praisonaiagents/cli_backend/protocols.py— full config/protocol surfacepraisonai-package/src/praisonai-agents/praisonaiagents/agent/agent.pylines 552–660, 1926–1935, 4694–4810 — how the Agent uses the backendpraisonai-package/src/praisonai/praisonai/cli_backends/claude.py— default ClaudeCodeBackend configpraisonai-package/src/praisonai/praisonai/cli_backends/registry.py— registry APIpraisonai-package/src/praisonai/praisonai/agent.py— wrapper Agent, string resolutionAlways re-read these before making any documentation change (SDK-first cycle, AGENTS.md §1.2).