@lmctl-ai/lmctl
v0.1.273
Published
A provider-agnostic control plane for teams of AI coding agents — across providers, with independent review and durable memory.
Downloads
12,723
Readme
lmctl
A provider-agnostic control plane for teams of AI coding agents.
Homepage · Documentation · Changelog
AI agents shouldn't be locked to one provider or one context window. lmctl is a local-first control plane for running teams of AI coding agents — across providers, with adversarial cross-provider review and durable memory, composed in plain text. It coordinates the agent CLIs you already use (Claude, Codex, Gemini, and more); it's not an IDE and not another chatbot.
Two things set it apart from how "multi-agent" and "AI review" usually work:
- Review is adversarial, not self-review. The reviewer is a different provider and model from the author — a Claude lead hands coding to Codex and has Gemini review it — so a model's blind spots aren't rubber-stamped by itself in a different hat. Different models have different (uncorrelated) blind spots; that model diversity is the point — a varied team catches what one model, or its clones, can't.
- You build and tune the team — the provider doesn't. Most "multi-agent" tools have one provider auto-spawn agents you can't steer. In lmctl you divide the work, compose the team in plain text, pick the provider and model for each role, and tune how they interact.
- Any model, local or remote — any mix in one team. Alongside the major CLIs, the OpenCode provider reaches any model: local (Ollama) or remote (DeepSeek, Qwen, OpenRouter, Copilot's GPT/Claude/Gemini, and more). Put any collection of them in a single team, working together — not one model at a time.
Public preview. Free to use during the preview. Closed-source, proprietary software distributed in binary form — see the License.
Install
npm install -g @lmctl-ai/lmctlUsage
lmctl --help
lmctl status
lmctl plan ./my-project
lmctl lint ./my-project/my-project.lmctl
lmctl seed ./my-project/my-project.lmctl
lmctl chat ./my-project/my-project.lmctl Lead "Inspect the repo and propose next steps."
lmctl mail sent --json
lmctl mail sent --limit 100 --cursor "$NEXT_CURSOR" --jsonlmctl status is team/SELF-scoped: from a member session it shows your
identity, team roster busy/idle state, and recent activity in both directions.
From a plain operator shell it shows a DB-wide summary.
Use lmctl status --json for the full machine-readable view; --project and
--web are not status flags.
Start with the plan -> lint -> seed -> chat flow. lmctl does not manage
foreground vs. background execution for provider turns or shell commands; use
the provider or host runtime's own background/task surface when you need async
execution.
Daemon, web, and API surfaces are secondary integration paths; the CLI is the
golden path for onboarding.
To inspect the daemon, run lmctl api daemon state while lmctl serve is
running; authenticate with LMCTL_API_TOKEN=<token> or start local serve with
--insecure-no-auth.
Lintable teamfile templates for research, writing, and review teams ship in the
examples gallery.
lmctl top-level subcommands are: api, chat, serve, device,
team, terminal, tail, health, session, session-index, restore, restore-teamfile,
ls, lint, seed, hire,
refresh, clone, plan, diagnose, diagnose-prompt,
mail, status, running, and db. Run lmctl <command> --help for details on each, and see the
documentation to define your first team.
Abstract managed-team providers retain the config keys
workspace.provider1, workspace.provider2, and workspace.provider3.
Set their concrete provider names directly in ~/.lmctl/state.db; the key
names remain stable for compatibility:
sqlite3 ~/.lmctl/state.db "INSERT OR REPLACE INTO lmctl_config(key,value,updated_at) VALUES('workspace.provider1','claude',strftime('%Y-%m-%dT%H:%M:%fZ','now'));"
sqlite3 ~/.lmctl/state.db "INSERT OR REPLACE INTO lmctl_config(key,value,updated_at) VALUES('workspace.provider2','codex',strftime('%Y-%m-%dT%H:%M:%fZ','now'));"
sqlite3 ~/.lmctl/state.db "INSERT OR REPLACE INTO lmctl_config(key,value,updated_at) VALUES('workspace.provider3','gemini',strftime('%Y-%m-%dT%H:%M:%fZ','now'));"lmctl mail exposes low-level, read-only/record-only primitives over the
event-log substrate: list pending or sent messages, read event history/
content, inspect historical provider evidence and causal lineage, and declare
handling/acknowledgement. mail pending defaults to messages addressed to
SELF when run from a member session; an explicit --receiver overrides that
scope. From a plain operator shell with no SELF marker, it retains the
fleet-wide discovery view.
Missing causal capture is shown as an explicit tree root rather than guessed
from timestamps.
For every mail subcommand, --json is the stable external contract and
includes schema_version: 1; default text output is for humans and may change.
JSON failures use {status:"error", errorKind, message}. The unbounded sent
and tree scans are cursor-paginated, with 100 results by default and 1,000
maximum; pass next_cursor back through --cursor until it is null.
schema_version is the authoritative output-contract version; the separate
schema: "event-log-v1" field identifies the backing substrate.
Stable top-level success fields are:
sent:schema_version,schema,identity,filters,messages,next_cursor.history:schema_version,schema,message_id,events.seen:schema_version,schema,message_id,receiver,db,transcript,answered.pending:schema_version,schema,filters,messages.read:schema_version,schema,message_id,message_type,in_reply_to,sender,receiver,content,pending_state.deliver:schema_version,schema,message_id,message_type,receiver,outcome,provider_status,message.handle:schema_version,schema,message_id,handling_by,handling_at,appended.ack:schema_version,schema,message_id,acked_by,appended,pending_outcome.tree:schema_version,schema,root,since,since_timestamp,sent,next_cursor,orphan_policy,incomplete_capture_note.lmctl chat <teamfile> <alias> "message"drives or messages a member. If a member-origin target is busy, chat queues the message for that sender->receiver lane; the next chat from that same sender to that receiver, when it is free, delivers the queued lane plus the new message. The lane key is(sender, receiver): a chat to that receiver from anyone else does not flush your queued mail. If you go idle waiting for the reply you queued, nothing else will unblock it — you are the only party whose next chat flushes that lane.
Model selection
Teamfiles can pin provider models with _MEMBER_ ... model=<id>. For
provider=opencode, GitHub Copilot reasoning variants can also be selected with
effort=<variant>, for example:
_MEMBER_ alias=Reviewer provider=opencode model=github-copilot/gpt-5.4 effort=xhigh sessiondir=/repoIf ~/.config/opencode/opencode.json is missing, lmctl lint warns and points
to this sample config.
Copy it into your opencode config location and adjust it for the models your
account can access.
Requirements
- Node.js >= 24.15.0. lmctl uses built-in
node:sqlite, so installation has no native SQLite build step.
Links
- Homepage: https://lmctl.com
- Documentation: https://lmctl.com/lmctl
- Changelog: https://lmctl.com/lmctl/docs/changelog
- License: https://lmctl.com/lmctl/docs/license
License
Proprietary — free during the public preview, © Lattice Systems LLC, all rights reserved. Not licensed for redistribution or modification. Provided as-is, without warranty. See the full License.
