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.
$filecannot split into two arguments.falsecannot 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
grantcannot 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.
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.
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.
| Platform | Binary | With utils | Installer |
|---|---|---|---|
| macOS arm64 | ral-macos-arm64 | ral-macos-arm64-allutils | — |
| Linux x86_64 | ral-linux-x86_64 | ral-linux-x86_64-allutils | — |
| Linux arm64 | ral-linux-arm64 | ral-linux-arm64-allutils | — |
| Windows | — | — | ral-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).