pure Rust · AMP-native · zero SDK

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
  • run raises a catchable error
  • No $? guessing games

First-Class Functions

  • Named + anonymous lambdas
  • Terse fn($x) = expr form
  • HOFs: map / filter / reduce
  • Closures capture scope

Native AMP Messaging

  • send / emit keywords
  • address / 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 .*.mix format
  • Configs, specs, journals
  • Round-trip serialization
  • Data files stay inert data
AMP broker node1 mix --serve node2 on noded.ping node3 send … node4 emit …
AMP mesh: each node runs a broker; 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-mix interpreter
  • cosmix-mix the 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.

Source .mix text Lexer tokens Parser AST Evaluator values
The interpreter pipeline — all three stages live in cosmix-lib-mix, pure Rust.
input line $-sigil or (parenthesized)? yes Mix statement $x = 1 no shell command x = 1 misread as command!
The shell-first classifier: parenthesized or $-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 interpolation
endcloses every block
and or notlogical keywords
fn($x) = …terse single-expr lambda
send / onAMP messaging keywords
-- commentline comments (no /* */)
newline / ;Mix statement separators

An 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.

fanout.mix
-- 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

shell
# 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")'