Example: a personal AI assistant
This page builds a small personal assistant: several long-running worker agents, each trusted
with a different slice of your data and tools, dispatched by a manager. One worker gets
access to your notes, another only web search, and no container holds a real credential. The
tools (notes, web) are illustrative.
The shape
manager ── dispatches ──▶ archivist (reads & writes your notes)
researcher (web search only, no notes)
archivistis a trusted persona: it may read and write thenotestool.researcheris less trusted (it reads untrusted web content, a prime prompt-injection vector), so it gets onlywebsearch. It cannot touch notes at all.- Both run in api-key mode (the default): the real model key lives host-side
in the broker, and
egress: closedseals the jail. Neither container ever holds a usable credential, and neither can reach anything but the broker.
The config
name: assistant
backend: orbstack
tree: workspace
egress: closed # seal the jail: agents reach only the broker
scion:
version: e82a2a08 # pin a scion commit; fetched + cross-compiled into the jail
manager:
image: scionlocal/lever-claude
prompt_file: manager.md
broker:
llm_auth: api-key # no real key in any container (the default)
api_key_file: ~/.secrets/anthropic-key # 0600; read host-side by the /llm proxy only
tools:
- name: notes
command: [lever-tool-notes] # the supervised subprocess the broker proxies to
backend: 127.0.0.1:3201 # loopback address it listens on (injected as -backend)
operations:
- { name: read }
- { name: write }
allowed_values:
folder: [journal, reference] # a notes capability may only be pinned to these
- name: web
command: [lever-tool-web]
backend: 127.0.0.1:3202
operations:
- { name: search }
workers:
- name: archivist # trusted: read + write notes
dir: workers/archivist
obtain:
- { tool: notes, op: read }
- { tool: notes, op: write }
- name: researcher # less trusted: web search only
dir: workers/researcher
obtain:
- { tool: web, op: search }
Every field here is in the configuration reference. The
two things doing the security work are broker (the credential boundary) and
the per-worker obtain grants (the capability boundary).
Each tools entry is a host-side subprocess the broker supervises: command
launches it and backend is the loopback address it listens on (injected as
-backend). The broker proxies gated calls to that address, so the tool’s real
backend never enters a container.
What happens at run time
Bring it up and talk to the manager (see getting started):
lever up
Inside the manager session you dispatch a persona:
lever-manager agent start archivist \
--task "Summarise this week's journal entries into reference/weekly.md"
Behind that one command, the boundary is enforced at three points:
- Identity. On boot,
archivistgenerates a keypair inside its container and enrols with the broker over mTLS. The private key never leaves the container; the broker knows this agent by its certificate CN. - Capability mint. When the agent wants to write a note, it asks the broker
for a
notes/writecapability. The broker checksarchivist’sobtainpolicy (allowed), validates any requested constraint againstallowed_values(foldermust bejournalorreference), and mints a short-lived token bound toarchivist’s identity with that constraint pinned in. - Gated call. The agent calls the
notestool through the broker at/mcp/notes/, presenting the token. The broker verifies the signature, that the caller is the bound agent, and that the request matches the pinned constraint, then forwards it. The real key for the model, and the real backend for the tool, stay on the host side.
Now consider researcher, fed a malicious web page that tells it to read your
private notes:
lever-manager agent start researcher \
--task "Research X and report back"
- It has no
notesgrant, so the broker refuses to mint anynotescapability for it (/request→ 403). - Even if it somehow obtained a token, every token is identity-bound, a
capability minted for
archivistis rejected when presented byresearcher. - It cannot reach
api.anthropic.com, your host, or the LAN directly. Egress is closed, so its only outbound path is the broker, and the broker only does what its capabilities allow.
The blast radius of a compromised researcher is web search.
Delegation (optional)
Sometimes the manager wants a persona to do something on its behalf, narrowed to
a specific scope. Grant the manager a delegate right:
manager:
delegate:
- { tool: notes, op: read, to: [researcher] }
Now the manager can ask the broker to mint a notes/read token bound to
researcher and narrowed (e.g. folder: reference), a one-off, strictly
weaker capability the broker enforces. Delegation always goes through the broker
(it is the sole minter), so a delegated token can only ever be narrower than
what policy allows, never wider.
Why this needs Lever
Scion alone would inject the real model key into every container, leave egress open, and gate tools with a coarse shared token; see what Lever adds over Scion and the security model.