ᨒᨚᨄᨗ — lopi: Konjo for "boat." Load it, sail it, trust it, bring it home.
Multi-agent Claude Code orchestrator, written in Rust. Runs concurrent agents in git-isolated branches, with retry loops, persistent memory, a TUI + web dashboard, and a native macOS app.
By KonjoAI · MIT licensed ·
v0.36.0
lopi run # queue and run a task
lopi watch # live TUI of in-flight agents
lopi sail # start the web dashboard (the Forge)
lopi tail # stream logs
lopi dock # list every task
You give lopi a goal — a failing test, a CI red, "fix the lint warnings in
src/auth/." lopi hands it to a Claude Code agent, isolates the work on its
own branch, and runs it through a scored loop: Plan → Implement → Test →
Score → Retry → PR. Multiple agents run concurrently, each sandboxed to its
own git branch and worktree, with automatic rollback if anything goes wrong.
It's the Rust successor to the OpenClaw-style single-agent Python prototype
— rebuilt for concurrency, safety, and persistence. See
LOPI_VS_OPENCLAW.md for the full feature-by-feature
comparison.
Runs on your existing Claude subscription — for the worker itself. lopi
drives the official claude CLI as a subprocess (claude -p) and
deliberately scrubs any inherited ANTHROPIC_API_KEY/ANTHROPIC_BASE_URL
env vars before every spawn, so the CLI always falls back to your on-disk
~/.claude subscription credentials rather than silently billing an API
key. No separate API key is required to plan, implement, or fix.
As of Sprint F1, that's also true of the checker layers. The verifier
(crates/lopi-agent/src/runner/verifier_runner.rs), LLM-judge acceptance
(runner/eval_runner.rs), and post-mortem (runner/postmortem_runner.rs)
all pick a backend automatically — a direct-API AnthropicClient
(Runner::with_api) when one is configured, the claude CLI otherwise
(crates/lopi-agent/src/verifier_cli.rs,
crates/lopi-agent/src/runner/postmortem_cli.rs). Nothing in the built
binary calls with_api (grep -rn "with_api" crates/lopi-orchestrator/ src/ still comes back empty), so the CLI backend is what every real
deployment actually runs on — subscription auth, no separate API key,
same as the worker. A checker session runs read-only (Write, Edit,
MultiEdit, NotebookEdit, Bash, and sub-agent/plan-mode tools all
denied — see .konjo/killtests/F1/KT-1.2.md) and as a fresh, never-resumed
session, so it never sees the worker's own reasoning unless a caller
explicitly opts into that (VerifierAgent::with_plan_context).
A checker session does not currently pass --bare — a live test in
this sprint's own sandboxed session found --bare fails authentication
outright there (.konjo/killtests/F1/KT-1.3.md), a stronger reason than
"needs project context," and one that has not yet been re-verified on a
plain local install. Until that's confirmed, a checker session loads
hooks/CLAUDE.md/skills like a normal session, which costs somewhat more
per grading pass than the brief's original --bare design assumed.
Two things sprint F1 explicitly did not ship, both gated on
measurement the brief requires and this session couldn't run (see
Requirements for why — the same attended, hardware-required
corpus run Sprint F0's Phase 3 already deferred): a cheaper two-tier
checker (cheap first pass, Opus escalation only on low confidence), and
--append-system-prompt reaching worker sessions. Both remain tracked in
NEXT_SESSION_PROMPT.md.
The one exception to all of the above is the Layer 5 stability gate:
lopi run --stability-gate wires its own, separate AnthropicClient from
ANTHROPIC_API_KEY at the CLI layer (src/run_command.rs) and requires a
real API key for that specific gate. (A couple of standalone server
components — see Configuration — can optionally take a
direct Anthropic API key for their own use; that's unrelated to the above.)
- Concurrent, git-isolated agents — an
AgentPoolruns N agents at once (default 4), each on its ownlopi/<task_id>-attempt-<n>branch and worktree. Off-limits path globs and automatic hard rollback on any scope violation keep runs contained. - Scored retry loop — a weighted composite score (tests + lint + a capped per-1000-line diff-size penalty) decides accept / retry / rollback. Failed attempts feed a SQLite pattern library so later re-plans are seeded with what already failed. Optional Reflexion-style adaptive retry and a Layer 5 stability gate (variance-checked plan sampling) are available per run.
- Granular budget controls — per-run USD caps (
--budget), named presets (quick/standard/deep/unlimited), token budgets, and a repo-level.lopi/loop.toml— because an unwired budget is how a session turns into a bill you didn't expect. - Three UI surfaces — a
ratatuiterminal TUI (lopi watch), a SvelteKit web dashboard ("the Forge," served bylopi sail), and a native SwiftUI macOS app (inmacos/) that talks to the same REST + WebSocket API. - Remote control from your phone — the native macOS/iOS app talks to the
same REST + WebSocket API
lopi sailserves. (A Telegram bot previously filled this role; removed in Sprint S10, Phase 4 — seeLEDGER.md. A Twilio WhatsApp handler exists inlopi-remote::whatsappbut is not wired to any CLI command and is unreachable from the built binary today — seedocs/security/TRIFECTA_PATHS.md§1 row D and the CHANGELOG.) - Event-driven, not just manual — a GitHub webhook listener turns CI
failures into auto-queued fix tasks; cron-style schedules
(
[[schedules]]inlopi.toml, editable live from the dashboard) handle recurring work like nightly lint sweeps. - Ships into Claude itself — a Claude Code plugin (
lopi mcp-serve) and a Claude Desktop extension (MCPB,mcpb/) expose task submission and live stack status as MCP tools, with a clickable status widget inside Claude Desktop. - Self-improving —
lopi specextracts a repo's spec surface from its tests,lopi gap-fillturns coverage gaps into queued fix tasks, andlopi skill promoteturns recurring lessons into reviewableSKILL.mddrafts.
| To… | You need |
|---|---|
| Run agents at all | The claude CLI, logged into a Claude subscription (Pro, Max, Team, or Enterprise) — lopi drives it as a subprocess and never needs a separate API key |
| Build from source | A stable Rust toolchain (edition 2021; verified with 1.89–1.94) and git |
| Build the Forge web dashboard | Node.js 18+ and npm — optional; without it, lopi sail serves a placeholder page instead |
| Build the native macOS app | Xcode 15+ and XcodeGen, macOS 14+ (only needed for macos/) |
lopi itself runs on macOS or Linux; the native app is macOS-only. No database server, no Docker, no cloud account — everything (SQLite, git worktrees, the web dashboard) is local to the machine you run it on.
Via Homebrew (macOS/Linux):
brew install konjoai/lopi/lopiVia crates.io:
cargo install lopiFrom source:
git clone https://github.com/konjoai/lopi.git
cd lopi
cargo build --release
cp lopi.toml.example lopi.toml
./target/release/lopi run --goal "fix the failing test in src/foo.rs" --repo .Just want to look around first? lopi demo fabricates a complete,
synthetic dashboard — repos, tasks in every status, agent traffic, token
counts, a quality trend, a couple of honest failures — and opens it. No
config, no credentials, no real repo touched; every surface is watermarked
🧪 SYNTHETIC DATA.
lopi demoScreenshots above are from lopi demo --seed 42 — the seed is recorded so
they're reproducible byte-for-byte. Every demo surface — including the
GET /api/stats/GET /api/budget/breakdown JSON, the MCP widget payload,
and the TUI (lopi watch --demo) — carries the same non-dismissible
synthetic marker; see docs/MEASUREMENT.md.
Building the web dashboard is optional but recommended (cd web && npm install && npm run build) — without it, lopi sail serves a placeholder
page instead of the Forge UI (the Homebrew formula builds it for you; cargo install ships without it — build from source if you want the Forge UI from
a cargo install). See docs/RUNNING.md for the full
build/run guide, including the native macOS app.
New to lopi? Start with recipes/ rather than a blank
.lopi/loop.toml. Six ready-to-copy loop configs — fix-failing-test,
lint-burndown, dependency-bump, flaky-test-hunter, doc-drift-check,
triage-issues — each with a README explaining what it does, the simpler
thing it beats, and its measured cost/duration from a real run. Copy one
into .lopi/loop.toml, adjust it for your repo, run it.
lopi is a single binary. Bare lopi (no args) drops into an interactive
REPL. The full surface:
| Command | What it does |
|---|---|
lopi run --goal "<g>" --repo <path> |
Run one agent task, stream status to stdout |
lopi bypass <goal…> |
Run with directory restrictions disabled (trusted envs only) |
lopi watch |
Live TUI — agent status (--remote <ws>, --local, or --demo) |
lopi sail |
Web dashboard + agent pool (single- or multi-repo) |
lopi demo |
Generate (if absent) and open a fully synthetic dashboard — zero setup (--seed, --reset, --off, --path) |
lopi tail / lopi dock |
Stream events / list all tasks |
lopi cancel <id> / lopi resume --agent-id <id> |
Cancel / resume a task |
lopi learn / lopi stability / lopi trust |
Browse mined patterns / stability ledger / trust stats |
lopi schedules list |
Scheduled tasks + next run times |
lopi loop show|validate --repo <path> |
Inspect / validate a repo's .lopi/loop.toml |
lopi worktree list|gc |
Manage per-task git worktrees |
lopi skill promote |
Promote recurring lessons into skill drafts |
lopi gap-fill / lopi spec / lopi check |
Test-driven fix queue / spec surface / KCQF quality gate |
lopi replay --task <id> |
Inspect a task's DAG trace and replay from a given stage |
lopi diag |
Export a diagnostic snapshot (tasks, logs, audit, stability) as committable JSON |
lopi mcp-serve |
Serve lopi's MCP tools over stdio — the Claude Code / Desktop entry point |
lopi serve-webhooks |
Standalone GitHub webhook server (CI-failure → task injection) |
17 crates in a Cargo workspace:
| Crate | Role |
|---|---|
lopi-core |
Shared types: Task, Score, LopiConfig |
lopi-agent |
Claude Code subprocess wrapper, retry runner, and scoring |
lopi-context |
Token-budget context window with phase-aware eviction |
lopi-git |
Branch management + path diff validation for agent runs |
lopi-memory |
SQLite-backed store for tasks, patterns, turn metrics, lessons |
lopi-orchestrator |
Concurrent agent pool, priority task queue, scheduler |
lopi-ui |
ratatui TUI + axum web/JSON API (the Forge) |
lopi-remote |
A Twilio WhatsApp webhook handler; not wired to any CLI command and isn't reachable from the built binary today (the Telegram bot that used to live here was removed in Sprint S10, Phase 4) |
lopi-webhook |
GitHub webhook receiver — CI-failure/PR/issue triage → tasks |
lopi-mcp |
MCP client — lopi agents discovering and calling external tools |
lopi-tools |
Durable tool registry (specs, timeouts, retry budgets) |
lopi-skill |
Runtime registry of SKILL.md project knowledge |
lopi-spec |
Spec surface extractor (tests → machine-readable coverage inventory) |
lopi-ratelimit |
Token-bucket rate limiting + Anthropic concurrency controls |
lopi-toon |
Token-Oriented Object Notation encoder (measured 3.3% fewer cl100k tokens than compact JSON on lopi's real prompt payloads — see crates/lopi-toon/benches/results/) |
lopi-github |
Thin GitHub REST client for write operations (PRs, labels, comments) |
lopi-demo |
Deterministic, seeded synthetic-store generator behind lopi demo — also used as a test fixture |
Plus macos/ (native SwiftUI dashboard), web/
(SvelteKit source for the Forge), mcpb/ (Claude Desktop
extension), and plugin/ (Claude Code plugin manifest).
Copy lopi.toml.example to lopi.toml and edit. Key sections:
[lopi]— max concurrent agents, log level, SQLite DB path[claude]—claudeCLI path and per-call timeout[git]— allowed/forbidden directories, auto-PR toggle[remote.telegram]— no longer wired to anything (Sprint S10, Phase 4); the section still parses for backward compatibility with an existinglopi.toml, but no code reads it.[remote.whatsapp]/ Twilio credentials exist as config surface but the handler behind them is not wired to any CLI command — see Highlights above.[web]— dashboard host/port/auth (see Security below)[[schedules]]— cron-style recurring tasks (also editable live from the dashboard)
Per-repo budget and safety policy lives in .lopi/loop.toml (see lopi loop show|validate). lopi serve-webhooks accepts an optional standalone
ANTHROPIC_API_KEY for its own automated triage; this is separate from, and
unrelated to, how the core agent loop authenticates.
lopi is built for one operator, on one machine, alongside your Claude Code
install. There is no multi-tenancy: no per-customer isolation, no
subscription tiers, no hosted-service surface. lopi sail's dashboard/API
authenticates the operator, not a set of separate users — see Security
below. Multi-tenant or hosted deployment (serving multiple customers from one
lopi instance) is explicitly out of scope and unsupported; the GitHub App
OAuth + Stripe billing server that once shipped for that purpose (lopi serve-app) was removed in Sprint S12 for exactly this reason — see
LEDGER.md. If you need to run lopi for more than one person, run one
instance per person.
- Git-isolated branches per attempt; base branch is never touched by
rollback. The attempt's worktree is torn down automatically
(
crates/lopi-git/src/worktree.rs, RAIIDrop), but the branch ref itself is not auto-deleted — it's reclaimed by a separate, manuallopi worktree gcsweep (crates/lopi-git/src/worktree.rs,WorktreeManager::gc/delete_stale_branches). DiffChecker: off-limits glob patterns, full path scan before any change is accepted. Diff size itself is a capped scoring penalty (default 0.10 per 1 000 lines, capped at 0.30 total), not a hard line-count limit.allow_self_modify: falseby default (crates/lopi-core/src/config.rs) blocks lopi from modifying its ownsrc//crates/— but only on thelopi runCLI and REPL paths (src/run_command.rs,src/repl/actions.rs). Thelopi sailweb dashboard's task-creation API and thelopi_submit_taskMCP tool do not currently check it; this is a real gap, not a documentation nuance, and is tracked for a follow-up sprint rather than silently left off this list.- A diff-scope violation triggers hard rollback (
git reset --hard,crates/lopi-git/src/manager.rs) and marks the attemptRolledBack— but the task is retried afterward (with model escalation toward Opus on repeated failure), up tomax_retries, same as any other failed attempt (crates/lopi-agent/src/runner/test_phase.rs,runner/run_loop.rs). It is not a terminal stop condition. - lopi never auto-merges. Every PR requires human review — from the dashboard or the macOS app.
- A task originating from an untrusted source — a GitHub webhook (issue, CI
failure, PR review) — is held for plan approval before the runner acts on
it, regardless of its configured autonomy level. See
docs/security/TRIFECTA_PATHS.md.
lopi sail's dashboard/API is auth-required by default — it refuses to
start without [web].auth_token (or LOPI_WEB_AUTH_TOKEN) unless you pass
--insecure-no-auth explicitly, and that opt-out itself refuses to start on
any non-loopback --host. CORS on /api/* defaults to an explicit origin
allowlist (the local dev origins the web app uses); set
[web].cors_allowed_origins for other origins, or [web].cors_permissive = true to opt out entirely. lopi serve-webhooks refuses to start without
LOPI_WEBHOOK_SECRET unless LOPI_ALLOW_UNVERIFIED_WEBHOOK=1 is set.
GET /api/tasks and GET /api/tasks/:id include a provenance field
("operator" or "untrusted") showing whether a run came from an
authenticated human action (CLI, API, or a historical Telegram-sourced task
— the transport was removed in Sprint S10, Phase 4, but old rows still
deserialize and read as "operator") or an untrusted source (a GitHub
webhook) — recorded and surfaced today, not yet gated on. Repo-supplied
shell commands (.lopi/loop.toml gate/until/test_command) are
untrusted by default for the same reason and refuse to run for an
untrusted-sourced task (Sprint S10, Phase 0); MCP servers are allowlisted
deny-by-default (Sprint S10, Phase 5). Full inventory and rationale:
docs/security/TRIFECTA_PATHS.md and docs/security/EGRESS_SURFACE.md.
Every number lopi displays states what it measures and where it came from —
lopi reports what it can observe directly, and says so plainly when a
number is unavailable rather than estimating without saying so or obtaining
it through means the source didn't intend. No bypassing bot protection, no
undocumented internal APIs, no stored third-party session credentials, ever,
for a nicer statistic. Full policy, including how dollar estimates are
versioned and degrade when stale: docs/MEASUREMENT.md.
Issues and PRs welcome at github.com/konjoai/lopi.
MIT © KonjoAI

