CLI reference
lmctl is a local command-line tool. It runs on your machine and works
directly against your local lmctl state DB, normally under ~/.lmctl/.
Provider CLIs still use their own configured services when they run model
turns, and the optional cloud console is an explicit opt-in.
Its commands come in two shapes, both part of the same CLI:
- top-level commands —
lmctl statusfor team/SELF state,lmctl chat,lmctl mail,lmctl serve,lmctl team,lmctl seed,lmctl refresh,lmctl diagnose, and so on. - the
lmctl api <noun>group — call the local HTTP API or direct DAL endpoints.apiis just the name of a command group; it is not a separate binary or a remote client.
Setup and status
lmctl status
lmctl diagnose
setsid lmctl serve start > lmctl.log 2>&1 < /dev/null & disown
lmctl status [--json] [--since <duration|ISO8601>] shows the member/team view
when LMCTL_SELF_SESSIONID resolves, and a team/activity summary from an
operator shell otherwise.
--project and --web are not status options; use --since 7d or an ISO
timestamp to widen the activity window.
lmctl serve start starts the local HTTP API, web UI, terminal manager, agent
services, and opt-in queue daemon loops for local service integrations. It runs
in the foreground and blocks until stopped; background or supervise it yourself. See
Daemon and session inspection.
DB teams
lmctl team create <name>
lmctl team list
lmctl team show <name>
lmctl team add-member <team-name> --alias A --provider P [--model M] [--role R] [--sessiondir D]
lmctl team seed <team-name> [--alias A]
lmctl team refresh <team-name> --alias A
Teamfiles, clone, lint, seed
.lmctl teamfiles are editable team documents. Use clone to copy a teamfile
without carrying over session ids:
lmctl clone ./backend/backend.lmctl ./backend-v2/backend-v2.lmctl
lmctl lint ./backend-v2/backend-v2.lmctl
lmctl seed ./backend-v2/backend-v2.lmctl
Cross-team calls work automatically at runtime — there is no command to wire them up. See Cross-team calls.
lmctl lint <teamfile.lmctl> validates teamfile syntax, session placeholders,
and configured models. lmctl seed <teamfile.lmctl> fills missing or
placeholder session ids by calling the configured native providers.
Generate a starter team document for a directory:
lmctl plan ./backend --provider codex
Direct member chat
Use lmctl chat when an operator or Lead needs to drive a specific member
directly. The common teamfile forms are:
lmctl chat ./team.lmctl:Coder "Implement the smallest safe fix."
lmctl chat ./team.lmctl Coder "Implement the smallest safe fix."
lmctl chat ./team.lmctl Reviewer "Review Coder's latest change."
chat is synchronous by default: it sends one prompt and blocks until that
provider turn finishes or errors. Verified against lmctl 0.1.248: with the
default queue setting, a busy receiver returns a busy error, exits non-zero,
and creates no queued mail row. Exit code alone is still not a task-completion
contract; use --json, status, health, and mail evidence when automation
needs to know the exact state. For raw provider sessions, use one of:
lmctl chat <sessionid> "Prompt text" --provider codex
lmctl chat --provider codex --session <sessionid> "Prompt text"
For safe input, especially prompts that contain command examples, backticks,
shell variables, or quotes, use --prompt-file:
lmctl chat ./team.lmctl Coder --prompt-file task.md
lmctl chat ./team.lmctl Coder --prompt-file -
With a positional prompt, your shell expands backticks, $(...), $VAR, and
quotes before lmctl sees the text. --prompt-file bypasses that shell
construction step. Write the prompt file with an editor or file-writing tool,
not with echo or a heredoc, because those still go through your shell.
By default, chat remains synchronous:
lmctl chat ./team.lmctl Coder "Run the long verification pass."
The mailbox queue is opt-in. If mailbox_queue_enabled=true or
LMCTL_MAILBOX_QUEUE_ENABLED=true is set, and lmctl can resolve a sender
identity, the same chat command can queue work for a busy target. If the
target is idle, chat drives a normal blocking turn. If the target is busy,
lmctl queues the message in that sender-to-receiver lane:
lmctl chat ./team.lmctl Coder "status note"
Exit 0 with enqueued mailbox message N means the prompt is queued, not
delivered yet. The lane is keyed by (sender, receiver). Base queued delivery
is the next lmctl chat from that same sender to that same receiver once the
receiver is free. A chat from another sender to the same receiver does not
flush the lane. With lmctl serve start running in normal daemon mode, mailbox
relay can drain queued lanes proactively after the receiver goes idle. When the
queue is off, there is nothing for the relay to drain. A receiver held by
lmctl terminal is legitimately busy.
Use lmctl chat ... --json for automation. Verified against lmctl 0.1.223:
JSON results that create a real message include message_id, which can be used
directly with lmctl mail read or lmctl mail history for inspection, and with
lmctl mail ack when you intentionally acknowledge handling.
If the receiver is free and the synchronous turn is accepted, chat --json
emits newline-delimited JSON: first an early durable-creation line, then the
final result line with the same message_id:
{"status":"accepted","message_id":"msg_tcl_10919"}
{"status":"ok","replyText":"OK","teamChatLogId":10919,"message_id":"msg_tcl_10919"}
The final line may instead be the existing stalled shape for a stalled turn.
Busy and enqueued outcomes still emit one JSON line. With default synchronous
behavior, a busy result has status: "busy" and no queued row. With the opt-in
queue enabled, the queued contract remains status: "enqueued" with
path: "enqueued"; queued JSON keeps the existing id field and also includes
message_id. See
Verifying delegated work.
Current API surfaces
lmctl api calls the local HTTP API or selected direct DAL endpoints. Keep the
current public docs focused on operational surfaces rather than retired
workflow/project-engine endpoints:
lmctl api status
lmctl api teams
lmctl api daemon state
lmctl api daemon cycle
lmctl api external-objects
lmctl api external-signals
Many API commands support --json. Prefer JSON when another program or agent
will parse the output. Use lmctl help api to confirm the installed command
shape, and treat subcommands as API-specific or advanced unless current
operator guidance names them.
Foreground/background ownership
lmctl chat blocks and returns a member reply when it can drive the receiver
now. By default, a busy receiver returns a busy error. With the opt-in mailbox
queue enabled and sender identity resolved, the same command queues if the
receiver is busy. Without sender identity, there is no lane to queue into.
lmctl does not expose a separate LLM-called harvest command.
Queued delivery
The member-to-member lifecycle is:
queued -> in-flight -> delivered with receipt
This lifecycle is real only when the mailbox queue is explicitly enabled. With
sender identity and queueing enabled, chat queues when the target is busy and
delivers directly when the target is idle. If chat exits 0 with
enqueued mailbox message N, that means queued, not delivered yet. When a
delivery turn runs, the queued lane is sent as one provider turn and the target
response is recorded as the receipt. Delivery is at-least-once in queue-enabled
mode: if a process dies after sending but before marking rows delivered, lmctl
may deliver the same queued message again. A duplicate is preferable to losing
work.
What delivers queued mail: base delivery is the next lmctl chat from that
same sender to that same receiver once the receiver is free. Mail queued by a
different sender is not affected; each (sender, receiver) pair has its own
lane. With lmctl serve start running in normal daemon mode, mailbox relay can
drain queued lanes proactively after the receiver goes idle. If the receiver is
still in a provider turn, or a human is holding that member with
lmctl terminal, the mail waits. There is no separate LLM-called harvest
command.
Legacy compatibility APIs
Some older workflow/project-engine API subcommands may still dispatch for compatibility. They are not the normal current agent-facing way to drive lmctl:
lmctl api attentions ...
lmctl api issues ...
lmctl api jobs ...
lmctl api runs ...
lmctl api workflows ...
lmctl api projects ...
lmctl api upload ...
Treat these as legacy compatibility or advanced API work unless your current operator workflow explicitly calls for them.
Mail evidence and actions
lmctl mail works with event-log messages, historical delivery evidence, and
causal lineage. Use --json for automation; mail JSON is the stable, versioned
external contract. Most subcommands below are read-only; ack is an active
acknowledgement that appends an event.
lmctl mail sent --to "/abs/path/team.lmctl:Coder" --status queued --json
lmctl mail history <message_id> --json
lmctl mail read <message_id> --json
lmctl mail seen <message_id> --json
lmctl mail ack <message_id> --json
lmctl mail tree --since 3d --json
Use mail sent --status queued when the opt-in mailbox queue is enabled and
you need to inspect queued work. With default synchronous behavior, a busy send
returns an immediate busy result instead of creating a queued row. See
Mail inspection.
Sessions
lmctl ls
lmctl ls --runs --limit 10
lmctl terminal <teamfile>:<alias>
lmctl tail <session-id> --provider codex
lmctl tail ./team.lmctl Coder
lmctl tail --session <session-id> --provider codex
lmctl health <teamfile>
lmctl health ./team.lmctl Coder
lmctl health <session-id> --provider codex
terminal --size reports message count, transcript bytes, and a local token
estimate. It does not compact or change the session.
For low-level provider-session inspection, use lmctl session --query-file.
This is not compact team status. Write a query file such as:
{"teamfile":"/path/team.lmctl","alias":"Lead"}
Then run:
lmctl session --query-file session-query.json
See Daemon and session inspection.
Device
lmctl device init
lmctl device id
lmctl device prompt --root ./team.lmctl --text "Summarize current status"
Debug logs
Debug output goes to ~/.lmctl/debug-*.log, not the terminal. Set
LMCTL_DEBUG=1 before a command, then inspect the newest debug file if you need
provider or transport diagnostics.
Connecting to a remote daemon (advanced)
By default lmctl uses your local daemon and needs no auth. To point the CLI
at a different or remote daemon — for example a shared host — set:
export LMCTL_API_URL=http://127.0.0.1:8787
export LMCTL_API_TOKEN=<token>
lmctl then sends the token as a bearer token to that daemon. Most setups
never need this.