Skip to content

CLI Reference

The CLI has two jobs: run agent work and operate the durable lifecycle around it. Start with li agent; move to fan-out or flow only when the work needs more than one worker.

Command map

Run work

Command Purpose
li agent [MODEL] PROMPT Run or resume one worker
li o fanout [MODEL] PROMPT Decompose into independent workers, optionally synthesize
li o flow [MODEL] PROMPT Plan and execute a dependency-aware, reactive graph
li play NAME [ARGS] Run a reusable playbook (li o flow -p NAME)
li engine run KIND SPEC Run a built-in coding, hypothesis, planning, research, or review engine

Observe and control

Command Purpose
li monitor / li mon List or watch sessions, invocations, plays, shows, and runs
li wait ID... Block until any mix of durable entity IDs reaches a terminal state
li monitor run ID... Wait for scheduled runs and their chains; optionally keep watching
li agent status [ID] Read stable session/invocation status, optionally as JSON
li o ctl {status,pause,resume,msg} Inspect or steer a live flow by ID
li kill ID Terminate one running entity or sweep stale processes; play kills cannot reach their workers and exit non-zero saying so; show ids are not directly killable (details)

Reuse, coordination, and operation

Command Purpose
li team {create,list,show,send,receive} Durable team inboxes across processes
li casts [NAME] Inspect built-in roles or modes
li skill {NAME,list,show} Read installed static skill instructions
li plugin {list,info,trust,enable,disable} Inspect and activate trusted plugin bundles
li hooks {import,trust} Import Claude Code / Codex hook configs and trust the imported commands
li invoke {start,end,list} Group sessions under one higher-level invocation
li studio [start] Start the Studio backend and selected frontend mode
li schedule {list,get,limits,create,enable,disable,trigger,delete,runs} Manage schedules through the Studio API
li state {import,import-teams,ls,stats,checkpoint,vacuum,prune,doctor} Inspect and maintain StateDB
li dispatch {ls,show,ack,retry,purge} Operate the durable dispatch outbox
li stats runs Aggregate run reporting from StateDB
li mirror Mirror Claude Code transcripts into StateDB/Studio
li mcp Serve an MCP server that submits li runs as background jobs (details)
li doctor Check installation, dependencies, Studio reachability, and writable state

play, skill, and wait are compatibility-friendly top-level conveniences handled before the normal argparse registry, so they may not appear in the command list printed by li --help. They are supported surfaces and are documented here.

Reusable definitions can be project-local, user-global, or supplied by a trusted plugin:

Primitive Location Invocation
Agent profile .lionagi/agents/<name>/<name>.md li agent -a <name> / li o flow -a <name>
Skill (static ref) ~/.lionagi/skills/<name>/SKILL.md li skill <name>
Playbook (parametric flow) .lionagi/playbooks/, ~/.lionagi/playbooks/, or a trusted plugin li play <name>
Plugin bundle .lionagi/plugins/<name>/plugin.yaml li plugin info <name>

See the repository examples for minimal templates of each.


Shared run flags

Available on li agent, li o fanout, li o flow. Source: cli/_providers.py

Flag Default Notes
--yolo false Auto-approve provider tool calls
--bypass false Bypass Codex approvals and sandboxing; intended for already-isolated environments
--fast false Use Codex priority service tier when the account supports it
-v, --verbose false Stream real-time output; suppresses final print
--theme {light,dark} none Terminal theme
--effort LEVEL none Override effort; provider-specific limits are normalized or clamped. Gemini CLI folds effort into its resolved model tier; direct gemini API has no effort setting
--cwd DIR none Working directory for CLI endpoint
--timeout SECONDS none Hard wall-clock timeout; partial branches saved. Injects a [DEADLINE] preamble into the agent's first message so it can pace itself
--invocation ID none Group the session under an ID from li invoke start
--project NAME auto Override project detection from config/git metadata

Model spec: provider/model[-effort] — for example claude/opus-4-7-high or codex/gpt-5.4-xhigh. Current bare aliases include claudeclaude_code/sonnet, codexcodex/gpt-5.3-codex-spark, gemini-codegemini_code/gemini-3.5-flash, and pipi/gemini-2.5-flash. Use gemini, without -code, for the direct Google API provider rather than the Gemini CLI backend.


li agent

One-shot agent turn or resumed conversation.

li agent [model] prompt [flags]
Arg/Flag Default Notes
model Spec or alias. Omit with -r or -c.
prompt Message to send.
--prompt TEXT none Prompt flag alternative to positionals
--prompt-file PATH none Read the prompt from a file; - reads stdin
-a, --agent NAME none Profile by name. Resolves .lionagi/agents/<NAME>/<NAME>.md first, then legacy .lionagi/agents/<NAME>.md. Sets model/effort/system/yolo.
-r, --resume BRANCH_ID none Resume prior branch.
-c, --continue-last false Resume most recent branch.
--preset coding none Wire the coding toolkit, path guards, and coding prompt; cwd defaults to the invocation directory
--form SPEC none Validate a YAML/JSON work-form before making any model call, then inject its typed values
--context-from REF none Inject distilled context from a prior session id, branch id, run id, or file path into the new branch's first instruction (above the prompt). Repeatable — refs concatenate in argv order, sharing one budget. cli/_context_from.py
--context-budget N 8000 Total token budget (~4 chars/token) for --context-from content, shared across all refs.
--resume-on-timeout false Resume a timed-out agent session once with a bounded continuation

-r and -c are mutually exclusive. --context-from is rejected together with -r / -c (resume already carries the source context). Common flags apply.

--context-from resolves the ref in order — session id, branch id, run id, then file path — erroring loudly on an unresolvable or ambiguous (2+ match) ref rather than spawning with silently-missing context. Distillation is mechanical (no LLM): a saved artifact/summary verbatim if it fits, else the initial instruction plus final assistant message, else a loudly-marked head/tail truncation.

li agent -a reviewer --bypass --context-from 20260420T110143-a1b2c3 --prompt-file review.md
li agent claude/sonnet "What does Branch.operate() do?"
# output:
Branch.operate() is the universal structured operation entry point...

[to resume] li agent -r 20260420T110143-a1b2c3 "..."

Python equivalent: branch.operate(instruction="...")Branch API

Read status without starting a worker:

li agent status                         # latest agent-kind session in this project
li agent status SESSION_OR_INVOCATION   # full ID or unique prefix
li agent status SESSION_OR_INVOCATION --json

li team

Persistent inbox messaging. Teams are stored at ~/.lionagi/teams/{team_id}.json under fcntl.flock.

li team create NAME -m MEMBERS
li team list     [alias: ls]
li team show TEAM
li team send CONTENT -t TEAM --to RECIPIENTS [--from NAME] [--from-op OP]
li team receive  -t TEAM [--as MEMBER]   [alias: recv]

li team create

Arg/Flag Required Notes
name yes Team name
-m, --members yes Comma-separated member names

Source: cli/team.py

li team create "docs-team" -m "researcher,writer,reviewer"
# output:
Created team 'docs-team' (7fa0d9abbf5b)
  Members: researcher, writer, reviewer
  File: ~/.lionagi/teams/7fa0d9abbf5b.json

list — sorted by mtime; shows ID, name, members, and message count. show TEAM — all messages with timestamps and read_by. TEAM = ID, prefix, or name.

li team send

Arg/Flag Required Default Notes
content yes Message text (positional)
--team, -t yes Team ID or name
--to yes all or comma-separated names
--from no _cli Sender name
--from-op no none Op id; ties signal to a specific flow invocation

Source: cli/team.py

li team send "Research done — see research.md" \
  --team 7fa0d9abbf5b --to writer --from researcher --from-op o1

li team receive

Flag Required Default Notes
--team, -t yes Team ID or name
--as no none Mark as read for this member; omit = see all

Source: cli/team.py

li team receive --team 7fa0d9abbf5b --as writer

Python equivalent: session.send() / session.receive()api/team.md


li o fanout

Three-phase: orchestrator decomposes → N workers in parallel → optional synthesis.

li o fanout [model] prompt [flags]
Flag Default Notes
-a, --agent NAME none Orchestrator profile.
-n, --num-workers N 3 Maximum number of assignments generated
--workers M1,M2,... none Worker model pool, assigned round-robin; excess specs warn when the pool is larger than -n
--max-concurrent N 0 Max concurrent (0 = all)
--with-synthesis [MODEL] false Enable synthesis; bare = orchestrator model
--synthesis-prompt TEXT none Override synthesis instruction
--output {text,json} text Output format
--save DIR none Write artifacts here
--team-mode [NAME] none Create persistent team; bare = "fanout"

Source: cli/orchestrate/__init__.py. Common flags apply.

li o fanout claude/opus-high "Audit lionagi/session/ for stale API surface" \
  -n 3 --with-synthesis --save ./audit-out
# output:
Phase 1: Orchestrator decomposing task into 3 agent requests...
Phase 1 done (3.2s): 3 requests generated.
Phase 2: Fanning out to 3 workers: [claude/opus, claude/opus, claude/opus]
Phase 2 done (14.1s).
Saved 3 worker results to /path/to/audit-out
Phase 3: Synthesis [claude/opus]...
Saved to /path/to/audit-out

Worker outputs are worker_1.md … worker_N.md in the artifact root. Synthesis is written to synthesis.md. Resume cancelled workers with li agent -r BRANCH_ID.


li o flow

Auto-DAG pipeline. The orchestrator plans an initial list of TaskAssignment entries with assignees, dependencies, and execution modes; the engine executes them with dependency-aware parallelism. When reactivity is enabled, workers can emit SpawnRequest follow-up work without re-running the initial planner.

li o flow [model] prompt [flags]
Flag Default Notes
-a, --agent NAME none Orchestrator profile. Resolves .lionagi/agents/<NAME>/<NAME>.md first, then legacy .lionagi/agents/<NAME>.md.
-f, --file PATH none Load flow spec from YAML/JSON file. File values are defaults; CLI flags override.
-p, --playbook NAME none Resolve a project-local, user-global, or trusted-plugin playbook. Declared args are injected as additional flags.
--with-synthesis [MODEL] false Final synthesis after all ops
--max-concurrent N 0 Max concurrent agents per phase (0 = all)
--max-agents N 0 Cap total ops (0 = unlimited)
--dry-run false Plan DAG and print; no execution
--show-graph false Render DAG as matplotlib PNG into --save dir
--bare false Ignore agent profiles; all workers use CLI model
--background false Subprocess run; requires --save; writes <save>/flow.log and prints the monitorable session ID
--output {text,json} text Output format
--save DIR none Artifact dir; required for --background
--team-mode [NAME] none Create a FRESH team every invocation (new UUID). Bare = "flow".
--team-attach NAME none Upsert: attach to existing team by NAME (preserving message history) or create if missing. Mutex with --team-mode.
--team-max-rounds N 2 Extra reactive wake-up rounds for unread teammate messages after active workers finish
--workers M1,M2,... none Mixed worker model pool; preserves each role's profile and overrides model routing
--pack PATH none Per-role routing pack used when --workers is absent
--max-ops N 0 Cap total graph nodes (0 = unlimited); --max-agents is deprecated
--reactive MODE all Roles allowed to emit SpawnRequest: all, off, or a comma-separated role list
--resume ID none Restart a checkpointed flow without re-planning; does not read other planning flags
--allow-degraded-context false Permit resumed inherited-context operations to run with empty predecessor history
--notify CMD none Run a terminal callback template with status/invocation payload values

-f and -p are mutually exclusive. --team-mode and --team-attach are mutually exclusive. Source: cli/orchestrate/__init__.py. --background re-invokes python -m lionagi.cli without itself. Common flags apply.

Team lifecycle summary

Goal Flag Behavior
One-off parallel workers, no shared history --team-mode [NAME] New UUID every invocation. Messages posted; team discarded conceptually.
Persistent thread across invocations --team-attach NAME First call creates; subsequent calls attach to the same team (same UUID, same history). No pre-step required — you never have to li team create first.
li o flow claude/opus "Write and test a CLI arg parser for a new subcommand" \
  --save ./parser-work --with-synthesis

Use --dry-run to inspect assignments, dependencies, and resolved model/mode routing before running. Artifact directories are <save>/{agent_id}/. Python equivalent: Builder + Session.flow()api/flow.md

Checkpoint resume and live control are intentionally separate:

li o flow --resume RUN_OR_SESSION_ID      # prior process ended; replay checkpoint
li o ctl resume RUN_OR_SESSION_ID         # process is alive but paused

li o ctl

Address read/control operations to a durable ID:

li o ctl status ID
li o ctl pause ID
li o ctl resume ID
li o ctl msg ID "Prioritize correctness over breadth"

status reads sessions, invocations, plays, and branch-backed sessions. pause, resume, and msg queue control for a running flow; msg is available for flows using context-mode operator steering. Use li o ctl SUBCOMMAND --help for the command-specific ID and JSON options.


Playbooks (-f, -p, li play)

A playbook is a YAML file that declares a reusable, parametric flow invocation: model, agent, effort, prompt template, and typed CLI args. Bare names resolve project-local .lionagi/playbooks/ first, then user-global ~/.lionagi/playbooks/, then active trusted plugins. Use <plugin>/<name> to select a plugin playbook explicitly.

Playbook YAML shape

name: audit
description: Parametric audit pattern
argument-hint: '[--mode MODE] [--workers N]'   # CC-compatible display string

model: claude-code/opus-4-7
agent: orchestrator
effort: high

args:                       # explicit, typed schema (preferred)
  mode:
    type: str               # str | int | float | bool
    default: dry
    help: "audit mode: dry | security | dead-code"
  workers:
    type: int
    default: 8
  strict:
    type: bool
    default: false

prompt: |
  Run a {mode} audit with {workers} parallel workers. Strict: {strict}.

  Target: {input}

All playbook fields map to li o flow flags. If both args: and argument-hint: are present, args: wins. If only argument-hint: is present, it's parsed as CC does — [--flag VALUE] → string arg, [--flag] → bool arg, no type coercion.

Template interpolation

Inside prompt:, three rules:

  1. {input} → the positional prompt text passed on the CLI.
  2. {arg_name} → a declared arg (CLI override > playbook default).
  3. If the template has no {...} placeholders, the positional text is appended with a blank line — same convention as Claude Code slash commands.

Invocation

# Long form
li o flow -p audit --mode security "the auth service"

# Sugar
li play audit --mode security "the auth service"
li play list                        # list all discovered playbooks
li play audit --help                # show playbook description, args, and usage
li play check audit                 # validate declared playbook artifacts/dependencies
li play status [ID]                 # latest play/flow status, or one durable ID
li play --resume ID                 # resume a checkpointed flow

li play list

Lists project-local, user-global, and active trusted-plugin playbooks. Plugin entries are namespaced as <plugin>/<name>.

li play list
# output:
audit        Parametric audit pattern              [--mode MODE] [--workers N]
refactor     Multi-step refactor with review       [--scope SCOPE]

li play NAME --help

Shows the playbook's description, its declared arguments with types and defaults, and a generated usage line. Does not execute the flow.

li play audit --help
# output:
audit — Parametric audit pattern

Usage: li play audit [--mode MODE] [--workers N] [--strict] PROMPT

Arguments:
  --mode MODE    str    default: dry    audit mode: dry | security | dead-code
  --workers N    int    default: 8
  --strict       bool   default: false

Prompt template:
  Run a {mode} audit with {workers} parallel workers. Strict: {strict}.

  Target: {input}

--help is checked before any flags are forwarded to li o flow, so it never starts execution.

Ad-hoc specs (-f)

For one-off specs not worth installing globally:

li o flow -f ./my-spec.yaml "target"

-f takes an absolute or relative path. -p takes a discovered bare name or an explicit <plugin>/<name> token. They are mutually exclusive.

See the playbook examples for ready-to-install playbooks with different shapes.


Skills (li skill)

A skill is static reference content the agent pulls on demand. Format is identical to Claude Code skills — you can symlink one source file into both ~/.claude/skills/<name>/SKILL.md and ~/.lionagi/skills/<name>/SKILL.md.

~/.lionagi/skills/commit/SKILL.md
---
name: commit
description: Conventional Commits style guide + safety rules.
---

# Commit conventions

... body ...

Commands

li skill NAME          # print body (post-frontmatter) to stdout
li skill list          # list installed skills
li skill show NAME     # print full file (frontmatter + body)

An orchestrator agent can shell out to li skill <name>, capture stdout, and inject the result into its own context — no extra protocol required.

See the skill examples for templates.


li monitor

Observe play/agent/run progress in real time. Replaces fragile file-polling and log-tailing with a single surface. Source: cli/monitor.py (add_monitor_subparser). Alias: li mon.

li monitor                      # table of all running entities
li monitor <id>                 # detail view for one run/play/agent/invocation
li monitor --watch              # live-refresh table
li monitor --watch <id>         # live-refresh detail view
li monitor --since 1h           # entities updated in the last hour
li monitor --type session       # filter table by entity type
li monitor --project myproject  # filter sessions by project
Arg/Flag Default Notes
id none Entity ID or prefix; omit for the table view
-w, --watch false Live-refresh every --refresh seconds
--refresh SECS 2 Refresh interval for --watch
--since WINDOW all Time window: 30m, 1h, 2d
-t, --type none One of session, invocation, show, play
-p, --project none Filter sessions by project name

A session detail view includes a khive injection block when an opted-in profile recorded non-zero recall, injection, failure, or writeback counters. The block contains aggregate counts, not injected source text.

For scripts, use a waiter instead of scraping the watch display:

li wait SESSION_ID PLAY_ID                 # any durable entity kinds; mixed IDs allowed
li wait ID_A,ID_B --interval 2

li monitor run SCHEDULE_RUN_ID             # follows on_success/on_fail children by default
li monitor run SCHEDULE_RUN_ID --no-chain  # wait for only the literal ID
li monitor run SCHEDULE_RUN_ID --follow    # keep watching for later schedule runs
li monitor run SCHEDULE_RUN_ID --max-wait 0

li wait accepts run, session, play, flow-invocation, and scheduled-run IDs or unique prefixes. li monitor run follows a watched run's scheduler chain by default; --no-chain disables that behavior. After the initial set drains, --follow keeps the monitor open and prints newly created schedule runs. The initial wait defaults to a bounded 900 seconds.


li kill

Terminate a running entity by id, or sweep stale entities whose OS process is already dead. Source: cli/kill.py (add_kill_subparser).

li kill abc123                        # kill by id prefix
li kill <session-id>                  # stop a worker process
li kill abc123 --reason 'stuck'
li kill abc123 --recursive            # kill + direct children (session -> invocation)
li kill --all-stale                   # sweep dead-PID sessions/invocations
li kill --all-stale --threshold 3600  # only rows older than 1h
li kill --all-stale --dry-run
Arg/Flag Default Notes
id none Entity ID or prefix: run/session/invocation/play
--reason "" Recorded in status_transitions
--recursive false Also kill direct child entities
--all-stale false Sweep stale sessions/invocations (and their child-derived plays/shows)
--threshold SECS 3600 Only sweep entities started more than this long ago
--dry-run false Only valid with --all-stale; prints without cancelling
--grace SECS 5.0 Wait after SIGTERM before escalating to SIGKILL

--recursive scope boundary. Recursion only reaches PID-bearing workers, and an orchestrator row reaches them only through a link it recorded:

  • Killing a play reaches its worker chain only if the play row records the session it started, in plays.session_id. One path binds that column: the Studio show importer, which resolves the session by name when it mirrors a show directory. A play created by a live run leaves it unset, and a worker session stores no play reference either, so there is no key to resolve those workers by. In that case the kill marks the play row blocked, prints an error saying no worker was stopped, and exits 1 — a play kill never reports success it did not achieve. Kill the worker session ids directly (li monitor lists them). --recursive is not needed for either case: a play row carries no PID of its own, so resolving its workers is the kill.
  • Killing a session with --recursive also cancels its linked invocation.
  • A show id cannot be killed directly today: only running rows are killable, and show rows persist as active (never running), so li kill <show-id> is rejected as already-terminal, with or without --recursive.

To stop everything under a show, kill the play id or session id directly (li monitor <show-id> lists its plays). --all-stale covers the abandoned case only as far as the recorded links allow: a play older than --threshold whose recorded worker session has gone terminal is marked blocked; a play that records no worker session is left alone, because age by itself cannot tell an abandoned play from one still doing hours of work. The sweep prints one line naming how many rows it skipped for that reason, and reports them as skipped_unlinked_plays in its closing counts. A show row is marked aborted only once it is older than --threshold and all of its plays are terminal.


li invoke

Group the sessions a skill spawns (e.g. /show, /codex-pr-review) into one parent invocation record, so the runs list and Studio dashboard collapse "14 sessions" into a single row. Opt-in — sessions spawned without --invocation behave exactly as before. See the CLI internals. Source: cli/invoke.py.

INV=$(li invoke start --skill show --prompt "resolve lionagi issues")
li play backend  ... --invocation "$INV"
li play frontend ... --invocation "$INV"
li invoke end "$INV" --status completed
Subcommand Flags Notes
start --skill (required), --plugin, --prompt, --metadata Opens an invocation; prints its id to stdout
end ID --status (default completed), --metadata Closes it with a canonical terminal status
list --skill, --status, --limit (default 20) Lists recent invocations

li hooks

Import an existing Claude Code or Codex hooks configuration into this project's .lionagi/settings.yaml hooks_external: block, then record trust for the imported commands so they are allowed to execute. Trust is hash-pinned: approval is recorded against the content-hashed argv, so a command that changes after import must be re-approved before it runs. Source: cli/hooks.py.

li hooks import claude                   # reads .claude/settings.json
li hooks import codex .codex/hooks.json  # explicit config path
li hooks trust                           # review and approve pending commands
li hooks trust --yes                     # record trust without the prompt
Subcommand Flags Notes
import SOURCE [PATH] --cwd SOURCE is claude or codex; PATH defaults to .claude/settings.json or .codex/hooks.json
trust --cwd, --yes Lists pending imported hook commands and records approval (content-hashed argv)

li mcp

Serve an MCP server over stdio. The server is a control plane over this CLI: it submits li runs as detached background jobs and answers questions about them, so a submit returns a run_id immediately instead of blocking and the run keeps going in its own process group, surviving an MCP-server restart. Requires the mcp extra (pip install 'lionagi[mcp]'). Source: lionagi/mcp/.

li mcp            # serve over stdio (same as: python -m lionagi.mcp)

The server advertises a single tool, request, and every operation is a namespaced verb passed to it. Seeing one entry in a client's tools/list is correct. Call request with help=true for the catalog of verbs.

Register it with any MCP client (e.g. an .mcp.json). The key here is the local name your client uses to launch the server; the name the server reports over the protocol is lion:

{
  "mcpServers": {
    "lion": { "command": "li", "args": ["mcp"] }
  }
}

Job records live under ~/.lionagi/mcp/jobs/<run_id>/; the authoritative run state is the CLI's own ~/.lionagi/runs/<run_id>/. In job.status, the top-level status is authoritative: the embedded run manifest is advisory and its own status may lag.

The verb catalog, the request result contract, and a worked submit-and-poll example are in the MCP server reference.

When a run's process is gone and nothing recorded how it came out

A background run normally records its own end: the CLI's terminal hook writes it, and a run stopped by li kill leaves it in the lifecycle store, which the server caches onto the job record. A run whose process dies before either of those happens leaves nothing behind at all — no surviving producer can ever write its end.

Where an observation positively establishes that the run's process is gone — the recorded pid holds no process, it disappears between two probes, or a live process holds the number and started at a different time, so it is a different process — job.status, job.list and job.wait record that end themselves and then report it:

Field Value
terminal true
outcome indeterminate
reason_code process_gone_without_outcome
terminal_source mcp_orphan_reaper

outcome: "indeterminate" means the process is conclusively gone and no authoritative outcome was reported. It does not mean the work failed: the run may well have finished what it was doing before it died, and nothing survived to say either way. failed stays reserved for a reported terminal status classified as a failure, and a caller may retry a failed run under its own policy. Do not automatically retry such a run — an external side effect it never got to report may already have committed.

terminal_source says what wrote the end: cli_terminal_hook (the run's own terminal hook), lifecycle_cache (an end read back from the lifecycle store), spawn_failure (the spawn was caught failing), mcp_kill (the run was killed through this server), or mcp_orphan_reaper (this server, from the conclusive observation above). It is null on records written before the field existed.

For a run ended this way, finished_at is when the loss was established and recorded, not when the process exited — nothing surviving can report that instant. Any duration derived from it is therefore an upper bound on how long the run actually ran.

liveness_conclusion on job.status says what the observation established: process_gone, alive, or unknown. Only process_gone can end a run. unknown — a pid the OS cannot be asked about, a denied or unreadable identity probe — never does, and such a run stays non-terminal and advisory (possibly_orphaned), reported by job.wait under stopped_without_end.

job.wait's all_terminal means every valid requested run has a recorded end, including runs whose outcome is indeterminate. It does not mean every run succeeded or reported an outcome; read each entry's outcome for that.

Terminal notices

When a background run finishes, the server records its terminal status and, if a delivery command is configured, runs it — on every terminal state, including failure and kill. Nothing is configured out of the box, so the default is no notice. The command is an argv list run directly (never through a shell); the placeholders {run_id}, {status}, {label}, and {target} are substituted into its arguments, and the same fields are offered as a JSON object on stdin.

Configure the command once via lionagi's own notify.on_terminal setting (~/.lionagi/settings.yaml or a project .lionagi/settings.yaml):

notify:
  on_terminal:
    adapter:
      kind: exec
      argv: ["/usr/local/bin/notify-run", "{run_id}", "{status}", "{target}"]

Or per submit: notify overrides the delivery command for one run (a JSON argv list), and notify_seat fills the {target} placeholder. The environment variables LIONAGI_MCP_NOTIFY_COMMAND and LIONAGI_MCP_NOTIFY_TARGET set a process-wide default. Delivery outcome is recorded on the job and surfaced in job.status (notify_delivery), so a notice that failed to send is visible rather than silently lost. job.list carries the same outcome collapsed to one word in notify_delivery_statedelivered, failed, or none when no notifier was configured — so a run whose notice never went out is spotted while scanning runs, not only when one is looked up.


li handshake

Report what this installation is, for a program deciding whether it can talk to it. Pair it with --machine, since a program is the only caller this is for.

li handshake --machine
{"ok": true, "contract_version": 1,
 "data": {"contract_version": 1, "min_supported_version": 1,
          "implementation": "lionagi", "implementation_version": "0.30.2",
          "module": "/path/to/lionagi/cli"},
 "error": null}

contract_version is the machine-result contract this build speaks and min_supported_version is the oldest it still accepts. A caller checks both once at startup, then validates contract_version on every envelope afterwards — the binary at a pinned path is replaced during normal operation, so a handshake governs registration and never stands in for per-response checking.

module is where the code being served actually lives, which answers "is this the checkout I think it is" without guessing from a version number.


li runs

List the runs recorded on disk and what each one wrote.

li runs --machine [--limit N]

Each entry carries the run id, its state root, its artifact root, and the artifacts found there. It reports which runs EXIST and what they left behind, not whether any of them finished or succeeded — for that, ask li job status or the MCP request operation job.status, which carry the terminal and outcome derivations.

The artifact list is wrapped in the availability shape, so a directory that could not be read is reported as unavailable with a reason rather than as a run that produced nothing.


li lifecycle

Report what the lifecycle store records about one CLI run.

li lifecycle <run-id> --machine

This is the one path from a run id to the rows the lifecycle writers actually write. A normal teardown records an end; so does li kill, which writes the row and signals the process without touching the MCP job record or the run manifest. A caller holding only a run id and reading only those two would see a dead process with no recorded end, which is what an orphaned run looks like.

The answer is read-only and carries its own availability. An established answer with found: false means no session was ever recorded under this id. An unavailable one means the store could not be read at all, which is not a statement about the run. A caller that collapsed the two would report a run as finished, or as never started, on the strength of a database it never opened.

The store consulted is the one LIONAGI_STATE_DB_URL names when it is set, and the default otherwise — including for the question of whether a store exists at all, so a configured store is never reported missing because the default path is absent.


Machine mode: --machine

Any command that reaches the dispatcher accepts --machine, which turns its output into exactly one JSON object on stdout:

{"ok": true, "contract_version": 1, "data": {...}, "error": null}

Exactly one of data and error is present, and error.kind is a closed set a caller may branch on. Diagnostics, progress and warnings go to stderr, so stdout carries the object and nothing else — a caller can parse it without scanning for where the JSON starts.

Anything derived from a read that can fail is wrapped rather than flattened:

{"available": false, "value": null, "reason_code": "unreadable",
 "detail": "permission denied"}

This keeps "there are no artifacts" and "the artifacts directory could not be read" from sharing an encoding, which is the difference between a caller reporting an empty result and reporting a broken one.

Check the exit status before parsing. 78 means nothing executed — the environment could not run the work at all — and stdout must not be parsed on it, because attributing an environment fault to the submitted work is the misattribution the code exists to prevent.

The full contract, including how status, terminal and outcome divide the question of whether a run is over, is in ADR-0106.


li engine run

Run a domain-specific multi-agent engine pipeline without writing Python. Progress events stream to stderr; the final result is emitted as JSON on stdout for piping. Run records persist in the StateDB engine_runs table. Source: cli/engine.py.

li engine run research 'What are the latest advances in GQA?'
li engine run review   'See artifact.py' --model claude/sonnet
li engine run coding   'Implement a BFS traversal' --test-cmd 'pytest'
li engine run hypothesis 'Finding: X causes Y' --export-dir ./out
li engine run planning 'Build a REST API'
Arg/Flag Default Notes
kind Engine kind (e.g. research, review, coding, hypothesis, planning)
spec Main input: topic / artifact / spec / findings / prompt
--test-cmd CMD none Validation command; required for the coding kind
--export-dir DIR none Output directory (coding, hypothesis)
--model MODEL default Provider/model override
--max-depth N kind default Max recursion/expansion depth
--max-agents N none Cap on spawned sub-agents
--session-id ID none Associate with an existing StateDB session
--no-persist false Skip writing the run record to StateDB

Agent profile layout

A profile is resolved by name. Two layouts are supported:

~/.lionagi/agents/
    orchestrator/                      # preferred — directory layout
        orchestrator.md                # main profile
        patterns/                      # optional supplementary references
            empaco.md
        refs/
            commit-conventions.md
    legacy.md                          # flat layout — backward compat

li agent -a NAME and li o flow -a NAME check for <NAME>/<NAME>.md first and fall back to <NAME>.md. Supplementary files beside the main profile are not injected into the initial system prompt — the agent reads them on demand (via direct file reads or li skill).

Project-local .lionagi/agents/ takes precedence over ~/.lionagi/agents/.

See the agent examples for minimal/ and with-refs/ templates.

Profile format

A profile is YAML frontmatter followed by a markdown body (the system prompt). Source: cli/_agents.py (AgentProfile).

---
model: claude_code/opus
effort: high
yolo: true
---

You are an implementer. Write production code, not stubs...

All frontmatter fields are optional; matching CLI flags override them at invocation.

Field Notes
model Provider/model spec (e.g. claude_code/opus, codex/gpt-5.4-xhigh)
effort Reasoning effort level (e.g. high, xhigh)
yolo Auto-approve tool calls
fast_mode Route via the OpenAI priority tier (codex only)
timeout Default hard timeout in seconds
resume_on_timeout Set to once for one bounded automatic continuation
lion_system Prepend LION_SYSTEM_MESSAGE to the body (default: true)
artifact_defaults Expected-artifact defaults; see ADR-0064

When lion_system: true, the global Lion system preamble is prepended to the body to form the system prompt. Set it to false for a verbatim body (e.g. when the profile already carries its own complete system prompt).


Run-ID and persistence

Task-producing agent, fan-out, flow, and playbook invocations allocate a run directory. Administrative commands such as doctor and monitor do not. Run IDs use the format YYYYMMDDTHHMMSS-{6hex}. Source: cli/_runs.py.

~/.lionagi/runs/{run_id}/
  run.json                        manifest (command, branches, artifact_root)
  branches/{branch_id}.json       branch snapshot — resumable via -r / -c
  stream/{branch_id}.buffer.jsonl live chunk buffer during streaming
  artifacts/                      deliverables — only when --save was NOT given

Authoritative state always lives under ~/.lionagi/runs/{run_id}/, so any branch is resumable from anywhere. User-facing artifacts (per-agent working dirs, synthesis.md, flow.log, flow_dag.png) land in the --save directory when one is provided, otherwise in artifacts/ under the run dir. The --save directory is not authoritative state — deleting it does not break -r.

Pre-run-scoped sessions (legacy ~/.lionagi/logs/agents/{provider}/{branch_id}) are still read as a fallback on resume.

Resume any prior branch:

li agent -r 20260420T110143-a1b2c3 "follow up"
li agent -c "continue most recent"

Env Vars

Variable Purpose Source
LIONAGI_RUN_ID When explicitly set for a task-producing child process, reuse the supplied run ID cli/_runs.py
LIONAGI_HOME Override ~/.lionagi/ base dir lionagi/utils.py
LIONAGI_WORKER_LIVENESS_TIMEOUT Seconds run() waits for a CLI worker's first stream chunk before retrying once, then raising WorkerLivenessError; default 120, 0 disables. Applied by default only to endpoints that stream output early (claude_code, codex) — buffered endpoints (gemini-cli, pi) are unaffected unless liveness_timeout is passed explicitly to run() lionagi/operations/run/run.py
OPENAI_API_KEY OpenAI REST API key (for iModel, not for codex CLI alias) lionagi/config.py
ANTHROPIC_API_KEY Anthropic REST API key (for iModel; claude alias uses claude login instead) lionagi/config.py
GEMINI_API_KEY Gemini API key (gemini provider, not gemini-code CLI auth) lionagi/config.py
GROQ_API_KEY Groq key lionagi/config.py

Sources: cli/agent.py · cli/team.py · cli/orchestrate/__init__.py · cli/orchestrate/fanout.py · cli/orchestrate/flow.py · cli/_providers.py · cli/_runs.py

Next: Python API reference