lever

module
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2026 License: MIT

README

Lever

Lever

Homepage: lever.to

Containerised, jailed multi-agent orchestration. Lever lets a single manager agent (the coordinator) drive a fleet of worker agents that do real work, each in its own isolated container, while the whole stack runs inside a jail designed so that a compromised or prompt-injected agent cannot read host secrets or reach your local network.

Lever is the orchestration and interface layer; Scion is the runtime engine underneath (containers, sessions, attach/resume, typed messaging). You talk to one tool, lever, and it drives Scion for you.

Status (v0.4.0): the single-project architecture — one Scion project per instance, manager + workers as agents in it, the real hub running dev-auth-off behind a host-side controller PAT — working end-to-end on macOS (OrbStack) and Linux (Lima/QEMU-KVM). Install with go install github.com/stevegeek/lever/cmd/lever@latest; the agent image is built locally (make lever-image). Gaps to know about before you rely on it are listed in Where this is today.

Why

LLM agents that run autonomous tool-call loops are powerful and dangerous in the same breath. The moment an agent processes untrusted content, a web page, a dependency, an issue comment, it can be steered. If that agent runs on your machine with your filesystem and your network, "steered" means your SSH keys and your LAN.

Lever's answer is not "trust the agent." It is assume the agent is hostile and let the OS contain it. The intended bound is a single directory subtree and a curated set of network endpoints, enforced by the operating system, not by the agent behaving. (What that bound does and does not cover, e.g. data exfiltration over allowed internet egress, is spelled out in the security model.)

The model in one paragraph

A project is just a directory. You register a directory with Lever and every agent working on it gets that directory bind-mounted, live, in place, no clones, no sync. One special project is the manager, whose workspace is the whole tree; every other project is a worker (a project directory an agent works in), isolated from the manager and from its siblings. The manager dispatches work to workers, watches a typed event stream for progress and questions, and is the single thing a human talks to.

graph TB
    Hu([Human])

    subgraph host["Host (macOS) — outside the jail"]
        CLI[lever CLI]
        BK["Capability broker<br/>holds real credentials<br/>capabilities · /llm · /worker/* · brokered tools (/mcp/&lt;name&gt;/)"]
        TOOLS[first-party tools<br/>host subprocesses]
        TREE[("project tree<br/>on host disk")]
    end

    subgraph jail["Jail — OrbStack isolated machine (the containment boundary)"]
        HUB["Scion server + runtime<br/>(rootless containers)"]
        FW{{egress allowlist<br/>in the jail's netns}}
        subgraph cons["Agent containers"]
            CO["Coordinator<br/>whole-tree workspace"]
            GA["Worker A → workers/a/"]
            GB["Worker B → workers/b/"]
        end
    end

    Hu <-->|converses with| CO
    CLI -->|brings up / drives| HUB
    CO -->|"dispatch · capabilities · LLM · tools<br/>(mTLS via host.orb.internal)"| FW
    GA -->|capabilities · LLM · tools| FW
    GB -->|capabilities · LLM · tools| FW
    FW -->|"allowlisted: broker + model API"| BK
    BK --- TOOLS
    BK -->|"/worker/start → Scion (operator)"| HUB
    HUB --> CO
    HUB --> GA
    HUB --> GB
    TREE -.->|"bind-mount: whole tree"| CO
    TREE -.->|"bind-mount: workers/a only"| GA
    TREE -.->|"bind-mount: workers/b only"| GB

How it stays contained

The runtime and every agent run inside one jail, an OrbStack isolated machine: a Linux guest that, unlike a normal machine, shares none of the host's files and has its own network namespace by default. The lever operator binary and Lever's capability broker (which holds your real credentials) run on the host; Scion's server and runtime broker, the container runtime, and all agents run inside the jail. The jail mounts only the project tree you choose and cannot route to your LAN. Inside it, agents run as rootless containers. The result the design targets:

  • Filesystem: host secrets (~/.ssh, cloud creds) are not in the environment, so they cannot be mounted or read, even by the orchestrator.
  • Network: the LAN is unreachable; only an explicit allowlist of endpoints (the model API and chosen local tool ports, e.g. MCP, the Model Context Protocol) is permitted.

No fork of the runtime is required, the containment is enforced from outside it. Full detail, caveats, and the validation evidence are in security model.

The jail is a VM-isolation contract, not a container runtime. OrbStack is the reference implementation; lima (macOS/Linux, its own VM kernel) is the second, both selectable today. Apple's per-agent-VM container is on the roadmap; each declares its own guarantees (lever backends, or the containment backends matrix). Docker Desktop is not an implementation, its shared VM auto-mounts your home directory and its network namespace is not yours to control, so it cannot provide the boundary; a native, no-VM Linux backend was explored and rejected on the mirror-image ground (no VM at all) — see the backends page.

Core + instance

lever.to ships the generic core: the orchestration engine, the manager runtime/role, the jail provisioning, the project model, and these docs. Your own setup is an instance built on top, your own knowledge base, your own tools, your own workers, and the manager's prompt/skills/tool config, consuming the lever binary as a dependency. The framework authors run their personal assistant as the first instance (dogfooding). See core vs instance.

Where this is today

Everything this README describes runs today, on macOS (OrbStack) and on Linux (Lima/QEMU-KVM, validated end-to-end on a remote server). Two runnable examples ship in-repo: hello-worker and assistant-demo.

The honest gaps — things you should know before relying on it:

  • On the Lima backend, lever stopup currently comes back with a fresh manager conversation (#3). OrbStack preserves the conversation across a power-off; Lima doesn't yet.
  • Prebuilt lever binaries now ship on each release (darwin/linux × amd64/arm64 — see Releases); a Homebrew tap is still pending (#2). You can also go install or build from a clone. A Go toolchain is still required at runtime (Scion is compiled at apply time), and the agent image is built locally with Docker.
  • Worker subtree isolation runs on stock upstream Scion. Each worker is confined to its own workers/<name> subtree via Scion's relative --workspace (resolved against the project root with a containment guard), merged upstream in scion#815. Requires a scion.version pin of b4c9911d or later — the shipped examples pin it.

Docs: getting-started, capabilities, operations, CLI, architecture, security model.

Build & run

There are two binaries (one shared internal/):

  • lever, the host control plane (provisioning + lifecycle). Runs on your machine.
  • lever-manager, the in-jail orchestrator (agent/msg/watch). Cross-compiled for the jail's linux/arm64 by make lever-image-bins and baked into the agent image (your Dockerfile COPYs it to /usr/local/bin). The manager runs it to dispatch and steer workers.
go install github.com/stevegeek/lever/cmd/lever@latest   # host `lever` onto your GOBIN/PATH
# — or from a clone:
make install              # build host `lever` → ~/.local/bin/lever (must be on PATH). Requires Go 1.26+
make all                  # same (the in-jail binaries ship in the agent image, via lever-image-bins)

# Bring an application up (jail + scion + manager) and attach the manager TTY.
# Run from the instance root (where lever.yaml lives, resolved from cwd, no walk-up):
cd path/to/my-instance && lever up

# …or pass an explicit config path from anywhere:
lever up path/to/my-instance/lever.yaml

# Headless (bring up, don't attach):
lever apply
lever apply --dry-run                     # print the bring-up plan only

Build the agent image (scionlocal/lever-claude:<arch>, loaded into the jail) with make lever-image — it cross-compiles the in-jail binaries and builds FROM scion's stock scion-claude:<arch> (build that once from a scion checkout: image-build/scripts/build-images.sh --target harnesses, then docker tag scion-claude:latest scion-claude:<arch>). Images are tagged by architecture (:arm64 / :amd64); a tagless manager.image auto-resolves to the jail's arch, so one config works on both. Instances that need extra tooling extend the image with their own Dockerfile FROM lever-claude:<arch>.

Overrides: make install PREFIX=/some/bin, make lever-image LEVER_IMAGE_ARCH=amd64, make lever-image-bins LEVER_IMAGE_CTX=/path/to/image-context (stage bins into an instance dir).

An application is one config file describing the manager + its workers (image, project tree, scion source, credential, allowed host ports). The canonical filename is lever.yaml at the instance root, which is not mounted, only the tree: subdirectory is bind-mounted into the jail (so the config and boot prompt stay out of the agent-writable mount). Commands with no config argument read ./lever.yaml from the current directory; there is no walk-up discovery (run from the root, or pass an explicit path). See examples/ for runnable configs and config reference for every key.

Commands

Host lever (control plane):

Command What it does
lever up [config] Bring the application up if needed (create jail, provision scion, start the manager) and attach the manager's TTY. Reads ./lever.yaml from cwd when omitted (no walk-up). --fresh starts a new manager thread; --no-attach brings up without attaching. The everyday entry point.
lever apply [config] Headless bring-up, runs the full plan (jail → images → scion init/config/server → credential → register manager + workers → mint bootstrap → start manager). No attach. --dry-run prints the plan and exits.
lever provision Low-level: provision the jail only (create the isolated machine, install runtimes + scion, apply egress). --machine, --tree, --allow-port. Rarely needed directly.
lever reload [config] Apply config changes to a running instance without a VM power cycle: restarts the broker on the current config (re-registers workers, re-applies egress) while leaving the manager container running, so its conversation is preserved. Needed because the broker reads lever.yaml only at startup.
lever attach [name] Attach your TTY to the manager (default) or a named worker. Strictly passive: fails fast with "run lever up first" if the jail isn't up.
lever msg send "…" --to NAME Host-side fire-and-forget note to the manager (app name) or a declared worker — no attach needed; the agent picks it up as its next user turn. --interrupt injects it ahead of the agent's next turn. Strictly passive like attach.
lever init Scaffold/refresh the framework operator skills (SKILL.md) into the instance tree — lever-operator at the tree root, lever-agent in each worker dir — plus a marked reference block in the tree-root CLAUDE.md. Hash-guarded: your edited copies are left alone with a warning (--force overwrites); --check reports staleness without writing. Re-run after upgrading lever or adding a worker.
lever stop Power the jail off but keep its disk (orb stop) — the daily "done for the day". Suspends the manager, stops the host broker; a later lever up powers it back on and resumes. Everything (installed runtimes, scion state) persists.
lever destroy Full teardown: delete the isolated machine and everything in it (orb delete). Targets lever-<name> from config; override with --machine. lever down is a deprecated alias.
lever doctor Diagnose the setup (broker alive, external tool backends reachable, credential file, scion registration, .mcp.json-in-tree, Go toolchain, operator-skills scaffold current); each failing check prints the fix. Targets lever-<name> from config; override with --machine.
lever version Print the version.

In-jail lever-manager (orchestration, run by the manager inside the container):

Command What it does
lever-manager agent <list|start|stop|suspend|resume> NAME Worker lifecycle, routed through the capability broker. Dispatch a worker with agent start NAME --task "…", where NAME is a worker declared in the config; the broker authenticates the call, validates the name, and resolves the worker's image and workspace from the config before starting it (--image overrides).
lever-manager msg send --to WORKER "…" / lever-manager msg list Send a message to a running agent / read the typed agent-event inbox (scion notifications).
lever-manager watch Stream scion events to a file the manager Monitors (the notification bridge).
lever-manager version Print the version.

lever up is the muscle-memory entry; apply is its non-interactive half for scripts/scheduled runs. Both are idempotent, re-running up resumes a suspended manager and re-attaches.

Requirements (intended)

  • macOS on Apple Silicon with OrbStack (the validated host today), or Lima (macOS or Linux, its own VM kernel; requires Lima ≥ 2.0.0) as the non-OrbStack backend. See containment backends.
  • Scion as the runtime engine.
  • An LLM coding-agent harness (e.g. an OAuth-authenticated Claude Code).

Documentation

  • Getting started, build and run a working instance from scratch (worked example).
  • Config reference, every lever.yaml key, defaults, and conventions.
  • Architecture, topology, components, the dispatch/notification loop, the project model.
  • Security model, threat model, the jail, what containment does and does not buy, validation evidence.
  • Core vs instance, the boundary, and how an instance is built on the core.
  • Conventions, recommended (not enforced) patterns, shown via the reference instance.

Licence

MIT © Stephen Ierodiaconou.

Directories

Path Synopsis
Package captool is the host-side SDK a first-party (capability-aware) lever tool uses to serve MCP, independently verify the presented capability token, enforce a hard backstop, and register with the broker at boot.
Package captool is the host-side SDK a first-party (capability-aware) lever tool uses to serve MCP, independently verify the presented capability token, enforce a hard backstop, and register with the broker at boot.
cmd
lever command
lever-agent command
Command lever-agent is the in-jail capability helper: it enrols the agent's mTLS identity (key generated in-container, never leaves), serves the capability MCP tool the LLM drives, renews before expiry, and (via CLI verbs) lets the acceptance harness mint/delegate/exercise deterministically.
Command lever-agent is the in-jail capability helper: it enrols the agent's mTLS identity (key generated in-container, never leaves), serves the capability MCP tool the LLM drives, renews before expiry, and (via CLI verbs) lets the acceptance harness mint/delegate/exercise deterministically.
lever-manager command
lever-tool-db command
Command lever-tool-db is the reference first-party capability-aware tool: a SQLite-backed read-only DB exposed over MCP via captool, with a hard table-allowlist backstop.
Command lever-tool-db is the reference first-party capability-aware tool: a SQLite-backed read-only DB exposed over MCP via captool, with a hard table-allowlist backstop.
examples
assistant-demo/tools/lever-tool-todo command
Command lever-tool-todo is the demo's FIRST-PARTY capability tool: a tiny read-only MCP server, built with the captool SDK, that lists todos from a CSV.
Command lever-tool-todo is the demo's FIRST-PARTY capability tool: a tiny read-only MCP server, built with the captool SDK, that lists todos from a CSV.
assistant-demo/tools/weather-stub command
Command weather-stub is a stand-in for an EXTERNAL MCP server the broker FRONTS (proxies) rather than owns — the same shape as a real host service like a calendar or a weather API you already run.
Command weather-stub is a stand-in for an EXTERNAL MCP server the broker FRONTS (proxies) rather than owns — the same shape as a real host service like a calendar or a weather API you already run.
internal
agent
Package agent is the in-jail lever-agent core: it owns the agent's keypair (generated in-container, never leaves), enrols + renews its mTLS identity with the broker, mints/attenuates capability tokens on the LLM's behalf, and serves the capability MCP tool.
Package agent is the in-jail lever-agent core: it owns the agent's keypair (generated in-container, never leaves), enrols + renews its mTLS identity with the broker, mints/attenuates capability tokens on the LLM's behalf, and serves the capability MCP tool.
apply
Package apply turns an application config into an ordered bring-up plan and (Part C) executes it.
Package apply turns an application config into an ordered bring-up plan and (Part C) executes it.
backend
Package backend defines the substrate contract every containment backend satisfies.
Package backend defines the substrate contract every containment backend satisfies.
backend/guest
Package guest provisions an Ubuntu jail guest — rootless container runtimes plus a cross-compiled scion binary — through host-side argv prefixes.
Package guest provisions an Ubuntu jail guest — rootless container runtimes plus a cross-compiled scion binary — through host-side argv prefixes.
backend/lima
Package lima implements the Backend contract via a Lima VM (vz on macOS, QEMU/KVM on Linux).
Package lima implements the Backend contract via a Lima VM (vz on macOS, QEMU/KVM on Linux).
backend/orbstack
Package orbstack implements the Backend contract on macOS via an OrbStack isolated machine + rootless Docker + iptables egress.
Package orbstack implements the Backend contract on macOS via an OrbStack isolated machine + rootless Docker + iptables egress.
backend/registry
Package registry constructs the selected containment backend.
Package registry constructs the selected containment backend.
bridge
Package bridge is the poll-based notification bridge: it consumes Scion agent events via an Inboxer and appends each NEW event (by id) as one JSON line to an events file.
Package bridge is the poll-based notification bridge: it consumes Scion agent events via an Inboxer and appends each NEW event (by id) as one JSON line to an events file.
broker
Package broker is the host-side capability authority and brokered-tool proxy.
Package broker is the host-side capability authority and brokered-tool proxy.
broker/registry
Package registry holds the broker's view of registered MCP tools: their backends, operations, the caveat→param mapping that lets the gateway turn a request's real arguments into the constraint-keyed param set the token layer verifies, and optional allowed-value rules.
Package registry holds the broker's view of registered MCP tools: their backends, operations, the caveat→param mapping that lets the gateway turn a request's real arguments into the constraint-keyed param set the token layer verifies, and optional allowed-value rules.
broker/rules
Package rules is the broker's request/delegation policy: per agent, which capabilities it may obtain for itself and which it may delegate (mint bound to another agent).
Package rules is the broker's request/delegation policy: per agent, which capabilities it may obtain for itself and which it may delegate (mint bound to another agent).
brokerctl
Package brokerctl is the host-side controller for the lever capability broker: it translates a lever config into a broker.Config, ensures the CA + capability signing root key, supervises first-party tool subprocesses, and runs the broker.
Package brokerctl is the host-side controller for the lever capability broker: it translates a lever config into a broker.Config, ensures the CA + capability signing root key, supervises first-party tool subprocesses, and runs the broker.
cap/ca
Package ca is a tiny internal CA: it issues per-agent mTLS certs whose CommonName is the agent identity, and proves the caller identity of a TLS connection.
Package ca is a tiny internal CA: it issues per-agent mTLS certs whose CommonName is the agent identity, and proves the caller identity of a TLS connection.
cap/token
Package token mints and verifies per-agent capability tokens: a compact Ed25519-signed struct (see token.go).
Package token mints and verifies per-agent capability tokens: a compact Ed25519-signed struct (see token.go).
cli
config
Package config loads an application config: the declarative description of a lever agent-manager application (the manager + its workers).
Package config loads an application config: the declarative description of a lever agent-manager application (the manager + its workers).
egress
Package egress generates the jail's network egress allowlist as iptables/ ip6tables OUTPUT-chain argv.
Package egress generates the jail's network egress allowlist as iptables/ ip6tables OUTPUT-chain argv.
exec
Package exec is the single seam to external commands (orb, docker, scion, iptables).
Package exec is the single seam to external commands (orb, docker, scion, iptables).
jail
Package jail provides a JailRunner: an exec.Runner that executes commands INSIDE a jail via a backend-supplied argv prefix — e.g.
Package jail provides a JailRunner: an exec.Runner that executes commands INSIDE a jail via a backend-supplied argv prefix — e.g.
opsig
Package opsig implements the operator-directive signature protocol: wire types, exact-byte statement parsing with duplicate-key rejection, and signing/verification via the system ssh-keygen (-Y sign / -Y verify).
Package opsig implements the operator-directive signature protocol: wire types, exact-byte statement parsing with duplicate-key rejection, and signing/verification via the system ssh-keygen (-Y sign / -Y verify).
scion
Package scion is the single seam to the `scion` CLI.
Package scion is the single seam to the `scion` CLI.
skills
Package skills holds the framework-authored SKILL.md files scaffolded into instance trees by `lever init`.
Package skills holds the framework-authored SKILL.md files scaffolded into instance trees by `lever init`.
tools
test/fakeupstream command
Command fakeupstream is a deterministic stand-in for api.anthropic.com used by the api-key live test suite.
Command fakeupstream is a deterministic stand-in for api.anthropic.com used by the api-key live test suite.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL