Ops as teammates
Synthesis is the easy half. Every IaC toolchain produces an artifact. The hard half is what happens after: the gated, multi-step, cross-system apply, and the rollback when step seven of nine fails. The others weld a fixed lifecycle to their engine and still leave that orchestration to shell scripts, CI YAML, and the operator’s memory. chant keeps synthesis pure and holds no opinion of its own, then lets you declare the orchestration as code in *.op.ts files.
An op is a convergent verb. Running it twice is the same as running it once, because every step re-observes and re-derives what it is about to do. That one property is what decides where an op’s durability has to live, and it is why chant does not host a workflow engine.
Four things people mean by “durable”
Section titled “Four things people mean by “durable””Ask what an apply needs to survive and four different answers come back. They are not one feature, and they do not live in one place.
The first is the outcome, and it is the op itself. The apply has to reach a declared state and stay there; a convergent verb gets you there by re-running, so a run that dies halfway is answered by running it again rather than by resuming it in place. There is nothing to check-point because there is no mid-sequence position worth remembering. The other three are the subject of this page.
The machine
Section titled “The machine”Something has to be running when the op runs, with a checkout and the credentials the work needs. A laptop is a fine machine for a change somebody is watching, and a bad one for anything that has to happen while nobody is. chant run <op> --on fountain hands the run to a steward, one agent on a persistent sandbox, which is a machine an environment is operated from rather than a machine somebody happens to be sitting at.
The cadence
Section titled “The cadence”Some ops run because a clock said so. That is schedule on the op, plain data on the declaration:
export const { op: prodWatch } = WatchOp({ name: "prod-watch", env: "prod", schedule: "*/15 * * * *",});Three readers render that same field. The github generator turns it into a workflow schedule: trigger. chant operator ticks it from a loop on your own machine. A fountain Steward turns it into a Schedule on the teammate. The declaration does not change when you change your mind about which.
The approval, and why a wait is a fact
Section titled “The approval, and why a wait is a fact”The fourth is somebody saying yes, and it is the one that used to drag a whole engine in behind it. A workflow engine’s gate is a suspended workflow waiting for a signal. That works, and it costs a live workflow plus a worker willing to hold it open for as long as the decision takes.
chant makes the same gate a pair of records on the gate ledger. A run that reaches an unapproved gate reads the ledger. Finding no resolution newer than the pending fact, it writes that fact if there is not one standing already, and exits 3. Nothing is suspended, so nothing has to survive being suspended.
chant run prod-apply # exits 3, pending on rollout-gatechant approve prod-apply rollout-gate --actor alex # records the resolutionchant run prod-apply # reads it, walks through, appliesExit 3 is deliberately not 1. A gate is a decision nobody has made yet, so a CI job that reads 3 reports “waiting on approval” instead of paging whoever is on call. Ordering is what makes it safe: a resolution counts only if it is newer than the pending fact it answers, so approving in advance does not clear a gate recorded since.
This is why a steward can be one computer rather than one per pending decision. Nothing sits blocked inside a sandbox holding a machine open, which is exactly what a durable wait would have cost.
Two stores, and nothing stored twice
Section titled “Two stores, and nothing stored twice”Once an op can run in two places, the question is which of them remembers what. chant answers it with a line rather than a merge.
| Fact | Store | Read it with |
|---|---|---|
| A run: its steps, its stdout, when it started and how it ended | the runtime that hosted it — the local ledger record, or fountain’s conversation | chant run status|log|list, with --on for a hosted runtime |
| A gate’s resolution, and anything that has to survive across runs | chant’s ledger branch in git | chant approve, chant run approve <op> <gate> |
| The declared shape of the estate | the repo | chant build, chant lifecycle diff --live |
A run belongs to the runtime that ran it; a fact about the world belongs to git, where it outlives every runtime that reads it. That split is what lets the same op run locally on Tuesday and on a steward on Wednesday without either of them losing track of an approval the other recorded.
Where to start
Section titled “Where to start”Lead with the local runtime. It needs nothing installed, it runs the whole phase grammar including gates and compensation, and it is the same executor a steward runs inside its sandbox. Move an op to --on fountain when it needs a machine that is not yours, a thread somebody else can read, or a cadence that fires while you are asleep. The declaration is the same either way.
See also
Section titled “See also”- Ops — declaring orchestration in
*.op.ts - Choosing Your Deployment Model — whether Ops fit at all
- The Steward — the hosted form, declared
chant operator— ticking aConvergeOpon git alone- State and Governance — why no authoritative state file is needed