Draft spec · v0.1
LAMP: Least-Authority Module Protocol
A protocol for packaging capability — for humans at a terminal and agents behind a tool call, under one enforced ceiling.
Abstract
Agents and humans reach the world through the same blunt instrument: a shell whose permission model is over text, not effects. LAMP defines a portable unit — a lamp — that couples a program with a declared capability ceiling, and a contract between three parties: the author declares, the operator grants, the runtime enforces the intersection and keeps the record. A lamp is one thing a human runs and one structured tool an agent receives; neither can widen what it was given. The protocol is language-independent: any runtime that enforces the invariant can execute any lamp.
1The problem: permission over text
When an agent is handed a shell, the grant is a string match — this command line looks safe. But text does not bound effects: the same binary reads and destroys, the credential travels with the invocation, and every new command is a new negotiation. The result is either over-granting (one broad approval) or friction (a prompt per line), and in both cases the record of what was actually reached is reconstructed from logs after the fact, if at all.
The correct unit of permission is not the command but the capability: which hosts, which paths, which binaries, which databases — as effects, enforced below the program, not promised by it.
2The protocol: three parties, four verbs
LAMP is not a wire protocol; it is an authority protocol — an agreement about how capability is declared, granted, narrowed and recorded. Three parties participate, and none of them has to trust the other two:
-
The authordeclares. The manifest states the lamp's ceiling — every family of effect it may ever touch (
net,file,exec,db…), scoped by pattern. -
The operatorgrants — once. A project ceiling and a session ceiling, written by a human, bound everything that runs. Enabling a lamp is a human act, never a tool an agent can call.
-
The runtimeenforces and audits. The program runs under the intersection of all three ceilings. Every check — granted or denied — returns as structured data and is appended to the record.
3The invariant
Everything reduces to one inequality, checked at run time by the layer below the program:
what runs ⊆ what the code asks ∩ what each ceiling allows
Intersection is the only composition operator. A lamp can narrow what it was given and
can never widen it; a dependency runs under its parent's effective ceiling, so composition
cannot escalate privilege. Unbounded execution (exec with no named binary) is
refused at load, unconditionally — the hole was never execution, it was execution
with nothing bounding it.
exec=gitgrantedenv=LAMP_*grantedstdoutgrantedsecret("*")refusednet(collector…)refusedfile.read("/*")refused
4The unit: a lamp
A lamp is a folder — small enough to read whole before running any of it:
| File | Role |
|---|---|
| lamp.json | The manifest: name, version, description, profile, the capability ceiling, the file list, and each tool with a JSON Schema for its parameters. This is the part read cheaply and often — by the CLI, by the agent, by the registry. |
| lamp.syn | The program. Its interface is the manifest; its interior is never read by the caller, only executed under the ceiling. Reference lamps are written in Synsema, but the protocol does not require it. |
| .pulled | Written on install: source ref, version tag, and a sha256 per file — what arrived is what is hashed, and an unchanged pull is a no-op. |
The same manifest serves the human and the agent —
lamp run git log at a terminal and lamp_git_log() behind a tool
call reach the same contract. There is no separate CLI implementation and agent
implementation to drift apart: the lamp is the source of truth.
5Inspection before execution
Because the ceiling is text in the manifest and the requests are text in the source, a lamp's promise can be checked before it is ever enabled — a lint over families, with pattern matching left to the runtime, which is the thing that actually decides:
lamp pull ./evil-formatterBREAKS ITS PROMISE — asks for 3 thing(s) it never declared: ! secret("*") ! net("collector.attacker.example") ! file.read("/*")Trust, under LAMP, never means “I trust this developer.” It means “I understand what this lamp can reach, and the runtime will hold that boundary” — even if the source is malicious, the model is confused, or the lamp has a bug.
6Distribution
A lamp is distributed as files, not as an ecosystem: the manifest lists them, the
installer fetches them one by one from a plain git host, hashes each, and records the
result. Git tags give versions (lamp pull owner/repo@v1.2.0); a registry —
when it exists — is a storefront over the same bytes, whose job is to answer the LAMP
question rather than the package-manager question: not “where do I get this?” but
“what authority am I granting to this machine or agent?” Long-term, identity
becomes content-addressed (sha256:…) so builds are reproducible and lockable.
Identity is the address: a lamp's name must equal the repo — or the folder — it lives in,
and the owner in front of every name is the host's guarantee, not a claim in a file.
7Relation to existing layers
| Layer | Relation |
|---|---|
| MCP | An adapter, not the core model. MCP defines how an agent talks to tools over a wire; LAMP defines what a tool is allowed to do. Every enabled lamp is exposed as an MCP tool with its effective ceiling stated in the description — and the same lamp works with no MCP at all, from the CLI. |
| Containers | The closest ancestor. Docker made software portable by packaging the filesystem; LAMP packages authority. The analogy is structural: spec / instance / runtime — the protocol is universal, each lamp is an instance, each runtime an implementation. |
| npm, pip, cargo… | Implementation mechanisms, never identity. A lamp may be
implemented over any ecosystem's tooling; the contract stays manifest + files + tools +
capabilities, not package.json + node_modules. |
| The shell | Still exists — as a lamp, with a ceiling and a human-written policy. It no longer defines what a tool is. Semantic capability first; shell fallback second. |
8Reference implementation
The reference CLI, lamp, is three Synsema files compiled to a single static
binary. It implements pull/update with per-file hashing, the promise linter, ceiling
intersection, enable/disable as the human act, an MCP stdio server, ad-hoc evaluation
under the session ceiling, and an append-only audit of every check. Six reference lamps —
shell, git, npm-deps, skills,
sql, http — exercise the contract; a hostile lamp exercises the
refusals. Each claim in this paper is a test in the implementation.
A lamp is a least-authority module: one thing a human runs, one tool an agent gets, one ceiling the runtime enforces.