CLI Agent Providers¶
lionagi integrates with CLI-based AI agents -- Claude Code, Gemini CLI, and OpenAI Codex -- as iModel providers. This enables agent-to-agent orchestration: your outer agent uses lionagi to spawn and coordinate inner agents.
Supported CLI Providers¶
| Provider String | CLI Tool | Endpoint Class |
|---|---|---|
claude_code | Claude Code | ClaudeCodeCLIEndpoint |
gemini_code | Gemini CLI | GeminiCLIEndpoint |
codex | OpenAI Codex | CodexCLIEndpoint |
All CLI providers use endpoint="query_cli" and require no real API key (api_key is ignored; the CLI tool handles auth).
Quick Setup¶
Claude Code¶
from lionagi import Branch, iModel
cc_model = iModel(
provider="claude_code",
model="sonnet", # model hint for Claude Code
cwd="/path/to/project", # working directory for the agent
permission_mode="bypassPermissions", # or "default"
allowed_tools=["Read", "Grep", "Glob"],
verbose_output=True,
)
branch = Branch(chat_model=cc_model, name="claude_agent")
response = await branch.communicate("Analyze the codebase structure")
Gemini CLI¶
gemini_model = iModel(
provider="gemini_code",
model="gemini-2.5-pro",
cwd="/path/to/project",
)
branch = Branch(chat_model=gemini_model, name="gemini_agent")
response = await branch.communicate("Review this module for issues")
Codex¶
codex_model = iModel(
provider="codex",
model="codex-mini",
cwd="/path/to/project",
)
branch = Branch(chat_model=codex_model, name="codex_agent")
response = await branch.communicate("Refactor the authentication module")
iModel Configuration Reference¶
Common Parameters (all CLI providers)¶
iModel(
provider="claude_code", # Required: provider string
model="sonnet", # Model selection hint
cwd="/path/to/project", # Working directory for the agent
verbose_output=True, # Show agent output during execution
cli_include_summary=False, # Include cost/usage summary
auto_finish=False, # Auto-complete if agent doesn't finish
)
Claude Code-Specific Parameters¶
iModel(
provider="claude_code",
model="sonnet",
permission_mode="bypassPermissions", # "default" | "acceptEdits" | "bypassPermissions"
allowed_tools=["Read", "Grep", "Glob", "Bash"],
disallowed_tools=["Write"], # Block specific tools
cli_display_theme="dark", # "dark" | "light"
max_turns=10, # Max conversation turns
max_thinking_tokens=16000, # Thinking budget
continue_conversation=True, # Continue previous session
system_prompt="You are a security auditor.",
append_system_prompt="Focus on OWASP Top 10.", # Appended to existing system
mcp_tools=["browser", "filesystem"], # MCP tool names to enable
mcp_config="/path/to/.mcp.json", # MCP config file
add_dir="/shared/reference", # Extra read-only directory
)
Gemini CLI-Specific Parameters¶
iModel(
provider="gemini_code",
model="gemini-2.5-pro",
sandbox=True, # Safety sandboxing (default True)
approval_mode="auto_edit", # "suggest" | "auto_edit" | "full_auto"
# yolo=True, # Auto-approve all -- emits safety warning
debug=False,
include_directories=["/extra/src"], # Additional directories to include
system_prompt="You are a code analyst.",
)
Codex CLI-Specific Parameters¶
iModel(
provider="codex",
model="gpt-5.3-codex",
full_auto=True, # Auto-approve with workspace-write sandbox
sandbox="workspace-write", # "read-only" | "workspace-write" | "danger-full-access"
# bypass_approvals=True, # Skip ALL approvals -- use with caution
skip_git_repo_check=False,
output_schema="/path/to/schema.json", # JSON Schema for structured output
include_plan_tool=True, # Enable planning tool
images=["screenshot.png"], # Attach images
config_overrides={"key": "value"}, # Custom config -c flags
system_prompt="You are a test engineer.",
)
Context Management¶
CLI providers have two layers of context: the CLI agent's own session (managed via --resume) and the Branch's MessageManager (which records all exchanges in a Pile). Both are useful.
Within a session, the CLI agent manages its own context -- lionagi automatically stores the session_id from each response and passes --resume on subsequent calls. You don't need to manually manage what the agent sees per-call.
Across sessions, Branch's message history and progression= become essential. CLI sessions can grow too long (context window limits, token costs, accumulated tool output noise). When that happens, start a fresh CLI instance and use progression= to select which prior context to inject as the initial prompt:
# Session grew too long -- rotate to a fresh CLI instance
branch.chat_model = iModel(provider="claude_code", model="sonnet")
# Use progression to carry forward only the relevant context
recent = list(branch.msgs.progression)[-30:]
await branch.communicate(
"Continue the analysis from where we left off.",
progression=recent,
)
This pattern is especially important for long-running branches where you might strip provider-side action input/output from appearing in assistant content, then feed a curated history into a fresh session.
| Aspect | API Providers | CLI Providers |
|---|---|---|
| Per-call context | progression= controls every call | CLI agent manages via session resume |
| Session rotation | N/A (stateless) | Start fresh iModel, use progression= for prior context |
| Long-running | Slide the progression window | Rotate sessions + curate progression |
| Branch records | Source of truth | Durable log + context source for fresh sessions |
Session Lifecycle¶
CLI session state is managed automatically via session_id:
- First call creates a new session. The CLI returns a
session_idin thesystemevent. - lionagi stores the
session_idon the endpoint. - Subsequent calls on the same iModel pass
--resumewith that ID. - If the resumed session gets a new ID (the CLI may reassign), lionagi updates to the new ID automatically.
Branch.clone()creates a fresh copy with no shared session state.iModel.copy(share_session=True)carries over the session ID.
# Fresh copy, independent session
new_model = cc_model.copy()
# Copy that resumes the same CLI session
resumed_model = cc_model.copy(share_session=True)
Multi-Agent Orchestration¶
Workspace Isolation¶
Give each agent its own working directory to prevent file conflicts:
def create_agent(role: str, subdir: str) -> iModel:
return iModel(
provider="claude_code",
model="sonnet",
cwd=f"/project/.agents/{subdir}",
permission_mode="bypassPermissions",
allowed_tools=["Read", "Grep", "Glob", "Bash"],
)
orchestrator = create_agent("orchestrator", "orc")
researcher_1 = create_agent("researcher", "res1")
researcher_2 = create_agent("researcher", "res2")
Fan-Out Pattern¶
from lionagi import Branch, Session, Builder
from lionagi.operations.fields import Instruct
session = Session()
builder = Builder("research")
# Orchestrator generates sub-tasks
orc_branch = Branch(chat_model=orchestrator, name="orchestrator")
session.include_branches([orc_branch])
root = builder.add_operation(
"operate",
branch=orc_branch,
instruction="Break this task into 3 research sub-tasks",
response_format=TaskList, # your Pydantic model
)
result = await session.flow(builder.get_graph())
# Fan out to researcher agents
for i, task in enumerate(result["operation_results"][root].tasks):
agent = create_agent("researcher", f"res_{i}")
res_branch = Branch(chat_model=agent, name=f"researcher_{i}")
session.include_branches([res_branch])
builder.add_operation(
"communicate",
branch=res_branch,
instruction=task.description,
depends_on=[root],
)
# Execute all research in parallel
final = await session.flow(builder.get_graph(), max_concurrent=3)
Cost Tracking¶
CLI providers include cost data in model responses:
from lionagi.protocols.messages import AssistantResponse
# After a communicate/operate call, inspect the last message
last_msg = branch.messages[-1]
if isinstance(last_msg, AssistantResponse):
cost = last_msg.model_response.get("total_cost_usd", 0)
result_text = last_msg.model_response.get("result", "")
summary = last_msg.model_response.get("summary", "")
Event Handlers (Claude Code only)¶
Claude Code supports streaming event handlers for fine-grained control:
cc_model = iModel(provider="claude_code", model="sonnet")
# Access the endpoint to set handlers
cc_model.endpoint.update_handlers(
on_text=lambda chunk: print(f"Text: {chunk.text}"),
on_tool_use=lambda chunk: print(f"Tool: {chunk}"),
on_thinking=lambda chunk: print(f"Thinking: {chunk.text}"),
on_final=lambda session: print(f"Done: {session.result}"),
)
Available handler keys by provider:
| Provider | Handlers |
|---|---|
claude_code | on_thinking, on_text, on_tool_use, on_tool_result, on_system, on_final |
gemini_code | on_text, on_tool_use, on_tool_result, on_final |
codex | on_text, on_tool_use, on_tool_result, on_final |
Async Context Manager¶
Both Branch and iModel support async context managers:
async with iModel(provider="claude_code", model="sonnet") as model:
async with Branch(chat_model=model) as branch:
result = await branch.communicate("Analyze this code")
# Branch dumps logs on exit
# iModel executor stops on exit
Error Handling¶
try:
result = await branch.communicate("Complex analysis task")
except ValueError as e:
if "Failed to invoke API call" in str(e):
# CLI tool likely timed out or crashed
# Default timeout is 18000 seconds (5 hours)
pass
Key Differences: CLI vs API Providers¶
| Aspect | API Providers | CLI Providers |
|---|---|---|
| Auth | API key required | CLI tool handles auth |
| Context owner | Branch (Progression controls every call) | CLI agent within session; Branch across sessions |
| Latency | Low (HTTP) | Higher (subprocess) |
| Session state | Stateless | Persistent session_id (auto-updated) |
| Concurrency | High (100 queue capacity) | Low (default 3) |
| Clone behavior | Shared endpoint | Fresh endpoint copy |
| Timeout | Provider default | 18000s (5 hours) |
| Tool calling | Via function schemas | Via CLI tool's built-in tools |
progression= | Controls LLM context window per call | Used when rotating to a fresh session (context injection) |