The interaction model.
The interaction model defines what an interaction is, how Refs name locations, how Interactions compose into programs. Language-agnostic, implementation-agnostic. A specification anyone can implement.
Names, and what to do with them.
A program that touches the world needs two things: names for what it touches, and descriptions of what to do with them. The model calls the names Refs and the descriptions Interactions. Every element of a program is one or the other.
ref
A name for a location.
The only atom that touches Context. A pointer to a KV slot, a DB row, a file, an in-memory cell, a UI widget. Carries the address, not the value at the address. Every read and every write in a program passes through a Ref — no side channels.
interaction
A description of what to do.
Everything a program does with Refs and with other Interactions: read, write, compute, branch, iterate, compose. Programs are trees of Interactions. Fabrics execute them against real backends.
Observability and mutation.
A program exists to mutate Context. Context changes exactly one way: a Ref in WRITE position carries a value into it. No Ref in WRITE, no landed mutation.
Two paths make an atom observable.
An atom is observable in its place iff it lands a mutation itself (own WRITE via a Ref), or it yields a value consumed by an ancestor whose chain eventually lands one.
Crossing the two paths gives four cells. Three are live — the atom kinds fall out of them. One is dead by construction and excluded from the model.
query
Yields, does not mutate.
Pure value producers: arithmetic, comparison, projection, iteration over values. A subtree may still mutate via Action descendants.
command
Mutates, yields nothing.
The pure mutator. Writes through one or more Ref children; that is the only output. Store, Copy, Append, Delete.
action
Mutates and yields.
One atomic step that writes and returns a value. Pop, Swap, INSERT…RETURNING, POST /resource → id. Splitting into Command-then-Query would lose atomicity.
Cardinality, and the kinds it produces.
The second dimension. Where mutation asks does this touch Context, cardinality asks how many values does it yield. Crossed with mutation, the two dimensions produce the full atom vocabulary.
scalar
One value.
The default. Refs are scalar (one address, one value). ScalarQuery, ScalarAction.
stream
0..N values.
Multi-value producers. StreamQuery (Map, Filter, ItemsOf) and StreamAction (Drain, DELETE…RETURNING).
void
Nothing.
No yield. Command and Flow — their observability comes from writing or from composing writers, not from returning a value.
transparent
Forwards the body.
Span wraps any atom and forwards the body’s yield in the same shape. Snapshot, Transaction, Retry, TryCatch.
The atom kinds, at a glance.
Eight concrete kinds fall out of crossing the two dimensions and adding the two composition atoms (Flow, Span).
ref
Address atom.
SCALAR. The only path to Context. Examples: user_ref, config["theme"], counter.
scalarquery / streamquery
Value producers.
Add, Eq, ref.fetch() (scalar). Map, Filter, ItemsOf (stream).
command
Pure mutator.
VOID. Writes and returns nothing. Store, Copy, Append, Delete.
scalaraction / streamaction
Mutate-and-yield.
Pop, Swap, Create (scalar). Drain, DeleteReturning (stream).
flow
Composition of mutators.
VOID. Its body slot needs a mutator, not a value. Sequential, Parallel, IfDo, ForEachDo, WhileDo.
span
Cross-cutting wrapper.
Transparent. Snapshotting, transactions, retry, fallback. Snapshot, Transaction, Retry, TryCatch.
One program, one tree.
A counter that ticks every second and a dashboard widget that mirrors it — the same example the README opens with, drawn as the tree the model actually is. No host-language syntax.
The tree is the program.
Every node is an atom. Every edge is a slot. The kinds on the right explain what each node contributes: a Flow composes mutators, a Command writes through a Ref, a StreamQuery emits values on change.
A Context binds each Ref to the Fabric that owns it — Counter.val to kv, Dashboard.count to the browser. Swap the Context, the tree does not change.
Try Nu.
One command gets you the wheel with every fabric. Then follow the movies tutorial to build a real app in an afternoon.
01 Install
pip install "nustack-py[all]"02 Run the demo
nu demo movies03 Build your app
Browse examplesLike what you see?
The project is young. Star it, join the room, watch what we ship next.