Skip to main content

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 commandslmctl status for 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. api is 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.