Userland
Userland is everything above the servers: what a person, an agent or a developer meets on the
box. A person logs in over SSH and gets an Elixir session, the shell’s interactive prompt running in a
beamlet VM of its own, in a budget of its own, with a namespace the steward built for it. An agent
gets the same kind of VM under a lease, with capabilities narrowed from its launcher’s. Native
programs, written in Rust against redoubt-rt, run in budgets the session carves for them. There
is no POSIX shell, no fork, no signals, no root and no global file tree: a session reaches
exactly what its handles name, and nothing else.
Purpose
These pages say what userland looks like from the prompt, and why. Each one takes one thing a person or agent does:
| Page | What it covers |
|---|---|
| Sessions and namespaces | logging in, the session VM and its budget, vault sessions, namespaces, approve@ |
| beamlet, the Elixir VM | the BEAM interpreter every session and agent runs in, and its boundary with the system |
| The shell | Redoubt.Shell: commands, jobs, line editing, completion, help, full-screen programs, the editor and file manager |
| Files and binds | files over 9P, what File and Path do, labels on files, binds, sharing |
| Native programs | launching Rust programs, standard I/O and pipes, killing jobs, redoubt-rt |
| Agents, leases and labels | an agent as a principal with a sponsor, leases, delegation, the agent harness |
| File transfer | SFTP and SCP inside SSH |
| Development on Redoubt | git, compilers on the box, Rust built off the box |
| Packages | pkg add, use, gc, trust lists, profiles |
The mechanisms underneath belong to other pages: handles, IPC, budgets and processes to
the kernel; the steward, the file server, sshd and the rest to
the servers.
What a person sees
Status: planned · M1 (sessions over SSH, kept apart)
Alice runs ssh alice@box. sshd and the steward authenticate her with her own key, the steward
starts her session, and she is at the shell’s prompt. The session is one beamlet VM in one budget carved
from hers; everything she types runs there, and every call it makes carries her account and the
session’s label set, which the kernel stamps and no process can forge
(R14 (unforgeable sender)).
$ ssh alice@box
/home/alice (1)> File.read!("/home/alice/notes.txt")
"buy milk\n"
/home/alice (2)> File.read!("/home/bob/notes.txt")
** (File.Error) could not read file "/home/bob/notes.txt": no such file or directory
Bob’s home is not refused; it is absent. Alice’s namespace has no entry for it, and nothing is inherited, so the path matches nothing (sessions). Bob’s session is a different VM in a different budget, and destroying either budget ends that session and nothing else (R10 (destruction)).
ssh alice+tax@box opens a vault session: one carrying Alice’s tax label, which can read
her labelled data and cannot send it anywhere unlabelled
(R1 (flow)). ssh approve@box is the one place Alice answers
requests for authority nobody has granted; only the steward talks to that terminal.
In M1 (sessions over SSH, kept apart) the session is the shell with what the milestone needs: the console, files, and launching native programs. The working shell (commands, file operations, pipes, jobs, line editing, completion, help and the editor) is the shell’s, for M2 (usable shell). Files in and out over SFTP and SCP are file transfer’s, for M4 (files in and out).
Open: none.
What an agent sees
Status: built · partly tested: a lease’s capabilities are its sponsor’s ~/project and the system volume, started by start_agent or by the sponsored principal’s login; the narrowed grants, the gateway and the harness’s tools are later · tested: bench:steward-lease-ends, bench:steward-end-lease, bench:steward-sub-agent-dies-with-agent, bench:agent-login
An agent is its own principal with a sponsor, the principal who answers for it. It runs in its own beamlet VM, under a lease: a budget with a kernel deadline, placed under its sponsor’s budget (budgets). It holds only the capabilities its launcher narrowed for it, and a sub-agent it starts holds only what it narrows again. When the lease ends, everything in it ends, sub-agents included. The design assumes every agent has been hijacked by something it read: it can do what its capabilities allow, until its lease ends, and nothing more (agents).
An agent never inherits network access. From M5 (self-hosted development) it reaches a model
provider through a gatewayd capability that holds the API key, never through a socket
(gatewayd).
What a developer sees
Status: planned · M5 (self-hosted development)
Redoubt is developed on Redoubt. A developer works in a session like any other: git reaches its
remotes through a gatewayd git capability, the Elixir and Erlang compilers run on the box in
beamlet, and Rust is built off the box. System code arrives in the signed boot bundle; a
developer’s own program arrives by SFTP and runs, unsigned, with the developer’s own authority
(development). Programs and libraries reach other principals as signed packages
from M6 (persist, install, share) (packages).
Open: none.
The layers
Status: planned · M1 (sessions over SSH, kept apart)
flowchart TB
P["a person (ssh alice@box)"] -.-> S["sshd"]
S -.-> ST["the steward"]
ST -.-> VM
subgraph VM["session: one beamlet VM in one budget"]
SH["the shell"] -.-> EX["Elixir modules: File, IO, Redoubt.*"]
EX -.-> NAT["beamlet natives and the Platform"]
end
NAT -.->|"9P and typed calls"| SRV["servers: littlefsd, consoled, ipd, ..."]
NAT -.->|"launch in a carved budget"| N["native programs on redoubt-rt"]
N -.->|"9P"| SRV
Figure: the userland layers of one session. The arrows show planned session wiring (dashed); beamlet already boots the shell on Redoubt’s UART with verified modules (beamlet).
Three layers, each with one job:
- A beamlet VM per session. beamlet is a BEAM interpreter in safe Rust. One VM is one trust domain: everything in it runs with the session’s authority, and code with different authority (an agent, a sub-agent) runs in a different VM (beamlet).
- Elixir modules. OTP’s
File,IO,Pathandgen_tcpwork unchanged over the VM’s natives. What has no POSIX equivalent (handles, namespaces, budgets, leases, labels, approvals) gets its ownRedoubt.*module and no compatibility shim. - Native programs. Rust programs, statically linked, each started in a budget the session
carves, with a namespace the session builds (native programs). Everything that
needs no address space of its own is Elixir: there is no
cporlsbinary.
Open: none.
Why
Elixir, not a POSIX shell. A shell language is a second programming language with its own quoting, word splitting and globbing, and a long history of injection bugs. A session that is an Elixir VM has one language, with real data structures, and the same code serves the prompt, a script and a program. The prompt gets short commands, plain Elixir functions, not a different language (the shell).
One VM per trust domain. Inside a VM, processes share memory-safe terms and a scheduler, so a VM cannot keep two parties apart. The kernel can: two VMs in two budgets are separated by page tables, handle tables and R1 (flow). So the unit of isolation in userland is the VM, and anything with different authority gets its own.
No inheritance. A Unix process inherits its parent’s user, file descriptors, environment and file tree, and has to shed what it should not keep. A Redoubt process starts with nothing and is handed each capability by name on its startup block. What a program can reach is what its launcher wrote down, so it can be read, audited and narrowed.