salmon
Yet another approach to xyz-dependencies: express provisioning, CI/CD and
general infrastructure operations as DAGs of idempotent operations (“ops”),
with uniform up/down/check semantics whether a node is as small as
“create a file” or as large as “turn a server up”. Run a graph once, or keep
it converged and supervised with a long-running run serve.
my-salmon config <seed-args...> | my-salmon run up # converge the graph
my-salmon config <seed-args...> | my-salmon run down # tear it down
my-salmon config <seed-args...> | my-salmon run tree # print the dependency tree
my-salmon run serve --http /run/salmon.sock # keep converging, tend the nodes, serve a web UI
Provisioning tooling tends to force a choice between one-off scripts (highly tunable, don’t compose) and do-everything tools (powerful, but their behaviour and their configuration become inseparable). Salmon’s bet: both are the same problem at different scales if operations are represented as an inspectable dependency graph rather than a flat ordered script — with the graph itself given real up/down/check semantics, so it converges to a target state (and back) instead of just running once.
Every node in that graph is the same shape, however small or large the effect
it manages — a check answering “is this already in place”, an up that
makes it so, and edges saying what has to happen first:

See it work
The clearest demo in the repository is
salmon-toy-qemu-pg-ha: three qemu guests it
builds for itself, a live Postgres primary/standby pair kept up by
salmon-pgpair, and a client that keeps writing through a bouncer while the
primary moves. Declaring a new primary drives a real switchover — pause the
bouncer, stop the old primary cleanly, promote the standby, repoint the
bouncer, rejoin the old primary through pg_rewind; killing the primary
outright is refused without an explicit --may-discard, and rewound with it.
See specs/pg-switchover.md
for the argument, and the Postgres pair guide for
what it does and does not promise (it is an operator-decided failover, never
an automatic one).
The code is on GitHub at
lucasdicioccio/salmon. It is a
Haskell cabal multi-package project: salmon-core (the graph model, no IO),
salmon-ops (the builtin nodes and the drivers, one-shot and serve),
salmon-ops-recipes (opinionated compositions) and salmon-apps (the
binaries).
What it looks like
run serve --http serves a web page over the same socket as its API: the
live graph, one box per node with its wanted direction, convergence and
last check, and a command line at the bottom that speaks the same input
language as the terminal. This is the repository’s own fixture binary,
salmon-ops-serve-fixture, with three seeds declared up:

Clicking a node opens its panel: help, paths, dependencies and dependants, the last check, the node’s own output ring (here a managed process that was just spawned), and the actions the input language offers on it:

Explore
- Getting started — build it, run the tests, and the seed → directive → ops protocol every salmon binary speaks.
- The model —
Graph,OpGraph,Track,Eval: why “create a file” and “turn a server up” are one type. - Writing ops — the cookbook for declaring and testing nodes, and the patterns that recur across recipes.
run serve— convergence, supervision, the HTTP API, the web UI, pull mode and the status sink.- A Postgres pair — a primary that is a declaration, not a discovery: switchover, refused failovers, rejoins.
- Builtins, recipes and binaries — every node module, recipe and shipped binary, one line each.
- Guides and Specs — the documentation and the design sketches, each spec headed by what of it has shipped.