Skip to content

Ops as teammates

llms.txt lists every page for an agent

Every IaC toolchain produces an artifact. The harder part comes after: the gated, multi-step, cross-system apply, and the rollback when step seven of nine fails. Other tools leave that orchestration to shell scripts, CI YAML, and the operator’s memory. chant lets you declare it 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.

Ask what an apply needs to survive and four different answers come back. Each lives in a different 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.

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 whose runs all go to one long-lived conversation, and that conversation’s sandbox keeps its disk across park and wake. That is a machine an environment is operated from rather than a machine somebody happens to be sitting at. chant run <op> --on e2b does the same on one E2B sandbox that keeps its clone between runs and sleeps paused in between, on hosted E2B or any API that speaks E2B’s.

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 * * * *",
});

Two 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. Managoat has no scheduler, so an op on a fountain Steward takes its cadence from a clock outside it, such as a CI schedule or a CronJob running chant run <op> --on fountain. The declaration does not change when you change your mind about which.

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.

Terminal window
chant run prod-apply # exits 3, pending on rollout-gate
chant approve prod-apply rollout-gate --actor alex # records the resolution
chant run prod-apply # reads it, walks through, applies

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

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.

FactStoreRead it with
A run: its steps, its stdout, when it started and how it endedthe runtime that hosted it — the local ledger record, or fountain’s conversationchant run status|log|list, with --on for a hosted runtime
A gate’s resolution, and anything that has to survive across runschant’s ledger branch in gitchant approve, chant run approve <op> <gate>
The declared shape of the estatethe repochant 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.

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 conversation somebody else can read, or a cadence that fires while you are asleep. The declaration is the same either way.

chant