ral — a Unix shell grounded on algebraic effects

4 min read Original article ↗

Running a command is an effect.

value

A value is.

A string, list, record, variant, or block; immutable. Retrieve it; it cannot act by itself.

command

A command does.

A computation that may read, emit bytes, return, or fail. Force it; the surrounding scope supplies its interpretation.

Effect handlers

The program stays put; its world changes.

Both programs below test a deployment without touching the network. Bash makes curl a global shell function and must remember to remove it. ral installs another interpretation of curl for one dynamic extent. At the closing brace, the operating system is the interpretation again.

Values, not words

Data is not a command waiting to happen.

Most shells make safe argument passing a matter of quoting discipline. ral removes word splitting from the language. $ARGS is a list of strings, and for receives those exact values: carol smith cannot fracture into two names.

native sandboxing

The sandbox is part of the language.

grant wraps a block with narrower authority over commands, filesystem paths, and the network. Every function and command called inside inherits that boundary. A nested grant intersects with its caller; it can take authority away, never give it back.

grant [
    exec: [git: ['status']],
    fs: [read: ['cwd:'], write: []],
    net: false,
] {
    git status
}

This is enforcement, not metadata. ral checks its own redirects and dispatch in-process; external and bundled commands enter Seatbelt on macOS, bwrap on Linux, or an AppContainer on Windows. If a requested restriction cannot be enforced, the command fails closed.

The difference

Everyday use looks like a shell. The difference is what cannot happen.

  • $file cannot split into two arguments.
  • false cannot masquerade as a failed command.
  • A byte/value pipeline mismatch cannot start half a pipeline.
  • A spawned block cannot race a shared mutable variable (there are none).
  • A nested grant cannot widen its caller's authority.
  • > cannot leave a regular file half-written.

Two pipes, one bar

The pipe follows the data.

cat log | grep ERROR remains a genuine Unix byte pipeline. range 1 21 | filter $even | sum is typed function composition. Codecs such as from-json name the exact point where bytes become values; a mismatched edge is rejected before any process starts.

Failure travels on a different axis. | moves data; ? chooses a fallback after failure; if branches on a Bool. Falsehood and failure never trade places.

examples

90 ordinary jobs, written twice.

Paths with spaces. grep returning 1. Atomic file updates. Quoted CSV. Cancelled races. Scoped mocks. Network-denied builds. Each example names the Bash footgun, then places the Bash and ral programs side by side.

Open examples

exarch

A shell an agent can be given.

exarch executes every model action as a ral program under a grant installed by the host. The agent may use that authority but cannot widen it; the execution tree is the audit. exarch does not bolt a sandbox around ral — it uses the shell's own semantics.

Open exarch

get ral

curl -fsSL https://lambdabetaeta.github.io/ral/scripts/install.sh | sh

Batteries included. The “with utils” build folds a curated set of uutils coreutils — the cross-platform Rust reimplementation of GNU coreutils — into ral (ls, cat, cp, mv, etc.). diffutils, grep, and ripgrep are bundled too.

PlatformBinaryWith utilsInstaller
macOS arm64ral-macos-arm64ral-macos-arm64-allutils
Linux x86_64ral-linux-x86_64ral-linux-x86_64-allutils
Linux arm64ral-linux-arm64ral-linux-arm64-allutils
Windowsral-windows.msi

Not POSIX, on purpose. POSIX compatibility requires word splitting, $IFS, implicit glob expansion, and context-dependent quoting. Those are precisely the mechanisms ral refuses. Bash scripts do not run unchanged.

The value–command split comes from call-by-push-value. Treating an external command as an operation whose interpretation is supplied separately gives ral algebraic effects.

The specification says what ral does. The rationale says why.

Funded by the Advanced Research + Invention Agency (ARIA).

ARIA — Advanced Research + Invention Agency