Skip to main content

Direct chat & background work

Verified against lmctl 0.1.248.

Current command surface: lmctl chat sends work to one member. By default it is synchronous: when the receiver is idle, it blocks for one member turn and returns the reply; when the receiver is busy, it returns a busy error and creates no queued mail row. The mailbox queue still exists, but it is opt-in: set mailbox_queue_enabled=true in config or LMCTL_MAILBOX_QUEUE_ENABLED=true when you intentionally want busy sends to queue.

Synchronous direct chat

Use lmctl chat when you want one member to handle one prompt now:

lmctl chat ./team.lmctl Coder "Implement the smallest safe fix."
lmctl chat ./team.lmctl Reviewer "Review Coder's latest change."

This blocks until the provider turn finishes or errors. It is the right path for handoffs, review requests, and operator answers where the shell should stay attached to the result.

For anything non-trivial, put the prompt in a file and use --prompt-file:

lmctl chat ./team.lmctl Coder --prompt-file task.md
lmctl chat ./team.lmctl Coder --prompt-file -

A positional prompt is built by your shell before lmctl sees it. Backticks, $(...), $VAR, and quotes can be expanded locally. --prompt-file avoids that shell layer and is the safer form for review packages, command examples, and long prompts. Write the prompt file with an editor or file-writing tool, not with echo or a heredoc, because those still go through your shell. This path is available in @lmctl-ai/lmctl 0.1.154 and was rechecked in 0.1.218.

Default busy behavior

With the default queue setting, a busy receiver is immediate feedback to the caller:

{"status":"busy","message":"... holder_pid=739451, live ...","teamChatLogId":null}

The process exits non-zero and lmctl mail pending has no new row for that send. The client owns retry, backoff, or inspection. Use lmctl health <teamfile> <alias> --json, lmctl status --json, and the holder PID/liveness details in the busy result before deciding whether the receiver is merely slow, terminal-held, phantom-busy, or stalled.

Opt-in queued member messages

The queued-mail lifecycle is retained for installations that explicitly enable it with mailbox_queue_enabled=true or LMCTL_MAILBOX_QUEUE_ENABLED=true. When lmctl can resolve a sender identity and queueing is enabled, lmctl chat can put work into the sender-to-receiver path when the target is busy. If it exits 0 with enqueued mailbox message N, that means queued, not delivered yet. The lifecycle is:

queued -> in-flight -> delivered with receipt

Delivery is at-least-once in queue-enabled mode: after a crash, a queued message may be delivered again rather than lost.

If a provider process dies while holding an in-flight member lock, lmctl checks holder PID liveness on later access and can reclaim stale locks automatically. Treat that as normal self-healing; do not edit the DB by hand.

Base queued delivery does not require a daemon: the sender's next lmctl chat to that same receiver delivers that sender's queued lane 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 is an optional accelerator that can drain queued lanes proactively after the receiver goes idle. When queueing is off, there is nothing for the relay to drain. If the receiver is still in a provider turn, or a human is holding that member with lmctl terminal, queued mail waits.

Practical consequence in queue-enabled mode: if the sender is idle because it is waiting for the queued reply, and nobody sends another lmctl chat from that same sender to that receiver, the queued mail will not unblock itself unless the optional relay drains it. Run lmctl status to see pending outbound lanes and member busy/idle state. If queued mail is not moving and the receiver is idle or phantom-busy from a dead holder PID, run lmctl serve status.

Quick choice

NeedUse
Ask one member and receive the replylmctl chat <teamfile> <alias> "<prompt>"
Busy target with default configTreat the busy error as the result; inspect holder PID/liveness and retry later
Deliver opt-in queued member mailSend the next lmctl chat from the same sender to the same receiver after it is free; keep lmctl serve start running when you want relay to drain queues proactively
Inspect pending mailbox laneslmctl status