Concepts¶
lionagi has two connected surfaces:
- Run work with one agent, a parallel fan-out, a dependency-aware flow, or a reusable engine.
- Operate work with durable run state, monitoring, control messages, schedules, and Studio.
The CLI is the shortest path to both surfaces. The Python API exposes the same core building blocks when an application needs direct control.
Choose the shape of the work¶
| Shape | CLI | Python | Use it for |
|---|---|---|---|
| One stateful worker | li agent | Branch | A focused task or conversation |
| Independent workers | li o fanout | Separate branches | Research or review that can run in parallel |
| Dependent or reactive work | li o flow / li play | Builder + Session.flow() | Multi-step work with explicit dependencies |
| A domain pipeline | li engine run | lionagi.engines | Planning, research, review, hypothesis, or coding workflows |
Start with the smallest shape that fits. A single agent is easier to inspect and resume than a graph; use a flow when dependencies or distinct worker contexts are part of the problem.
Branch¶
A Branch is one stateful unit of model work. It owns message history, registered tools, model configuration, logs, and optional memory.
import lionagi as li
branch = li.Branch(
chat_model=li.iModel(model="openai/gpt-4.1-mini"),
system="You are a concise technical writer.",
)
answer = await branch.communicate("Explain dependency-aware execution.")
Choose the operation deliberately:
| Method | Records the turn | Executes tools | Best for |
|---|---|---|---|
communicate() | Yes | No | A normal stateful model turn |
operate() | Yes | Optional | Structured output and tool-enabled work |
chat() | No | No | A low-level call when you manage history yourself |
chat_and_record() | Yes | No | chat() semantics with a recorded turn |
ReAct() | Yes | Yes | Several think-act-observe rounds |
run() | Yes, as streamed messages | Provider-managed | Streaming a CLI-backed model |
chat() returns the response value, usually a string, by default. Pass return_ins_res_message=True only when you need the generated Instruction and AssistantResponse objects.
Tools and structured output¶
operate() is the general Python entry point for a recorded turn with structured output, tool schemas, and tool invocation.
from pydantic import BaseModel
class Finding(BaseModel):
summary: str
severity: str
result = await branch.operate(
instruction="Inspect the supplied change and report its highest-risk issue.",
context={"diff": diff},
response_format=Finding,
)
Registering a tool does not make chat() or communicate() execute it. Enable the action path explicitly:
branch.register_tools([search_docs])
result = await branch.operate(
instruction="Find the current retry policy and summarize it.",
actions=True,
tools=["search_docs"],
)
For a task that may require multiple tool rounds, use ReAct() instead of assuming one operate() call will complete an open-ended workflow.
→ operate() and the Middle protocol
Session and flow¶
A Session owns one or more branches, their in-process exchange, shared memory, and the graph execution kernel. A default branch is created automatically.
import lionagi as li
session = li.Session()
researcher = session.new_branch(name="researcher", chat_model="openai/gpt-4.1-mini")
writer = session.new_branch(name="writer", chat_model="anthropic/claude-sonnet-4")
session.send(researcher.id, writer.id, "Research is ready")
await session.sync()
messages = session.receive(writer.id)
For Python DAGs, Builder creates operations and Session.flow() executes them:
builder = li.Builder()
research = builder.add_operation(
"communicate",
instruction="Research the trade-offs of the proposed design.",
)
summary = builder.add_operation(
"communicate",
depends_on=[research],
instruction="Turn the research into an executive summary.",
)
result = await session.flow(builder.get_graph())
print(result["operation_results"][summary])
Builder is incremental: after the first node, omitting depends_on attaches the new operation after the builder's current head or heads. It is chaining shorthand, not a way to create a new independent root. Use explicit dependencies or expand_from_result(..., strategy=ExpansionStrategy.CONCURRENT) for parallel work.
The CLI's li o flow uses the same execution kernel but has a model plan the graph from a task. li play NAME runs a reusable, parameterized flow specification.
→ Session reference · Python DAG API
Durable runs and control¶
Task-producing CLI commands persist run/session state under ~/.lionagi/ and in StateDB. This is what makes background execution, monitoring, and resume possible. User-facing artifacts go to --save when supplied; durable state is not the same as the artifact directory.
li agent claude "Review the authentication module"
li agent -r BRANCH_ID "Now propose the smallest fix"
li o flow claude "Audit, fix, and verify the package" --save ./out --background
li monitor --watch
li wait RUN_ID
li o ctl status RUN_ID
li o flow --resume ID restarts a checkpointed flow after its process ended. li o ctl resume ID is different: it unpauses a flow that is still running.
Python users control serialization themselves with to_dict(), from_dict(), to_df(), and log persistence. The CLI/Studio run lifecycle is not automatically created merely by constructing a Branch.
Providers and endpoints¶
A provider selects a backend family. An endpoint selects one capability from that family. iModel resolves the pair through the endpoint registry.
api_model = li.iModel(provider="openai", endpoint="chat", model="gpt-4.1-mini")
cli_model = li.iModel(provider="claude_code", model="sonnet")
Keep API and CLI providers distinct:
openai,anthropic, andgeminicall hosted APIs and use API keys.codex,claude_code,gemini_code, andpilaunch installed command-line agents and use those tools' authentication.- Model strings with a slash, such as
anthropic/claude-sonnet-4, infer the provider from the prefix.
Provider packages are organized by implementation owner, not always by public alias: Gemini endpoints live in lionagi/providers/google/, while users select provider="gemini" or provider="gemini_code".
→ iModel reference · Provider matrix
Reusable definitions¶
The CLI supports four different kinds of reusable material:
| Definition | Typical location | Purpose |
|---|---|---|
| Agent profile | .lionagi/agents/<name>/<name>.md | Model defaults and system prompt |
| Skill | .lionagi/skills/<name>/SKILL.md | Static instructions loaded on demand |
| Playbook | .lionagi/playbooks/<name>.playbook.yaml | Parameterized flow invocation |
| Plugin | .lionagi/plugins/<name>/plugin.yaml | A trusted bundle of profiles, playbooks, providers, or other extensions |
Project-local definitions take precedence where supported. Plugins are inert until their declared contents are explicitly trusted and enabled.
li agent -a reviewer "Review this patch"
li skill show commit
li play audit --mode security "the auth package"
li plugin info my-plugin
AgentSpec and permissions¶
AgentSpec is the Python equivalent of a repeatable agent configuration. It combines a role/profile with model, tools, permissions, policy pack, context management, and tool hooks, then create_agent() wires a ready-to-use Branch.
from lionagi.agent import AgentSpec, create_agent
from lionagi.agent.hooks import log_tool_call
spec = AgentSpec.coding(
model="openai/gpt-4.1",
cwd="/path/to/project",
secure=True,
)
spec.post("*", log_tool_call)
branch = await create_agent(spec)
result = await branch.operate(
instruction="Inspect the import cycle and make the smallest safe edit.",
actions=True,
)
The secure coding preset installs destructive-command and workspace-containment guards in the security_pre hook phase. Permissions and guards are complementary: permissions decide which calls are allowed, while guards enforce non-negotiable safety checks and re-check rewritten arguments.
→ AgentSpec, permissions, and hooks
Team¶
A CLI team is a durable, named inbox shared across separate processes. It is useful when coordination outlives one flow invocation.
li team create docs-team -m researcher,writer,reviewer
li team send "Draft ready" --team docs-team --to writer --from researcher
li team receive --team docs-team --as writer
Within one Python process, use the Session exchange. Within a single dependency graph, prefer graph edges unless workers genuinely need asynchronous messages.
Sandbox¶
SandboxSession creates an isolated git worktree for reversible code changes.
from lionagi.tools.sandbox import create_sandbox, sandbox_diff, sandbox_discard
sandbox = await create_sandbox(repo_root="/path/to/project")
agent = await create_agent(AgentSpec.coding(cwd=sandbox.worktree_path))
await agent.operate(instruction="Refactor the auth module.", actions=True)
print((await sandbox_diff(sandbox))["stat"])
await sandbox_discard(sandbox) # or commit and merge after review
Use a sandbox for speculative writes that need an explicit review boundary. A read-only analysis does not need the worktree overhead.
Next: Choose a surface, CLI reference, or Python API reference.