Concepts · 2 of 4

Lend a host binary, don't rebuild one.

A tool-lane cell runs an executable the host already vouches for — a real binary that already exists on the host filesystem, declared explicitly and sealed in read-only. This is the tutorial for celln spec and celln run: writing the declaration, seeing what the trust model decides, and running it for real.

What "tool lane" means

Celln doesn't ship a curated library of tools. You point it at a binary already on your host — /usr/bin/python3, a linter, a build step, whatever your agent needs to invoke — and declare that in a spec file. assay, the host-side attestation service, hashes those bytes and admits them into its manifest at one of three tiers:

TierMeansCost
forgedRebuilt from source on this host, twice, in different directories, and the bytes matched. Reproducibility is established, not just claimed.Minutes, done in the background
verifiedAn upstream binary, pinned and scanned but not rebuilt here.Seconds — the default cold path
unsealedNo attestation at all. Still hardware-isolated, but carries no tool-lane authority.Instant

Whichever tier a tool lands at, the cell only ever sees it as read-only pages it cannot modify — not by writing to the file, not by writing to its own page tables.

Tools that are a closure

Most real tools are not one file. python, curl and node are a binary plus a loader plus a tree of shared objects resolved by absolute path, and a cell has no loader and no libc of its own — on a typical host only a handful of the binaries in /usr/bin are static enough to lend on their own.

So a closure is lent as a whole sealed filesystem, built from an OCI image and pinned by digest:

[[tool]]
alias = "/usr/bin/python"
image = "python"                   # a catalogue name; celln resolves the pin
exec  = "/usr/local/bin/python3.12"
interpreter = true

Materialise it once with celln image pull; a cell then maps bytes that already exist, so starting a cell never waits on a registry. A tag is refused — it can be moved, and what a cell is lent must not change without the spec changing.

Several images can be mounted into one cell, and tools naming the same image share its mount and its single physical copy. Lend a tool and its dependencies, not a distribution: celln image pull refuses anything over 512 MiB. The tutorial works through it.

Write a spec

A spec is what your agent may be lent, and what it intends to run. Nothing not listed here can execute inside the cell — that's the point of the file.

celln spec init > agent.toml
name = "code-reviewer"

[cell]
memory = "256MiB"
require_tier = "verified"

[[tool]]
alias = "/usr/bin/python"     # the name your agent uses
image = "python"              # the closure the bytes come from
exec  = "/usr/local/bin/python3.12"
interpreter = true            # see below

[run]
exec = "/usr/bin/python"
args = ["-c", "print('reviewed 42 files')"]
input = "data"                # the agent wrote it

alias is only a naming convenience — the content hash is the actual authority, never the name. image plus exec names the closure the bytes come from, pinned by digest so it cannot change under you; path is the alternative for a genuinely static binary already on this host.

Check it

celln spec check validates the file and tells you exactly what the trust model will decide before anything runs:

$ celln spec check agent.toml
✔ code-reviewer  1 tool(s), 256MiB memory, require_tier=verified

tools
  /usr/bin/python          interpreter  python → /usr/local/bin/python3.12

run
  /usr/bin/python -c print('reviewed 42 files')
  runs in the agent lane — demoted: an interpreter fed agent-authored input

Notice the verdict: even though python is fully attested, this particular invocation is demoted to the agent lane, because the input it's being fed is marked data — something the agent wrote, not something that came in through the attestation gate. See the agent lane for why that matters.

Run it

$ celln run agent.toml
● sealing cell code-reviewer
  + /usr/bin/python        tier=verified cold — verified now, forged queued
  ✔ /usr/bin/python permitted in the agent lane
  · cell sealed, 1 tool(s) lent read-only
  ✔ pilot: /usr/bin/python permitted:agent
  · /usr/bin/python exit=0

reviewed 42 files
● cell dissolved

The verdict appears twice on purpose: once on the host before the cell exists, and once from pilot inside it, after re-hashing the bytes it actually found. Behind them is the authority ratchet — once a cell's first tainted exec happens, materialisation closes for that lineage permanently. Rights only shrink from there; there is no path back to "lend one more tool."

The interpreter flag

interpreter = true is the single most consequential line in a spec. An interpreter fed something the agent wrote is moved to the agent lane for that invocation — this is the laundering ban, and it's why python evil.py and python -c "…" don't get to launder agent-authored code into full tool-lane authority just by passing through an attested binary. Leave an interpreter marked interpreter = false and you've quietly created a hole: anything fed to it keeps full tool-lane authority, including code the agent wrote. celln spec check guesses at this from the binary's name and warns if your answer disagrees.

What's actually enforced

Full detail, including what this does not claim: the security boundary.