Mix
An ARexx-inspired, AMP-native shell & scripting language
An AMP-native shell where every variable is $-sigil, concatenation is .., blocks close with end, and command results are structured maps — not string soup. Network messaging is part of the grammar.
A shell and a language, in one binary
Glue scripts, daily-driver shell, and mesh orchestration — the same tool covers all three
Mix is a lightweight orchestration language for controlling distributed systems: a pure-Rust, ARexx-inspired scripting language and interactive shell with first-class network messaging built into the grammar. It runs as an ordinary scripting language, as a login shell, and as a supervised mesh citizen — and command results come back as structured data, never $? string soup.
Shell + Language
- Daily-driver login shell
- Real scripting language
- Shell-style command running
- REPL meta-commands
Structured Returns
run_rc→ {rc, stdout, stderr}- Never raises — branch on a field
runraises a catchable error- No
$?guessing games
First-Class Functions
- Named + anonymous lambdas
- Terse
fn($x) = exprform - HOFs: map / filter / reduce
- Closures capture scope
Native AMP Messaging
send/emitkeywordsaddress/on … end- Reaches a peer in one line
- No SDK, no socket wiring
Collapse the tool pile
One language for what used to take six
Controlling systems across many machines normally means stitching together shell scripts, SSH, Ansible, REST/gRPC clients, message queues, and hand-rolled networking — each its own glue, its own failure mode, its own mental overhead. Mix makes network communication a core part of the language rather than something bolted on, so coordinating remote machines and services feels like writing a plain local script. It is the agent-operable control surface for the cosmix substrate.
Less boilerplate
- No repeated socket wiring
- No retry / serialization glue
- No service-to-service plumbing
- Mix handles it underneath
Agent-operable
- The substrate's control surface
- Every step is observable
- Structured data in / out
- Drives daemons from outside
Rust-based safety
- Memory-safe by construction
- Saner concurrency model
- No embedded foreign VM
- Single-threaded, predictable
Data substrate
- Strict-data
.*.mixformat - Configs, specs, journals
- Round-trip serialization
- Data files stay inert data
send noded.node1.amp … routes service-to-node addresses across the mesh — no SSH, no SDK.Lineage from ARexx
Every app a message port — the 1987 Amiga idea, on a modern mesh
The ARexx model from the 1987 Amiga was simple and powerful: every app is a named, addressable port, and one script orchestrates across all of them. Mix carries that ergonomic precedent forward onto a WireGuard mesh of Rust-native daemons with AMP markdown framing. Built for the cosmix substrate, Mix is its ARexx-equivalent and primary control surface. Lua was the original implementation sketch — it was deprecated and fully removed once Mix reached parity. Today Mix lives as two crates: cosmix-lib-mix (the interpreter) and cosmix-mix (the mix binary).
1987 · ARexx
- Every app a message port
- One script across many
- The ergonomic precedent
Lua, removed
- The original sketch
- Deprecated at parity
- Pure Rust now, no VM
Two crates
cosmix-lib-mixinterpretercosmix-mixthe binary- Versioned in lockstep
Lex → Parse → Evaluate
A shell-first classifier decides command vs. Mix statement
Source text flows through a three-stage pipeline. The lexer tokenises sigils, ${var} interpolation, heredocs, numbers, comments, and the AMP keywords. The parser builds the AST, handling postfix chains so $users.sort_by(...).take(10) parses correctly. The evaluator walks the tree single-threaded — on handlers serialize, one at a time, with events queued FIFO. In shell contexts a classifier decides whether a line is a shell command or a Mix statement — which is why bare x = 1 is misread, and every variable needs the $ sigil.
cosmix-lib-mix, pure Rust.$-sigil lines parse as Mix; everything else dispatches as a shell command. So always write $x = 1, never bare x = 1.Sigils & operators at a glance
$xevery variable is $-sigil..concatenation (never + / .)${var}interpolation inside "…"'raw'fully literal — no interpolationendcloses every blockand or notlogical keywordsfn($x) = …terse single-expr lambdasend / onAMP messaging keywords-- commentline comments (no /* */)newline / ;Mix statement separatorsAn AMP fan-out, in Mix
Ping every node with a lambda, keep the ones that answer
A terse lambda inside a higher-order builtin, an AMP send per node, and a structured result — no socket code, no retry loop, no serialization. This is the whole program.
-- ping a list of nodes over the AMP mesh, keep the live ones
$nodes = ["node1", "node2", "node3", "node4"]
-- a multi-statement probe must be var-bound before a HOF
$probe = function($n)
send ("noded." .. $n .. ".amp") noded.ping timeout=2000
return { name: $n, up: ($rc == 0) }
end
$results = map($nodes, $probe)
$live = filter($results, fn($r) = $r["up"])
print(length($live) .. " of " .. length($nodes) .. " nodes up")
Build & install
No build step for the docs; the binary builds with cargo
# Mix depends on the amp library family — sibling checkouts under $HOME
git clone https://github.com/markc/amp ~/.amp
git clone https://github.com/markc/mix ~/.mix
cd ~/.mix/src && cargo build --release
# install the binary
sudo install -m 0755 ~/.mix/src/target/release/mix /opt/cosmix/bin/mix
# run a one-liner
mix -c 'print "hello, " .. env("USER")'