Orchestration
The orchestrator runs a set of components in dependency order, dispatching each step to its capability. It contains no per-component code. There are two modes, and both make pipeline count independent of component count.
Interpret mode
Section titled “Interpret mode”chant generates one orchestrator Op that reads the declarations, orders them with chant graph --components (dependency order from each component’s dependsOn, plus parallel-safe waves), and drives each composition. No per-component pipeline files exist.
The orchestrator is an Op, so it runs local by default on the in-process executor. Phase sequencing, retries, activity profiles, and onFailure saga rollback all work locally with nothing installed.
Gate approvals and environments
Section titled “Gate approvals and environments”A component’s gate ledger is keyed by the component, so it holds the facts for every environment. Each approval is bound to the environment and the plan digest it was given for (#2574), the way an Op gate binds its plan. A run that reaches a gate records a pending fact with both, and chant approve <component> <gate> --env <env> copies them onto the resolution. Without --env, the newest pending fact is approved.
A component has no Plan phase, so its plan digest is taken over what the run will execute in that environment:
| Covered | Left out |
|---|---|
| the environment | outputs of earlier phases in the run, such as a fresh build’s digest |
the deploy and rollback phases, with every step’s authored input | outputs of upstream components |
| the recorded digest a promote, rollback or redeploy deploys | the run id and the time |
environment config passed as vars |
A release recorded before real SHA-256 digests (#2514) keeps its legacy-digest value, and the plan takes that value as recorded. The plan digest itself is always real SHA-256, written as jcs1-sha256: and the hex of the SHA-256 of the RFC 8785 canonical JSON of the plan (#2547). Before that it was sha256: and the same hex. A pending gate or an approval recorded under the bare prefix still matches the plan with the same hex, and nothing in the ledger is rewritten.
A deploy to prod does not accept an approval given for staging. A promote does not accept one given for a plain deploy or for another digest, and an edited composition does not accept one given before the edit. chant run --components, chant components promote, chant components rollback, chant components redeploy and the components inside chant components fan-out all decide gates this way.
An approval recorded before this change records neither an environment nor a plan, and is refused. The run stops at the gate, names the old approval, and prints the command that replaces it:
Gate "release" is approved, but not for this plan. approved: (none — recorded before plan-bound gates) (by alex at 2026-09-20T10:00:00.000Z); planned: jcs1-sha256:4c1e0b...That approval predates environment- and plan-bound component gates (#2574) and records neither, so it cannot answer for this one. Approving again binds it: chant approve search-service release --env prodv0.80.0 warned about it. It printed one warning per component and gate when a gate passed on an approval that recorded no plan.
Generate mode
Section titled “Generate mode”The same declarations synthesize plain CI YAML through the GitHub, GitLab, or Forgejo lexicon. Pipeline files still exist, but nobody maintains them by hand, and a cross-cutting change is one edit to the generator. The generated YAML is a thin trigger and runner; the deploy logic stays in the component, not the YAML.
The two compose: interpret mode behind a thin generated CI trigger.
In a workspace, each member gets its own pipeline per environment. It keeps the same triggers and runs in the member’s directory, as chant build --components --generate describes.
Generate mode has an Op counterpart too, see Scheduling an Op. A scheduled Op’s CI trigger is not limited to cron: pull_request and push triggers are generic beyond the cron case, for an Op whose natural trigger is a PR or a push to a branch rather than a clock.
See Execution Backends for the backend interface and the general-consumption default profile.
Rollback comes free
Section titled “Rollback comes free”Because each capability declares its own compensation, the orchestrator gets saga rollback without a hand-written rollback pipeline: on failure it unwinds executed steps in reverse and calls each rollback. Native rollbacks are reused where the platform provides them — cfn-deploy triggers CloudFormation’s own rollback-stack, code-deploy triggers CodeDeploy’s own automatic rollback to the last known-good revision. Where a capability has no native rollback (ecs-update-service, lambda-deploy), it records the prior state before it changes anything and its compensation puts that state back. ecs-update-service reads the service with describe-services before update-service and keeps the task definition the service was running in its step output; its rollback sends that task definition, never the step’s own imageRef, which is the revision the step just rolled out. When that read got no task definition back, the rollback fails with an error that names the service. A component that needs a different restore supplies its own component-level rollback phase, such as rollback-previous with the taskDefinition to return to. Non-auto, non-compensable cases opt out with a stated reason (noRollback: "<reason>" on the step) rather than failing silently — the COMP003 lint rule requires the reason at author time, and the driver reports the same fact at run time: a step whose capability has no rollback gets its own rollback-opted-out record in the run result, carrying that reason, rather than being passed over without a trace.
Rolling back to an earlier release
Section titled “Rolling back to an earlier release”The unwind above belongs to one run: when that run fails, the steps it executed are undone in reverse, and nothing of the unwind outlives the process. Taking an environment back to last week’s release is out of its reach, because the only work it knows about is the run it is part of.
chant components rollback <env> --component <name> covers that case. It reads the environment’s release ledger, picks an earlier release (the one before the current digest, or the one --digest names), and deploys that release’s recorded digest through the component’s own composition as a new, forward run. Build steps do not run, and the publish step is replaced by the recorded digest, since the environment received that artifact when the release was first deployed. Gates in the composition apply to the rollback as they do to any deploy. A successful rollback appends a release record whose restores field names the release it brought back, so the ledger shows it as a rollback rather than as an unexplained return to an old digest.
The two also differ in what survives. A rollback is an ordinary run with its record on the ledger, so a crash partway through leaves the ledger saying what was last recorded, and running the command again starts it over. If the rollback run itself fails, its own unwind runs as usual.
Recovering from a failed deploy
Section titled “Recovering from a failed deploy”A deploy writes its release record only once it succeeds. When one fails partway, whether it came from chant run --components or from a promote or rollback, the ledger still names the last good release as current. The environment may meanwhile be running part of the failed one, if the unwind’s compensations were best-effort or the process died during it. Here chant components rollback is no help, since it always moves to a release other than the current one.
chant components redeploy <env> --component <name> deploys the release the ledger records as current, by the same forward run a rollback uses and with the same gates. Its record carries a redeploys field naming that release.
Fanning a change out
Section titled “Fanning a change out”A change in one component reaches everything downstream of it, in an order chant reads off the source rather than one anybody wrote down. A child declares stackOutput() and its parent reads that output back; a component names its upstreams in dependsOn. Both are already in the declarations, so the order is a walk over them. The alternative shape is a registry where you state that B depends on A, which is a second statement of a relationship the source already makes, and it is wrong the first time somebody adds a reference without updating it.
A change signal becomes a run in three moves. An artifact diff between two git refs says which stacks moved, which is what chant lifecycle affected answers. Each component claims the stacks its own deploy steps target, so that answer resolves to components with nothing declared twice. The graph walk then adds everything transitively downstream of those components and layers the result into parallel-safe waves, seeding the outputs of dependencies that are not themselves running.
Every component lands in one of three outcomes, never two. It is selected, it is unaffected, or it is indeterminate because its inputs arrive at deploy time and no source diff can judge it. One the walk reaches is selected like any other dependent, since reachability is a fact about the graph rather than about whether the component’s own inputs could be read. One the walk does not reach is reported and left undecided. Fanning out to everything on any change is easy to build, worth nothing, and reads much like diligence in a log.
chant components fan-out is the command. It prints the derivation before dispatching anything, and --gate <name> puts a single approval over the whole ordered set rather than one per component. A component that fails stops its own subtree, while branches sharing no edge with it run to completion.
What chant adds over hand-driving it
Section titled “What chant adds over hand-driving it”- Semantic lint on the composition. Publishes-but-never-applies, an
imageRefto a phase that does not exist, a mutating capability with no rollback and no opt-out, an unacknowledged gate, a capability named after a component. These are the long-term entropy guards a plain JSON contract cannot enforce — see the Composition lint rules (COMP001–COMP007). - The graph and the generated driver.
chant graph/describeover components;chant buildemits the orchestrator Op.
Read next
Section titled “Read next”- Execution Backends — the backend interface and the general-consumption default profile.
- Cloud-Agnostic Boundary — what ports across clouds and what does not.
- Ops as teammates — the runtime trade-off in depth.