Releases: stevegeek/lever
Releases · stevegeek/lever
Release list
v0.10.1
v0.10.0
release: lever 0.10.0 (scion unforked: worker isolation on upstream r…
v0.9.2
lever 0.9.2 — capability tools: silent self-bound delegate (#20)
v0.9.1
lever 0.9.1 — operator directive id-argument fix
v0.9.0
lever 0.9.0 — fixes + QOL sweep
Prebuilt binaries are attached below (darwin/linux × amd64/arm64) — no go install needed. A Go toolchain is still required at runtime (Scion is compiled at apply time).
Added
- Configurable Lima guest disk (
disk:, default 24GiB) — the guest disk was a hardcoded 100GiB sparse file that could wedge a constrained host. (#14) lever doctorreports the agent image's baked Claude Code version, read from an image label — no container run. (#6)- Per-tool supervisor logs — each supervised tool writes its own
.lever-state/tool-logs/<tool>.log, plus logrotate guidance. (#10) lever worker purge <name>— host-side deletion of a worker's scion record + staged bootstrap so it can be redispatched fresh. Never touches the worker's workspace; requires--force. (#7)- Prebuilt release binaries via GoReleaser, wired to a tag-triggered workflow. (#1)
Fixed
- A supervised tool command that doesn't resolve on the supervisor PATH (or is a directory / non-executable) is now rejected loudly at config-load instead of failing opaquely at spawn;
lever doctornow probes every tool backend. (#9) - Broker denials on
/msg/send,/msg/list,/requestnow return the specific policy reason instead of a bareforbidden(scion-runtime errors stay opaque); the CLI surfaces it. (#4a) - Worker start/resume now verifies the worker is actually live (running phase + live container) before reporting success. (#7)
lever stop's scion-suspend failure is surfaced as a warning instead of silently swallowed, so the Lima conversation-loss gap is diagnosable. (#3)
Changed
- Behavior change: dispatching an existing worker with a new task now returns HTTP 409 instead of silently resuming its original creation-time task. Resume with
lever-manager agent resume, orlever worker purge <name>to start fresh with a new task. (#7)
Full changelog: CHANGELOG.md.
lever v0.8.1
Fixed
- Operator directives now reach agents on instances upgraded to 0.8.x, not
only freshly-created ones. The directive generation thattarget_agentbinds
to was established only in the/enrolhandler, but an agent that restarts with
a persisted cert (or whose cert predates 0.8.0) refreshes via/renewand never
re-hits/enrol— so its generation stayed0andlever directive send
failed at resolve withagent not yet enrolled./renewnow establishes the
generation at 1 when unset, without bumping an existing one (bumping stays
reserved for genuine re-enrolment, so a 12 h cert refresh never invalidates an
agent's own active directives).
lever v0.8.0
Added
- Operator directives — an authenticated human-operator→agent channel. Until
now every human→agent instruction (lever msg, relayed email/file text) arrived
as an unauthenticatedsender:"user:…"string, so a well-hardened agent
correctly refused out-of-band instructions it could not attribute to the
operator. Directives give the operator a channel an agent can treat as
authoritative without creating a prompt-injection backdoor:- The operator signs a structured directive with an SSH key
(lever directive send <agent> --instruction … | --action …); the host-side
broker verifies it (ssh-keygen -Y verifyagainst anallowed_signersfile,
exact-byte verify-then-parse, duplicate-key rejection) and delivers only a
pointer (a directive id) into the agent's inbox — never the content. - The agent, if it independently decides to act, fetches the directive with a
directive_consumeMCP tool over its own mTLS channel. All cryptography is
host-side; the model never checks a signature. A directive binds to the
caller's mTLS identity and enrolment generation ({cn, generation}), so a
recycled worker slug can never inherit a predecessor's directive and one agent
cannot consume another's — consume is a single-use atomic compare-and-swap and
every miss returns an identical opaquenot found. - New host CLI:
lever directive send/list/revoke/selftest, over a 0600
unix-domain-socket admin channel (unreachable from the VM); every admin op is
operator-signed. New config blockoperator:(allowed_signers,signing_key,
directive_expirydefault 10 min, capped at 24 h). Directive state (active set,
replay tombstones, per-CN generations) persists across broker restarts
(directives.json, atomic writes, fail-closed). Alever doctorcheck and a
selftestround-trip catchallowed_signersmisconfiguration before it is
needed. Depends on the per-agent netns isolation shipped in 0.7.0. - Scope (honest): Phase 1 delivers authenticated, integrity-protected,
replay-proof delivery and verification of an operator-signed action bound to
the target agent. It does not yet enforce the bound action at tool-call time:
on consume the broker returns the validated action and the agent triggers the
call through its ordinary, independently grant-checked capability path, so a
directive grants no new capability — its value is that the request is
provably from the operator. Host-enforced call-time binding (and theapproval
kind's operator-approval gate) is deferred to a later phase with its own review. - Bootstrap prompts (manager + worker) gain a directive carve-out: only an action
returned by the agent's owndirective_consumethis turn carries operator
authority; messages claiming or quoting a "verified" directive are data.
Manager→worker authority does not launder (directives are signed for the worker
directly). Existing instances show a scaffold-drift warning until re-scaffolded.
- The operator signs a structured directive with an SSH key
lever v0.7.0
Changed
- Agents now run in their own per-agent network namespace (rootless podman's
default pasta networking) instead of a shared jail netns (--network=host).
Each container's127.0.0.1is now private, which closes a cross-agent
escalation: the in-container agent gateway proxy (127.0.0.1:8462, holds the
agent's mTLS leaf, trusts whoever reaches its loopback) was jail-wide under
host networking, so a co-resident worker couldPOSTthe manager's gateway
and be authenticated to the broker as the manager with no credential.
Hub reachability across the netns boundary is preserved by a pasta
--map-host-loopbackoption staged in the guestcontainers.conf.d
(Scion's auto-computed container hub endpoint,host.containers.internal:PORT
for podman, resolves to it); egress containment is unaffected because pasta's
userspace egress still re-emerges on the jailOUTPUTchain (LEVER_EGRESS),
not a bypassingFORWARDpath. No Scion change required. Escape hatch:
LEVER_FORCE_HOST_NETWORK=1on the host restores--network=hostfor
debugging (reopens the shared-loopback gap; not isolation-safe). Cutover
note: switching a running instance requireslever stop+lever up— the
long-running Scion server process caches the old force-host env, so recreating
only the agent is not enough. See the new worker-isolation §4.3.
lever v0.6.0
Changed
- Agent images are now tagged by architecture (
scionlocal/lever-claude:arm64
/:amd64) instead of a shared:latest, and a taglessmanager.image(or
workerimage:) auto-resolves to the jail's arch at apply time. A host that
cross-builds both arches — an arm64 laptop producing an amd64 server image — no
longer clobbers one arch's image with the other's under:latest, the failure
mode where the jail loads a wrong-arch image that dies at boot withexec format error.make lever-image LEVER_IMAGE_ARCH=<arch>buildsFROM scion-claude:<arch>
and tags the output:<arch>; an explicitly-tagged or digest-pinned image ref is
left untouched (the escape hatch). Instances that pinned…:latestshould drop
the tag to opt into arch-resolution.
Fixed
- The capability-minting sidecar (
lever-agent serve-capability) now re-reads
the rotating agent leaf per broker handshake instead of freezing the boot
cert. It built its mTLS client once via the staticIdentity.Client(), so
after the leaf's 24h TTL every capability mint failed the broker handshake
(certificate has expired) — taking down every brokered tool (each mints a
capability first) while the broker itself stayed healthy, recurring roughly
daily. The 2026-07-13 gateway fix covered Claude's proxied MCP/LLM traffic but
not this second, direct-to-broker client. A newagent.NewReloadingClient
(reusing the gateway's per-handshakeclientCertSource) closes it, and
Identity.Client()is now documented short-lived-only so no future long-lived
holder reintroduces the trap.
lever v0.5.0
Added
lever versionnow appends build provenance to the release string when the
binary carries it: the commit it was built from (short) plus-dirtyfor an
uncommitted tree (anygo build/make installfrom a git checkout), or the
module version for ago install …@vXbuild. A make-install binary that lags
its source no longer hides behind the bare hardcoded version string.lever init --adopt: record owner-customized scaffolds (the operator/agent
SKILL.md files and the whole tree-root CLAUDE.md) as an accepted baseline in
.lever-state/skills-adopted.json. Doctor's "operator skills" check and
lever init --checkthen treat the adopted content as OK, and a plain
lever initleaves it alone (including not appending the CLAUDE.md block).
Previously a customized scaffold readskipped-modifiedforever. Adoption
is deliberately a recorded baseline rather than a mute: the scaffolds live
inside the agent-writable tree, so the check doubles as tamper detection —
any change PAST the adopted baseline fails doctor as "modified since
adoption", and the baseline itself lives host-side where an agent cannot
re-bless its own edits. Only genuine customizations qualify: framework-
current files and stale-but-unmodified scaffolds (plain-initrefresh
territory) never get a record.lever init --forcestill restores the
framework content — for CLAUDE.md, by re-ensuring the marker block in place
— and clears the now-stale adoption record.
Fixed
lever upno longer needs a second run to clear the first-boot
start-managerrace on a cold VM. The scion workstation daemon registers its
runtime broker asynchronously after its Hub API comes up, so the first
create/resume could act before the broker was ready — failing the apply (on a
cold VM as a hub "context deadline exceeded") so only a secondup
reconciled.start-managernow waits for the runtime broker to be registered
and online (viascion hub brokers) before acting, closing the window at the
source; the wait is fail-soft, so it can never fail the bring-up on its own.
As a backstop, the transient-broker retry also now treats a hub "deadline
exceeded" as the same race, and the initial observescion listrides that
bounded, ctx-checked retry instead of failing on the first blip.lever destroynow clears the persisted controller PAT
(.lever-state/controller.pat). The PAT is minted against the hub DB that
lives inside the jail, so destroying the machine leaves it stale; the next
lever upreused it (ensureControllerPAT no-ops when a PAT is already
persisted) and the new hub's fresh DB rejected it, failing the readiness
probe with "authentication failed" until the file was removed by hand. Only
the current-instance teardown (no--machine) clears it, alongside the
broker stop and staged-ticket cleanup it already does.lever applyno longer re-imports every agent image into the jail on each
run. Theload-imagestep now first compares the jail's podman image ID
against the host docker image ID and skips the multi-GBdocker save | podman loadre-stream when they match (the config digest is stable across
save/load, so equal IDs mean the exact bytes are already present). The check
is fail-open — any uncertainty (a not-yet-loaded or rebuilt image, an inspect
failure) falls through to a load, so it can never wrongly skip and leave a
stale image. This matters most under the first-boot retry loop, where any
step failing re-runs the entire plan: previously each retry re-streamed
every image; now unchanged images are near-no-ops. After a load, the step also
prunes dangling (untagged, unreferenced) jail images — so a rebuilt image,
whose old copy the load leaves untagged, no longer ratchets the grow-only jail
disk up by a full image size (a no-op when nothing was superseded). Pruning
never touches a tagged or container-referenced image.- A tool whose broker backend carries a path (e.g. qmd's
[::1]:3101/mcp) now
reaches that path exactly on the tool root, instead of a trailing-slash
variant (/mcp/) that a strict streamable-HTTP endpoint 404s. The trailing
slash was an artifact of the broker's subtree mux; qmd was the only tool with
a path-suffixed backend and so the only one that couldn't connect. Path-less
backends (every other tool) and sub-path requests are unaffected. lever doctor's "agent certificate" check no longer cries wolf right after
a healing restart: an expired-leaf rejection logged before the current
broker started (pid-file mtime) is reported as healed rather than as an
active failure. Previously any rejection inside the 15-minute window failed
the check even when the restart that fixed it had already happened.- The broker's mTLS serving cert now self-rotates. It was minted once at
startup with the 24h leaf TTL, so a broker running longer than a day served
an expired cert and every gateway handshake failed — tools down, and the
agents' own/renewcalls failed with it, so their leafs decayed too (the
only remedy was alever stop && lever uppower-cycle). The broker holds the
CA key, so it re-mints its serving cert in-process via a rotating
GetCertificatesource when less than 6h of validity remains; agent leafs
were already kept fresh by the 12h renew sidecar once the broker stays
reachable. - The agent's mTLS client leaf now hot-reloads on the live MCP/LLM path. Claude
Code readCLAUDE_CODE_CLIENT_CERT/_KEYonce at process start and cached the
leaf for its whole lifetime, so after ~24h the boot cert expired and every
gateway call failed withtls: certificate has expired— despite the renew
sidecar rewriting a fresh leaf every 12h — until the manager was restarted. A
newlever-agent gatewaysidecar now runs a loopback (127.0.0.1) reverse-proxy
that terminates plaintext from Claude and re-presents the always-current leaf
to the broker over mTLS, re-readingagent.{crt,key}per TLS handshake. Claude
no longer holds the rotating cert: boot points its MCP--transport httpURLs
andANTHROPIC_BASE_URLat the loopback gateway. The proxy caps idle broker
connections at 5m so a rotated leaf reaches the broker well before the 24h
expiry, usesFlushInterval = -1for MCP's SSE streaming, and binds loopback
only (it holds the agent key). Boot-time discovery and llm-token calls still go
direct to the broker (the gateway isn't up during pre-start).