MIT License

项目介绍

damianvtran/local-operator 是一个托管在 GitHub 上的开源项目,主要使用 Python 编写,采用 MIT License 许可证,目前获得 229 个 Star、36 次 Fork。

仓库全名
damianvtran/local-operator
开源许可证
MIT License
Star 数
229
Fork 数
36
未关闭 Issue
8

README 摘要

src="./static/local-operator-icon-2-light-clear.png"> Local Operator An open-source AI agent hub: build organizations of collaborating agents that run on your own machine, around the clock Roles, teams, and cross-agent messaging on top of a fast terminal UI, using every AI subscription you already pay for A manager session with three workers running in the background (each with its own role, budget, and progress)…

完整内容见 GitHub 原始 README 或本页 。

常见问题

local-operator 用什么语言开发?

主要使用 Python 编写,采用 MIT License 许可证,目前有 229 个 Star。

local-operator 可以免费商用吗?

仓库采用 MIT License 许可证,可免费使用与修改,商业使用请遵守许可证条款(例如保留版权声明与许可声明)。

如何获取 local-operator 的源码?

可以直接克隆仓库:git clone https://github.com/damianvtran/local-operator.git,默认分支为 main;安装与运行步骤见仓库 README。

local-operator 最近还在维护吗?

仓库最近一次更新为 2 天前,当前有 8 个未关闭 Issue;Star 数为 229,可作为社区活跃度的参考。

关于

GitHub 原始描述(英文)

AI agents platform that gives you a workspace with an integrated team of personal assistants that can work behind the scenes to handle daily monotonous tasks.

描述取自 GitHub 官方 API,保持仓库作者书写的原文。

简要分析

热度分
307
价值分
148
活跃状态
活跃

语言占比

C
CSS
Dockerfile
HTML
JavaScript
Makefile
Nix
Objective-C
PowerShell
Python
Shell
TypeScript

README

Local Operator logo

Local Operator

An open-source AI agent hub: build organizations of collaborating agents that run on your own machine, around the clock

Roles, teams, and cross-agent messaging on top of a fast terminal UI, using every AI subscription you already pay for


A manager session with three concurrent workers in the subagent dock, each showing elapsed time, context usage, and cost, above a shared todo list

A manager session with three workers running in the background (each with its own role, budget, and progress) while the shared plan updates.


Local Operator is a harness for organizations of agents: the runtime that hosts, supervises, and connects them. A single session plans its own work, runs tools, browses, and remembers what it did. Give it a team and it becomes a manager that delegates to tool-restricted workers, messages sibling sessions in other repos, schedules its own follow-ups, and picks those follow-ups back up after you close the terminal. Everything runs on your machine, asks before it writes or executes, and is MIT licensed. It draws on every ChatGPT, Claude, Kimi, Grok, Z.AI, and Qwen login you already have, pooled, load-balanced, and used with the prompt cache in mind.

📚 Table of Contents

✨ Why Local Operator

  • Agent organizations with enforced roles. Roles are capability boundaries (a reviewer loses the tools to edit what it reviews), specialists carry their own standing instructions, and a team is a saved roster (a manager plus members) with two short briefs: how the group works together and what product it owns. Reuse the same roster on a different product by swapping one brief. Nested teams show in the chart today; a manager delegating into a nested team’s manager is coming.
  • Agents that talk to each other. A manager peeks at, questions, steers, pauses, and resumes its workers through hub; independent sessions in different repos message each other with send, choosing whether to leave a note, wake an idle peer, or redirect one mid-turn. Loopback only, your OS account only.
  • Always on. Wakes persist and fire on schedule even after the terminal is closed; long commands and subagents run as background jobs whose results auto-deliver when the session is idle; lop exec --background detaches a whole task; a paused worker resumes after a process restart; the mobile daemon keeps every session reachable from your phone.
  • Every subscription you already pay for. Sign in to ChatGPT, Claude, Kimi, Grok, Z.AI, and Qwen with the accounts you already have. Have two Claude or ChatGPT accounts? They’re pooled: eligible accounts default to cached reset-soonest selection, a rate-limit tries a sibling, and only when both are exhausted does it fall back to the next model you’ve listed.
  • Cheaper long sessions. The prompt is laid out so your provider’s prompt cache keeps hitting turn after turn: stale tool output is cleared without invalidating the cache, large contexts automatically get the longer Anthropic cache window, skills and MCP tools load only when used, and agents wait for events instead of polling. The result: a long session doesn’t re-pay for its history every few minutes.
  • Approval-gated by default. Reads run; writes and shell commands show the exact command and ask. Opting out is an explicit act: /approvals auto or --yolo. Every tool call leaves a visible receipt in the transcript.
  • Reach beyond the terminal. Watch and steer sessions from your phone, and drive the Chromium browser you already use, with your real logins, through the published browser extension. The desktop app puts the same sessions in a desktop window, with a browser pane, a file canvas, and — coming soon — a terminal agents can use.

🚀 Quickstart

One line installs it — no Python needed beforehand (the installer uses uv, which brings its own Python 3.12+). It announces each step with an estimate and finishes in about 20–30 seconds on a clean machine:

# macOS / Linux
curl -LsSf https://raw.githubusercontent.com/damianvtran/local-operator/main/scripts/install.sh | sh
# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://raw.githubusercontent.com/damianvtran/local-operator/main/scripts/install.ps1 | iex"

Then start it and sign in:

lop                  # opens the setup screen
/login radient       # recommended: one browser sign-in, or create an account there

Radient is the recommended first sign-in: no existing AI subscription or API key needed, many models behind one account, and it is also what phone access signs in through. Everything else works too — /login lists every provider, grouped as Use a subscription (ChatGPT, Claude, SuperGrok, Kimi, Z.AI), Use an API key (OpenAI, Anthropic, xAI, Google, DeepSeek, OpenRouter, Mistral, Qwen), and Run models on this computer (LM Studio, Ollama, vLLM, llama.cpp). The same list is lop login in a shell.

After you sign in, Aida — your chief of staff in Local Operator — opens and says hello: she asks your name (your Radient account already tells her), how you want to use AI (“don’t know yet” is a fine answer), explains what Local Operator can do, and helps you set up your first agents and teams.

Prefer a package manager? uv tool install local-operator or pipx install local-operator do the same thing. Plain pip install local-operator needs Python 3.12+: on an older Python pip reports “from versions: none”, and on Debian/Ubuntu/Homebrew Pythons it refuses with externally-managed-environment — use the installer or uv/pipx instead.

lop is the short alias the install provides alongside local-operator; the rest of this page uses it. lop login <provider> also sets that provider as your default hosting and picks a default model when none is configured, so the very next lop just works. A headless or piped run with nothing configured prints the exact commands to configure hosting, model, and a key instead.

Inside, esc stops the agent, /help lists commands, /exit quits. Try something like summarize this repo and list what’s untested. lop update upgrades the install from PyPI and restarts the mobile daemon when the LaunchAgent is installed.

The Local Operator welcome screen with rotating tips and the composer ready for a first prompt

The main view of the Local Operator TUI: the splash, the keybinding hints, and the composer where you start your first prompt.

Prefer a local model? A 7–14B model needs roughly 10–16 GB of RAM or VRAM. Start LM Studio, load a chat model, and enable its server in the Developer tab; then /login lmstudio inside Local Operator picks the endpoint and model. /login also offers Ollama, vLLM, llama.cpp, and a generic OpenAI-compatible server. See the local-provider guide. The CLI form still works for a model installed in Ollama:

lop --hosting ollama --model qwen2.5:14b

🪟 Desktop App (local-operator-ui)

The terminal stays the core surface, and the rest of this page describes it. The desktop app is the second way in: Local Operator UI is a desktop application for macOS, Windows, and Linux that drives the same sessions, teams, schedules, and configuration, and adds the surfaces a terminal cannot draw.

The desktop app on the Invoices workspace session: the rail (New chat, Search, Aida, Agents, Projects, Schedules, Browser, Agent hub, Mesh) above the Agents and Teams sections; the transcript's reconcile request, Read invoices/march.csv and Ran python reconcile.py receipts, and the streamed write-up; the Run details pane with two subagents running (one drafting the summary, one auditing) and the shared to-do list; and the composer holding a draft.

The desktop app on the Invoices workspace: the rail with its chats, agents, teams, and mesh; the transcript's tool receipts and streamed write-up; two subagents running above the shared to-do list; and the composer holding a draft. Fixture-driven like the mesh figures below — the real interface over the app's own fixtures, not a live session.

What the app adds:

  • A browser the agent can drive. A Chromium pane with its own tabs and address bar lives inside the session window, with an approval prompt before the agent acts on a page, so a task can browse where you can watch it.
  • A file canvas and viewer. Documents a session works on open beside the chat — markdown (with a WYSIWYG editor), code, HTML, images, PDFs, spreadsheets, audio, and video — instead of being printed into the transcript.
  • The same surfaces, as pages. Agents, schedules, and settings are pages; teams are panels on the agents page — all reachable from a command palette, over the same configuration the TUI reads, with a built-in theme gallery.
  • A terminal agents can use — coming soon. A per-session Console tab with real terminal emulation, which agents can open, read (stdout and stderr), capture visually, and type into. That is what makes end-to-end testing of interactive TUIs, and of other commands that need a live terminal, possible for an agent where bash alone cannot.

A desktop app conversation carrying images: a chart card at the top ('March invoices — who still owes us?'), the assistant noting it is plotting the amounts, a Ran python plot_outstanding.py receipt, the bar chart it rendered ('Outstanding at the end of March': Contoso 4,820, Fabrikam 1,150), and the closing write-up.

A conversation where images are part of the turn: a chart pasted into the session, and the bar chart the agent plotted and showed inline above its write-up.

Easiest install — the desktop build. Download the installer for your platform from the downloads page (macOS, Windows, and Linux, with the system requirements listed there). The app bundles its own backend and installs it on first run, and uses an existing Local Operator install when it finds one, so a separate lop install is optional.

Or install from a terminal (Node.js 22.13.1 or newer):

npx local-operator-ui              # download and run in one command
npm install -g local-operator-ui   # or install it globally
local-operator-ui                  # ...then start it by name

Source, issues, and release notes live at damianvtran/local-operator-ui.

🏢 Agent Organizations

Three layers: subagents are the parallel workers, roles decide what each one is allowed to touch, and a team is a saved roster you can point at a different product.

Subagents. Ask for parallel work and the agent fans it out into concurrent background workers, then keeps working while they run. The subagent dock shows each worker’s status, spend, and progress live, and you can open any of them to read its transcript and plan (the reader’s keys and limits are in docs/subagent-reader.md).

The desktop app's run pane mid-fan-out: two subagents running — 'Summarise the findings' (writer, drafting the summary) and an audit running pytest (reviewer) — each with elapsed time, context use, and spend, above the shared to-do list; beside them, the conversation shows the reconcile request and a Read invoices/march.csv receipt.

Two workers mid-run in the run pane — one drafting, one auditing — each with its role, elapsed time, context use, and spend, above the to-do list they share.

Roles are capability boundaries. A subagent launched as reviewer carries vetted review guidance and loses the tools to edit code. It can read and run tests, but it has no way to alter what it reviews. A restricted role cannot enable new MCP tools either, and the restriction is inherited by everything it delegates to, at any depth. Packaged starters for reviewer, coder, architect, manager, designer, scout, ux-reviewer, tui-designer, and copy-reviewer ship in the package: task(agent=…) and /team use them even on a fresh install, and agent install copies one into your registry so you can edit it. lop agents list shows what’s installed, so a fresh install prints “No agents found.” You can also author your own agent profiles: reusable roles and named specialists with their own instruction sets, matched to tasks by semantic routing. When a profile gives bad guidance, you fix the profile once instead of every prompt that uses it.

Teams. A saved roster (a manager plus members with counts) layered with two briefs the individual agents never hard-code: a collaboration brief (how this group works together, who blocks a release) and a project brief (what product this instance owns). Swap the project brief and the same roster staffs a different product. A roster slot can name another team (team:<name>), so a team becomes an org of teams; /team chart <name> draws it as an org chart. Nesting is live: task(agent="team:<name>") starts that sub-team’s manager, with its own roster and briefs. lop teams list is empty until you create one.

The desktop app's Agents and teams page with the release-crew team open: its name and description, manager agent (architect), members (coder ×2, reviewer ×1), collaboration instructions, and project brief; the page's own list shows the release-crew and docs-pod teams.

A team in the desktop app: the roster — manager plus members and counts — and its two briefs, how the group works together and what it ships.

The /team picker listing a saved team

Sending a real request to a team: /team lopdev Can you implement a mobile relay functionality in lop using tailwind, shadcn

Sending a request to a team is one line: /team <name> <request> makes the current agent that roster’s manager, which breaks the work down and puts the right roles on it. Agents and teams can also be managed from the CLI:

lop agents create "My Agent"
lop agents list
lop teams list
lop teams show lopdev

🔁 Cross-Agent Communication

Two shapes of conversation, one machine, no cloud in the middle.

Down the tree. Every worker a session spawns is addressable through hub: peek reads the last few steps of its transcript without spending its attention, ask poses a question and waits for the answer, send drops a note, steer changes its course, pause stops it while keeping it resumable, cancel ends it, and resume relaunches a stopped, paused, or settled child against its own transcript, including after the parent process has restarted.

Across sessions. Two lop sessions you started yourself, in different repos, with no parent between them and no shared context, can message each other directly. One can tell another to hold off on a deploy, hand over a finished branch, or claim a shared resource, without you relaying it between terminals.

A lop session receiving an inbound peer message card from another session named 'Audit custom fields on profiles E2E' (pid 50793), which announces it is taking the user-dashboard QA and prod deploy slot for MR !1356 and asks the receiver to object now if it has an in-flight QA validation; below it the receiving session's own send tool card replies 'No objection — go ahead', followed by its wait, bash, and hub peek receipts

Two independent sessions negotiating a shared deploy slot. One claims it and asks for objections; the other checks its own in-flight work and clears it. No human in the loop.

lop sessions is the directory of every session on the machine: state, pid, kind, conversation, model, memory footprint, uptime, and heartbeat age (--json adds cwd and session_id). From a shell you use lop send; from inside a session the agent uses its own send tool, which lands as an auditable card in its transcript. Both share the same three delivery modes and differ only in the default: the CLI drops to the mailbox unless you ask otherwise, while an agent’s send wakes an idle peer. --wake starts a turn if the target is idle; --now injects mid-turn to redirect a session that is actively working. A running bash, eval, or MCP call is never cut short, and a session parked in wait returns early to read the message.

lop sessions
lop send "release cutter" "gates are green, ready for review"
lop send "release cutter" --wake "the deploy finished; verify prod"
lop send "ingest refactor" --now "hold off, the schema changed"
lop send --pid 12345 "gates are green, ready for review"   # address by exact pid
git log -1 --stat | lop send "release cutter"      # body from stdin

Every delivery leaves a receipt on both ends: the target sees an inbound ↔ peer message from "<conversation>" (pid N, <model>) card, and the sender gets back how the message landed. Targeting is a case-insensitive substring over conversation name, session id, and cwd basename, and it refuses to guess: an ambiguous match lists the candidates and exits non-zero rather than delivering to the wrong session. The trust boundary is your OS account: every session publishes a 0600 discovery record under a 0700 directory and answers on an authenticated loopback server, so there is no remote or cross-user path. The full targeting and refusal rules, the receipt strings, and the limits (256 KB bodies; headless exec sessions may not receive) are in the packaged peer-messaging guide.

🛰️ Agent Mesh (your own devices)

lop network pairs your machines into a mesh: a group of devices that trust each other by a shared secret, each with a keypair of its own. Pair once, and the sessions running on those devices become one list you can read and act on from any of them — the desktop you work at, the laptop upstairs, the small box in the cupboard. Another device’s sessions appear in the sidebar under their own heading, marked ⇄, and you can start, list, warm or stop a session on a peer without leaving the machine you are sitting at.

The mesh is peer-to-peer: your devices dial each other directly, with no service in the middle, and the relay that coordinates them runs on your own machine (under a LaunchAgent on macOS). The trust boundary is the network itself — a device you paired — and pairing is a single-use token plus a code two people compare on two screens.

The Local Operator sidebar: another device's two sessions, both running on it, grouped under a heading that names the device; each row carries the locality mark ⇄ and its own live mark, and the cursor row shows its caret alongside both. Above them sit the pinned and active sections of this device's own sessions.

A device in a mesh: another machine's running sessions carry the ⇄ mark and group under it, beside this device's own pinned and active sessions. Captured from a real two-device mesh — the rows come from the peer's own listing, over a live link, not from a fixture.

The /network screen: this device's name and abbreviated id, the networks it is in with their role and member count, its peers with reachability and the reason for any that did not answer, and the relay's install and running state

/network: this device, the networks it is in, which peers answer, and whether the relay is running.

Getting started. On the device that should own the network:

lop network init devmesh                 # create the network, mint this device's identity, start the relay
lop network invite --role drive          # a single-use token, written to a file, never printed into a pipe

It prints where the token went; hand that file to the other device out of band and, there:

lop network join @/path/to/token         # both devices show a code; a person compares them, then confirms
lop network status                       # installed, identity, relay, and this device's links
lop network ls                           # devmesh  n_2bf6b70bd36b1cca228daa9f  epoch 1  admin  1 member(s)  active

invite --print reads the token straight off a terminal when you would rather copy it than move a file. Roles are read, drive and admin; a token can be bound to one device and given a shorter life. The pairing did work when lop network peers names the other machine — it is the live answer, not the stored one — and lop network doctor is what to run when a device that should be there is missing, since it reports reachability, epochs and identity per link. The full walk-through — including what to do when a device is unreachable, and the incident verbs (panic, disconnect) — is in the packaged network guide.

From inside a session. The mesh is a slash-command family, so you never leave the composer:

/network               this device's networks, peers and relay state, as a screen
/network peers         which devices answer right now
/network sessions --all-peers  what other devices are running: list, engage, stop
/network approvals     onboarding cards: list, show, approve, deny
/network status        relay health and the log path
/network log           the recent mesh event trail

The pairing verbs are here too — /network new <name> creates the network and /network invite mints the token — and on a device that has never paired, /network on its own opens a screen whose empty state names the whole path in order: create, invite, join. The joining half is the one step a TUI cannot host. Pairing shows a code on each device for a person to check across, so it runs in a terminal, and /network join typed into the composer says exactly that instead of half-starting a pairing that cannot finish.

Running a session on another device. /new remote creates the session on the peer, with a trailing sentence as its first prompt:

/new remote radiant-m4 rebase the auth branch and run the test suite

The /new device picker: one row per paired device, each showing the device's name, the network it is in and the role it holds there — radiant-m4 in devmesh as admin, an unnamed device as drive, and pixel-8 in homelab as read — while the composer above previews /new remote radiant-m4.

/new lists the devices you paired: the row carries the whole remote <peer> argument, so the first Enter fills a command that runs, and the composer preview always shows exactly what will be sent. The splash's version line is the build this frame was captured from — a development worktree — and not the version of the release this page documents.

The session is minted, spawned and admitted by that device, and the receipt in your transcript names it. /network sessions --peer radiant-m4 then lists what that peer is holding, and --engage <session> warms one or --stop <session> ends it — so a session started this way is visible and controllable from the machine you are sitting at, without a second terminal. The same act is one command from a shell, which is how a script or another agent asks:

lop network sessions --peer radiant-m4 --create --name "auth rebase" \
  --prompt "rebase the auth branch and run the test suite"

Whichever front end starts it, the session is the peer’s: it appears in that device’s own lop sessions, it shows up here under the ⇄ heading for that device, and piloting it is what piloting a local session is — read the transcript, send a turn, watch it work.

Moving a session between devices. Work that has outgrown the machine it started on, or work you want back in front of you, moves with its id and its transcript:

lop sessions move 9f3ac1e0b7d2 --to build-box          # hand it to the peer; the copy here is retired
lop sessions move 9f3ac1e0b7d2 --to local              # bring a remote one home to this machine
lop sessions move 9f3ac1e0b7d2 --to build-box --keep   # copy it and leave the original running — a new id, marked as a fork

The device that will hold the conversation is the one that issues the move, so --to <peer> is this machine asking the peer to pull and --to local is this machine pulling; there is no push verb. A session with a turn in flight is refused rather than interrupted, and --wait re-checks a busy one every five seconds; --queue instead records the move to run by itself at the conversation’s next safe point (lop sessions move --cancel-queued <id> calls it off before it starts). Inside the TUI the same act is /move <id> --to <peer|local> [--keep] [--queue]. lop sessions sync <id> keeps this machine’s copy of a conversation a peer owns up to date without opening it, and lop sessions move <id> --to local --from-replica recovers that copy as a new session for when the device that held it is gone.

The desktop app's Mesh tab with a peer's device panel open on the right — cloud-node-1's memberships, its conversations and an Invite to a network button — the whole tab dimmed behind a scrim, and over it the 'Recall to this device' dialog, whose subtitle reads 'The copy on cloud-node-1 is deleted once this device has it.' above a selected 'Recall to this device' choice and a 'Copy here, leave it there' alternative

Recalling a conversation from a peer, and the --keep distinction in a single dialog: the recall deletes the copy on the device the conversation leaves — the subtitle names it, cloud-node-1 — while the other choice copies it and leaves the original running. The tab behind the dialog sits under a scrim (measured: about 40% of its brightness), so the choice is the only live thing on screen. Fixture-driven like the canvas below — the real page over local-operator-ui's own fixtures, not a live mesh.

Credentials are brokered, not copied. lop network credential share <provider> --with <device> lets a peer borrow a login this device holds, for a bounded grant (network.credentials.grant_ttl_s, fifteen minutes by default), and lop network credentials shows who owns what and what this device borrows. A refresh belongs to the device that owns the credential — it lends a short-lived access token and never its refresh token, which is why a token is never refreshed on a device that does not own it. kimi is the one provider that can never be lent: its grants are signed with the fingerprint of the device that made them.

The desktop app has its own view of it. local-operator-ui mounts a Mesh tab beside the sessions it lists: the networks this device is paired with, the devices in each, and what each of them is holding. The tab appears once this device is in a mesh — a device in none shows no Mesh row at all, so pairing comes first.

The desktop app's Mesh tab: a summary line reading '1 network · 2 devices · this device is damians-MacBook-Pro' above a canvas whose damian-mesh network node joins by one edge each to cloud-node-1 (4 conv · seen 4m ago) and to this device, marked 'this device' and 'no conversations here', with a Canvas/List toggle at the top right

The mesh in the desktop app: the networks this device is paired with, the devices in each, and what each of them is holding. It is also where a move is made by hand — a conversation is dragged from one device's node onto another's, which a still cannot show, and the dialog above is what that drag asks before it commits. From local-operator-ui's committed Mesh-tab evidence set — the real page over that repository's own fixtures, so the shapes are the backend's and the values are not a live mesh.

Nothing above documents a command that does not run today.

The design set behind all of it is in docs/design/mesh-network.md — the spine, with its requirements and, for each one, the document that owns it — alongside mesh-transport-identity.md, mesh-session-mobility.md, mesh-credentials.md, mesh-incident-response.md, mesh-compute-pool.md and mesh-ui.md.

🌙 Always On

Work keeps moving after you walk away.

  • Scheduled wakes that survive a closed terminal. The agent’s wake tool schedules a future turn (“check the build again in 30 minutes”). Schedules persist with the session. On macOS a small wake supervisor (installed on demand as a LaunchAgent the first time a wake is scheduled) starts a runtime for whichever session’s wake is due, so a wake fires even if you closed the terminal that scheduled it (a session that is still open fires its own). On Linux and Windows there is no supervisor: a wake fires the next time that session is running. If a wake fires unattended and a tool needs approval, the turn stalls at the prompt until you answer from the phone or /resume; /approvals auto or --yolo opts into unattended execution. A session that was asleep past a due time fires the wake late and reports how many occurrences it skipped, rather than replaying six hourly checks at once. lop wake status reports whether the supervisor is actually running (not merely installed), the soonest wake that will fire, and how many are overdue, dormant, too stale for the supervisor to keep retrying, or blocked by a wedged runtime holding the session lease; lop wake list shows every schedule with that state per row. When the supervisor has stopped or gone missing, lop wake install puts it back. Human-readable wake times use the machine’s local timezone, labelled explicitly, with a 12-hour AM/PM clock by default. Other dates include the month/day (and year when different). Choose Wake time format in /settings → Appearance, or run lop config edit display.time_format 24h for a 24-hour clock (12h restores the default). This changes display only; stored timestamps and JSON output remain epoch milliseconds, and existing transcript confirmations are not rewritten.
  • Background jobs that report back on their own. task always runs in the background and bash can (background=true); a long command interrupted by a steer detaches instead of dying; and every settled job auto-delivers its result when the session is idle. jobs peeks at new output since your last look.
  • lop exec --background detaches a whole task with a log file and exits immediately.
  • Waiting without polling. wait blocks up to 60 minutes and returns the moment a job settles, a message arrives (a peer’s send, a wake, a subagent’s hub note), or you steer. One sized wait replaces a chain of short polls that each re-send the whole context.
  • Resume after a restart. Transcripts persist and /resume picks a session back up; a paused or settled subagent can be resumed with hub op='resume' after the parent process has restarted.
  • A daemon that keeps sessions reachable. lop mobile install runs a supervised session daemon (LaunchAgent on macOS; on Linux and Windows the daemon is portable and foreground-runnable, with no installer yet); every interactive session registers with it, and you watch, steer, and start sessions from your phone. See Phone Access. lop update restarts the daemon when the LaunchAgent is installed.

The desktop app's Schedules page listing recurring wakes by conversation — Invoices workspace (a nightly ledger sync and a morning report), Release crew ops (a nightly CI queue check), Standup notes, and Weekly finance digest — each with its cadence, run count, and next fire time, above the page's note that wakes fire whether or not the window is open.

The Schedules page: the recurring wakes a workspace runs, and the desktop app's reminder that they fire whether or not the window is open.

💳 Subscriptions

Sign in with the accounts you already have. lop login lists every login-capable provider; the ids and plan requirements:

You pay for Type Plan needed
ChatGPT lop login openai (openai-device on a headless box) ChatGPT Plus/Pro
Claude lop login anthropic Claude Pro/Max
Kimi lop login kimi (international kimi.ai), or lop login kimi-wechat (mainland/WeChat kimi.com) Kimi (Moonshot)
Meta Muse Code (unofficial) lop login meta-muse Active Muse Code plan; billing/privacy limitations
Grok lop login xai-oauth (xai = API key) Grok OAuth
Z.AI / GLM lop login zai-oauth (zai = API key) GLM Coding Plan
Qwen lop login alibaba-token-plan-oauth QwenCloud Token Plan (a personal plan also needs the console ticket for /usage)

OpenAI, Anthropic, Z.AI, and Qwen logins are keyed by account identity, so a second login adds to the pool; Kimi holds one account (xAI identity is best-effort). API keys and local servers sit alongside.

What this means for you. With one account per provider, Local Operator uses that account’s plan quota (no API bill) and, when it is rate-limited, falls back to the next model you listed. With two or more accounts on the same provider, it spreads your sessions across eligible siblings using cached quota evidence. A compatible, expiring lease from a successful request keeps a warm account preferred; after expiry the policy reselects even in the same conversation. This is a local routing hint, not a guaranteed provider cache hit.

  • Accounts on one provider form a rotation pool. The default retry.accountSelectionPolicy: reset-soonest prefers the nearest fresh weekly reset, five-hour reset within one-minute ties, then stable spreading. Unknown reports stay neutral; ranking makes no extra blocking probes. headroom and fair retain alternative preferences within the same credential tier, never cross-provider or paid-tier promotion.
  • Failover follows a fixed order. On a quota, auth, or 5xx failure the request rotates to a sibling account first. Only when the pool is spent does it walk your model fallback chain (retry.fallbackChains in config.yml), backing off between attempts. /failovers prints the cascade and marks which account is serving right now.
  • Pinned subagents descend the chain, cross-vendor only as the last resort, and say so. A subagent launched on a resolved model (a role’s effort tier, or a resumed child) holds that pin until its own model cannot serve; the walk then goes family-first and may land on another vendor’s model, announced on the child’s row, the parent’s notice and the completion record (pinned X, ran on Y). The /settings row is Pinned child fallback — allow cross-vendor (cross-family) by default; same family only (same-family) restores the strict refusal.
  • Success-derived account affinity. Every policy honors a compatible, unexpired local account lease before ranking. Resolve/preflight/inspection never renews it; failures, cancellation, empty streams and isolated errands do not renew it either. Expiry is request-start plus the actual wire lifetime or an explicitly bounded endpoint heuristic. Resume preserves the original expiry, not a new lease. Low-but-usable accounts retain a valid lease; hard depletion, own failure, incompatibility or expiry can reselect. Changing policy leaves unrelated model/effort fallback pins alone.
  • Opt-in proactive switching. retry.usageAwareFallback spends one lightweight quota request per user message to leave a provider before it fails, with retry.usageReservePercent (default 10) as the headroom floor.
  • Everything is visible in-app. /usage shows each provider’s quota windows and account spend; /accounts lists every signed-in provider account (the lop secret store is separate); /session reports the current session’s cost, cache, and request diagnostics, and /session --copy copies the session ID. One exception: QwenCloud’s personal Token Plan window needs a console ticket stored alongside the login.

The /usage panel showing per-provider quota windows and account spend

The packaged failover guide explains the routing in full, including how to change the order.

🧮 Built for Token and Cache Efficiency

Long-running agents spend most of their tokens re-sending context, so the harness treats the provider prompt cache as a resource to be managed:

  • A stable prefix. The tools array is built in a deterministic order and rides in the same cache prefix as the system prompt, so the provider can reuse it turn after turn. Most extra capabilities live behind gated tools, skills, or MCP instead of sitting in every prompt.
  • Cache-aware pruning. Superseded tool outputs (an older read of a file that was read again, a zero-match search) are blanked in place without forcing a cache rewrite. Once a session has idled past the cache TTL, the cache is provably cold and everything eligible flushes at once.
  • Automatic 1-hour cache TTL. At or above 150k context tokens, Anthropic requests switch from the 5-minute to the 1-hour cache window; tunable via providers.anthropic.cache_ttl_1h_min_context_tokens (0 disables it).
  • Compaction before overflow. Context compacts itself before the window fills; /compact runs it on demand and /context reports what is occupying the prefix right now.
  • Lazy everything else. Skills are indexed semantically and their bodies load only when read; MCP servers advertise a bounded summary and individual tool schemas enter the context only when enabled; wait returns on job settle or message arrival so a long job costs one round trip, not twelve.

🖥️ A Tour of the Terminal UI (TUI)

The TUI is a full-screen Textual app, not a REPL. Everything the agent does shows up as a card or a one-line receipt: tool cards expand (enter/space) to show the full command and output, and the status line tracks the current step, token usage, and cost. Inside a terminal multiplexer (tmux, wezterm, cmux, and others), lop publishes a per-pane crash-restore binding (multiplexer-resume.md) and, in a Herdr Agents panel, reports whether it is idle, working, or blocked (herdr-agents.md).

Local Operator TUI running a real task: streamed response, an expanded tool card showing a command and its output, and a live status line

One session mid-task: streamed responses, expandable tool cards, and one-line receipts for everything the agent does.

When a tool call needs your sign-off, the approval prompt shows exactly what is about to run before anything touches your system:

An approval prompt showing the exact shell command awaiting user confirmation

Switching models goes through a picker rather than a config file. /model lists every model your signed-in providers offer, with fuzzy filtering. ChatGPT OAuth uses the account’s supported maximum context by default while keeping the provider default visible; context limits and the opt-out explain how without changing your compaction settings.

The /model picker with a fuzzy filter applied, showing context length and pricing per model

Coming back later is /resume, a picker over your recent sessions, each with its title and age:

The /resume session picker listing recent conversations with titles, ages, and short ids

Slash commands

/help shows the full table in-app. The highlights:

Command What it does
/model Switch model for this session; /model default saves the current one for new ones, /model saved reverts to it (/settings edits the boot default too)
/effort Show or set reasoning effort (shift+tab cycles; save a level for new conversations with /model default)
/fast Toggle fast mode where the provider sells one: the same answer sooner, at premium pricing
/approvals Set whether tools ask first (ask/auto; add default to keep it)
/resume Pick a past conversation and continue it
/new, /clear, /reload Fresh conversation · wipe the screen · relaunch this conversation on the current install
/update Install the latest version from PyPI and relaunch
/goal <text> Set the session objective and send the same text to start work. A judge checks it at each turn end and continues or marks it done. Bare /goal opens the goal card (other hosts print a one-line report). /goal --done marks it done, --dismiss clears a done goal, --history lists settled goals, and --clear deletes it without a history entry. None of these start a turn (/goal clear, none and reset still work)
/loop Iterate autonomously toward the session objective: /loop <n> for a bounded count, /loop <goal> toward an inline goal; /loop --stop cancels a running one and /loop --clear clears it — which on a detached owner means dismissing a finished run’s published state
/btw Ask a side question off the record; it never joins the conversation
/compact Compact the context now (it also happens automatically)
/usage, /context Provider quota and account spend · what’s occupying the context window
/session Current-session recorded usage, combined cost, cache, and request diagnostics; /session --copy copies the session ID to the clipboard
/failovers The model cascade for this session, and which account is serving
/provider, /login, /logout, /accounts Manage providers and signed-in accounts
/credential Hand over a secret: type it after /credential and a space to have it masked and captured; a paste your terminal delivers as text is captured the same way; the composer’s own paste key (Ctrl+V on macOS) is not. Bare /credential lists this session’s credentials, and /credential --persist <KEY> saves one to the long-term store. See Credentials and secrets
/search Configure web-search providers and load balancing
/team Launch a saved team: /team <name> <request>; /team chart <name> draws its org chart
/skills, /mcp List loaded skills · MCP servers
/theme, /rename Pick from 20+ built-in themes (arrows preview live) · rename the session

/reload and /update can replace the terminal while its detached session runtime keeps working. The new terminal reattaches to the same saved conversation, including streamed output and unanswered approval or ask prompts. The runtime adopts installed code only when all work is safely idle: active turns, tools, compaction, gates, loops, child agents, background jobs, and imminent wakes defer that refresh. Reloading an unchanged build does not restart the runtime. Local in-process work and terminal-owned ! shell commands must finish first. A failed update leaves the current terminal and runtime running.

Keys worth knowing

  • $<skill>: run a named skill on the rest of the line ($research the payments rewrite); $ alone opens the picker.
  • Type while the agent works: your message is delivered at the next step as steering, no need to wait.
  • esc — stop the agent without ending the session.
  • ctrl+b or /sidebar — show or hide active and recent conversations in the same TUI. cmd+b is also accepted when the terminal forwards the Super modifier. /settings → Session sidebar and Sidebar position control visibility and left/right placement; the defaults are hidden and left. f9 or /sidebar focus opens and focuses the list without changing your draft. Arrow keys choose a row, Enter opens it, and Escape returns to the previous input surface. F9 also returns focus without hiding a pinned list. ctrl+shift+↑ / ctrl+shift+↓ switch straight to the previous/next conversation in the list’s order without opening it, wrapping at the ends; the caret and your unsent draft stay where they were. On narrow terminals, selecting a conversation dismisses the overlay drawer.
  • f8 — open an aside (side question) without losing what you were typing; ctrl+f promotes the aside into the conversation.
  • shift+tab: cycle reasoning effort.
  • ctrl+l: clear the transcript (history is untouched).
  • ctrl+t / ctrl+g: expand the todo list · cycle the subagent panel.
  • Hand over a secret: /credential followed by a space opens a masked capture: type the value and Enter turns it into a chip, and a paste your terminal delivers as text (Cmd+V on macOS) is captured the same way. The composer’s own paste key (Ctrl+V on macOS) never consults the capture: it reads the clipboard itself, so it inserts the value as ordinary text.
  • option+← / option+→ (ctrl+← / ctrl+→ on Linux and Windows): move the caret a word at a time in the composer; add shift to select by word. Works the same in shell (!) mode and with a command list open, and option+↑ / option+↓ behave as plain ↑ / ↓. On macOS this works whichever option-key mode your terminal is set to. The default, “Use Option as Meta”, and “Esc+” all behave the same, so there is nothing to configure.

🔌 Providers

Every provider below runs on the same harness and the same model picker. OAuth providers sign in through the browser and use your existing subscription; API-key providers prompt once and store the key locally. Local servers can be configured in the app with /login, including endpoints and optional masked API tokens. See Local and self-hosted providers for setup, metadata overrides, desktop-app support, and server-specific limitations.

Provider Access
OpenAI / ChatGPT openai / openai-device, or OPENAI_API_KEY
Anthropic / Claude anthropic, or ANTHROPIC_API_KEY
Kimi (Moonshot) kimi, or KIMI_API_KEY
Meta Muse Code (unofficial subscription) meta-muse device approval; no PAYG-key fallback
xAI / Grok xai-oauth, or xai / XAI_API_KEY
Z.AI (GLM) zai-oauth, or zai / ZAI_API_KEY
Qwen (Alibaba) alibaba-token-plan-oauth, or API key
Google Gemini GOOGLE_AI_STUDIO_API_KEY
DeepSeek DEEPSEEK_API_KEY
Mistral MISTRAL_API_KEY
OpenRouter OPENROUTER_API_KEY: one key, many models
Radient RADIENT_API_KEY: automatic per-step model selection
LM Studio, Ollama, vLLM, llama.cpp User-operated server; optional token
OpenAI-compatible Explicit server URL; optional token; MLX/LocalAI/proxy escape hatch
lop login              # list login-capable providers
lop login openai       # OAuth flow; for OpenAI/Anthropic/Z.AI/Qwen, repeat to add a second account
lop login-status       # what's signed in
lop logout kimi

Legacy --hosting <name> --model <name> flags keep working, and API keys can be stored with lop credential update <KEY_NAME> (a masked prompt); the lop secret store, and the /credential hand-over that keeps a secret out of the prompt text, are covered in Credentials and secrets.

🧰 What the Agent Can Do

The agent’s built-in tools, each with its own card in the transcript:

  • Run things: bash (shell commands), eval (a persistent Python kernel: variables survive across calls).
  • Work with files: read, write, edit (surgical search/replace), glob, grep, plus lsp for Jedi-backed Python code intelligence.
  • Reach the web: load-balanced web_search across eight providers, web_fetch for reading pages headlessly, and a browser tool for pages that need rendering, a login, or interaction.
  • Stay organized: a visible todo list for multi-step work, ask to put real decisions back to you as a picker instead of a wall of text.
  • Handle secrets: the secret tool stores, lists, updates and removes entries in the encrypted long-term store, and hands a value to bash or eval without ever returning it to the model. See Credentials and secrets.
  • Run an organization: task spawns subagents, hub talks to them, jobs/wait manage background work, send reaches other sessions, agent and team author profiles and rosters, and wake schedules future follow-ups.

Search works out of the box. The chain walks free legs first and metered legs last: the free providers you list, then the credential-free providers this install can reach, then the best-effort ones, then anything that would spend money or a model turn — including a paid provider you listed, which is tried first among the paid legs and never before a free one.

lop search list
lop search test "Python 3.13 release notes"
lop search enable perplexity          # clears an exclusion
lop search disable brave             # excludes it from every search
lop search order duckduckgo tavily   # the priority prefix: free legs first, paid ids run after them
lop search setup brave --api-key
lop search setup tavily --oauth      # official Tavily MCP server
lop search setup searxng --endpoint https://search.example.com

web_search.providers is a priority prefix, not an allowlist, and its authority is over order within a band: a named free provider joins the free pool where you put it — and with the default round_robin that pool is rotated, so “where you put it” is its position in the pool rather than a promise it is tried first on every call (search balance ordered is the strict version). Every other usable provider joins automatically behind the ones you named. web_search.excluded_providers is the only way to say never — an id there is skipped in the prefix and in the automatic bands. A provider that becomes usable mid-session (a DeepSeek login, an Exa key) joins its band on the next search with no further configuration; lop search list states each provider’s state, so the chain is never a guess.

Listing a provider whose transport would spend — a key, or a model turn — does not put it first. It moves it to the head of the paid band, so every free leg is still tried before it: a paid provider listed ahead of a free one is tried first among the paid legs, and lop search list names it as paid rather than as a plain enabled. That keeps the free-before-paid rule absolute, and search list’s chain: line shows the resulting order.

round_robin spreads the first attempt across the whole free pool — the listed free providers in their listed order, then the credential-free providers that joined automatically — and never across a band boundary, so nothing that spends can be rotated (or listed) ahead of a free leg.

Provider Access Default
DuckDuckGo Credential-free Priority prefix
Tavily Keyless, TAVILY_API_KEY, or OAuth MCP Priority prefix (keyed: paid band)
Exa Keyless MCP (no key needed), or EXA_API_KEY Automatic (free; keyed: paid band)
Parallel Keyless MCP (no key needed), or PARALLEL_API_KEY Automatic (free; keyed: paid band)
Perplexity Anonymous or PERPLEXITY_API_KEY Automatic (best-effort; keyed: paid band)
DeepSeek Model login or DEEPSEEK_API_KEY Automatic (paid)
Brave BRAVE_API_KEY Automatic (paid)
SerpApi SERPAPI_API_KEY Automatic (paid)
SearXNG Self-hosted endpoint URL Automatic (free, with an endpoint)

The same controls are available in-app via /search.

🧠 Skills and guides

Drop a SKILL.md (with optional reference files) into ~/.local-operator/skills/<name>/ and the agent indexes it semantically. Only the skills relevant to the current turn are surfaced, and their bodies load on demand via skill://<name> reads, so your context isn’t taxed by knowledge you aren’t using. /skills lists what’s loaded.

Skills can also live in grouped libraries: ~/.local-operator/skills/<group>/<name>/SKILL.md is discovered too (up to 3 levels below a skills root by default; set LOCAL_OPERATOR_SKILL_MAX_DEPTH to 1-5 to change it, 1 = flat only). A directory that holds a SKILL.md is a skill boundary and is never searched further, so a skill’s own references/ or scripts/ folders never register as skills.

You can also invoke one by name instead of leaving the choice to the router: type $ in the composer to pick from your skills, then write the request: $research compare these two API designs loads that skill and sends it with your message. A skill marked hide: true is excluded from automatic routing and is reachable this way only, which makes $ the home for “never pick this on your own, but run it when I say so”.

🔗 MCP servers

Local Operator speaks MCP over the official SDK, with lazy tool loading: servers advertise a bounded summary, and individual tool schemas enter the context only when the agent actually enables them.

lop mcp add linear --url https://mcp.linear.app/mcp --oauth
lop mcp login linear     # complete the OAuth flow
lop mcp list

Server configs are discovered from the project (.local-operator/mcp.json, .mcp.json), your home directory, and best-effort imports of Claude Code, Cursor, VS Code, and Codex CLI configs, so servers you already configured elsewhere are available without re-declaring them. See docs/mcp.md for the trust model before enabling project-supplied servers.

⚙️ Headless & Server Modes

Headless sessions for scripts, saved teams and goal loops:

lop exec "summarize the failures in ./test.log"
lop exec "long migration" --background   # detach with a log file
lop exec "audit deps" --json             # one JSON line per event
lop exec "review the change" --team release --background
lop exec --goal "Finish the checklist" --loop 3
lop exec --status JOB_ID                 # durable outcome and session ID
lop --resume SESSION_ID                  # live TUI attachment or cold resume

Foreground exit code 0 means success, so lop exec composes in a pipeline. Background launch prints a readiness receipt; use --status for the eventual outcome. Saved teams, reusable profiles, goals and conversation names persist with the ordinary session. Non-TTY approvals still deny unless explicitly changed; --control opts into supervisor gates, not automatic approval. See the full exec guide for option combinations, stdin, resume precedence, loop counting, approvals and lifecycle semantics.

Server mode exposes the agent as a FastAPI service (used by the optional desktop UI):

pip install "local-operator[server]"
lop serve                 # http://127.0.0.1:1111, docs at /docs

The legacy HTTP API has no authentication and defaults to loopback. Keep it away from untrusted clients; an explicit --host can widen the bind only when you provide access controls in front. This is separate from the authenticated mobile relay. See API filesystem boundaries for fresh-ID profile imports, workspace-confined edit reads and live editor buffers.

📱 Phone Access (Mobile Relay)

lop mobile turns the machine you run agents on into a phone-facing control plane for every lop session on it. A single supervised session daemon owns the web surface, and every interactive TUI session registers with it automatically over an authenticated loopback socket. From your phone you can watch transcripts stream, steer a running turn, switch model and effort, run slash commands, drill into subagents, and start brand-new sessions. Sessions you start from the phone also answer their own approval and ask prompts there; for a terminal session those prompts are still answered at the terminal (the phone shows that it is waiting).

The Local Operator mobile relay open on a phone: a live session transcript with streamed assistant text, one-line tool cards with state glyphs and durations, a tasks counter, and the mobile composer with steer, stop, and send controls

A live lop session driven from a phone: the same transcript, tool cards, and composer as the TUI.

You can ask Local Operator to set this up for you. Tell the agent something like “set up phone access” and it will walk through the install, confirm the health check and the closed auth gate, and get you the portal password through a channel you choose (or leave it in the Keychain for you to retrieve). If you would rather do it by hand:

lop mobile install      # generate/keep the portal password, install the daemon, verify health
lop mobile status       # install state, health probe, and registered sessions
lop mobile password     # show or rotate the portal password
lop mobile logs -f      # follow the daemon's and the runtimes' logs

Once the daemon is up, every interactive lop you start publishes itself and shows up in the phone list live. No extra flag per session.

Additional setup is required for remote access

The daemon binds loopback only (127.0.0.1:4098) and never a wider address. That keeps it private by construction, so reaching it from your phone over the internet needs a secure path you put in front of it, together with an identity proxy so only you can open it.

The recommended method is a Cloudflare Tunnel with Cloudflare Access in front: the tunnel gives the daemon a public hostname without opening a port on your machine, and Access enforces sign-in before any request reaches loopback. A WireGuard-based mesh such as Tailscale is a good alternative if you would rather keep everything on a private network. Either way, do not change the bind address to expose the daemon directly; put the tunnel and the identity proxy in front of the loopback listener instead.

The portal itself is protected by a single password (Keychain-backed on macOS, or LOP_MOBILE_PASSWORD for containers), and session cookies are derived from it, so rotating the password invalidates every logged-in phone.

🌐 Drive Your Own Browser (Browser Extension)

The browser tool can drive a cmux panel, a macOS terminal built for running agents side by side. On any Chromium browser (Chrome, Edge, Arc, Brave) the free Local Operator browser extension gives the agent the same capability against the browser you already use, with your real logins, entirely on your machine. A small loopback bridge daemon connects the extension to your lop sessions; nothing leaves your computer, and the agent can only open sites you approve.

You can ask Local Operator to set this up for you, or do it by hand:

lop browser install     # install and start the loopback bridge daemon
lop browser status      # daemon health, whether a browser is attached, pairing state
lop browser pair        # print the 6-digit code to type into the extension popup

Then install the extension from the Chrome Web Store (or build it from extension/ in this repo with pnpm -C extension build and load extension/dist unpacked at chrome://extensions with Developer mode on), click its toolbar icon, and enter the pairing code. Once paired, browser tool calls drive a dedicated tab in your real browser. The first time the agent wants a new site, the published store build asks Allow once, Always allow, or Deny; you stay in control of which sites it can reach, and can revoke the whole browser any time from the extension’s Settings or with lop browser pair --reset.

The bridge daemon binds loopback only and is pinned to your extension by the pairing code; a compromised page cannot reach it, and a revoke drops any live connection within seconds. Each store release is recorded in docs/store/release-record.md.

📦 Installation Options

The default install i</key_name>

README 内容过长,已截取前 64 KB。完整内容请前往 GitHub 仓库查看。

评论

暂无评论