Skip to content

Releases: stevegeek/lever

v0.10.1

Choose a tag to compare

@github-actions github-actions released this 22 Jul 14:39
release: lever 0.10.1 (version-constant bump missed in 0.10.0; releas…

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 22 Jul 14:33
release: lever 0.10.0 (scion unforked: worker isolation on upstream r…

v0.9.2

Choose a tag to compare

@github-actions github-actions released this 22 Jul 11:04
lever 0.9.2 — capability tools: silent self-bound delegate (#20)

v0.9.1

Choose a tag to compare

@github-actions github-actions released this 22 Jul 10:07
lever 0.9.1 — operator directive id-argument fix

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 22 Jul 06:14

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 doctor reports 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 doctor now probes every tool backend. (#9)
  • Broker denials on /msg/send, /msg/list, /request now return the specific policy reason instead of a bare forbidden (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, or lever worker purge <name> to start fresh with a new task. (#7)

Full changelog: CHANGELOG.md.

lever v0.8.1

Choose a tag to compare

@stevegeek stevegeek released this 21 Jul 17:40

Fixed

  • Operator directives now reach agents on instances upgraded to 0.8.x, not
    only freshly-created ones. The directive generation that target_agent binds
    to was established only in the /enrol handler, but an agent that restarts with
    a persisted cert (or whose cert predates 0.8.0) refreshes via /renew and never
    re-hits /enrol — so its generation stayed 0 and lever directive send
    failed at resolve with agent not yet enrolled. /renew now 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

Choose a tag to compare

@stevegeek stevegeek released this 21 Jul 15:44

Added

  • Operator directives — an authenticated human-operator→agent channel. Until
    now every human→agent instruction (lever msg, relayed email/file text) arrived
    as an unauthenticated sender:"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 verify against an allowed_signers file,
      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_consume MCP 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 opaque not 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 block operator: (allowed_signers, signing_key,
      directive_expiry default 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). A lever doctor check and a
      selftest round-trip catch allowed_signers misconfiguration 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 the approval
      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 own directive_consume this 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.

lever v0.7.0

Choose a tag to compare

@stevegeek stevegeek released this 21 Jul 08:09

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's 127.0.0.1 is 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 could POST the 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-loopback option staged in the guest containers.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 jail OUTPUT chain (LEVER_EGRESS),
    not a bypassing FORWARD path. No Scion change required. Escape hatch:
    LEVER_FORCE_HOST_NETWORK=1 on the host restores --network=host for
    debugging (reopens the shared-loopback gap; not isolation-safe). Cutover
    note:
    switching a running instance requires lever 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

Choose a tag to compare

@stevegeek stevegeek released this 17 Jul 12:06

Changed

  • Agent images are now tagged by architecture (scionlocal/lever-claude:arm64
    / :amd64) instead of a shared :latest, and a tagless manager.image (or
    worker image:) 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 with exec format error. make lever-image LEVER_IMAGE_ARCH=<arch> builds FROM 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 …:latest should 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 static Identity.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 new agent.NewReloadingClient
    (reusing the gateway's per-handshake clientCertSource) closes it, and
    Identity.Client() is now documented short-lived-only so no future long-lived
    holder reintroduces the trap.

lever v0.5.0

Choose a tag to compare

@stevegeek stevegeek released this 16 Jul 08:30

Added

  • lever version now appends build provenance to the release string when the
    binary carries it: the commit it was built from (short) plus -dirty for an
    uncommitted tree (any go build / make install from a git checkout), or the
    module version for a go install …@vX build. 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 --check then treat the adopted content as OK, and a plain
    lever init leaves it alone (including not appending the CLAUDE.md block).
    Previously a customized scaffold read skipped-modified forever. 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-init refresh
    territory) never get a record. lever init --force still restores the
    framework content — for CLAUDE.md, by re-ensuring the marker block in place
    — and clears the now-stale adoption record.

Fixed

  • lever up no longer needs a second run to clear the first-boot
    start-manager race 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 second up
    reconciled. start-manager now waits for the runtime broker to be registered
    and online (via scion 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 observe scion list rides that
    bounded, ctx-checked retry instead of failing on the first blip.
  • lever destroy now 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 up reused 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 apply no longer re-imports every agent image into the jail on each
    run. The load-image step now first compares the jail's podman image ID
    against the host docker image ID and skips the multi-GB docker save | podman load re-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 /renew calls failed with it, so their leafs decayed too (the
    only remedy was a lever stop && lever up power-cycle). The broker holds the
    CA key, so it re-mints its serving cert in-process via a rotating
    GetCertificate source 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 read CLAUDE_CODE_CLIENT_CERT/_KEY once at process start and cached the
    leaf for its whole lifetime, so after ~24h the boot cert expired and every
    gateway call failed with tls: certificate has expired — despite the renew
    sidecar rewriting a fresh leaf every 12h — until the manager was restarted. A
    new lever-agent gateway sidecar 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-reading agent.{crt,key} per TLS handshake. Claude
    no longer holds the rotating cert: boot points its MCP --transport http URLs
    and ANTHROPIC_BASE_URL at the loopback gateway. The proxy caps idle broker
    connections at 5m so a rotated leaf reaches the broker well before the 24h
    expiry, uses FlushInterval = -1 for 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).