Lifecycle¶
What happens between zordon start and a READY stack, and how
reconfigure / stop behave.
zordon start¶
- Discover chain — walk up collecting Alphasfiles (root-first),
plus optional
~/.zordon/Alphasfile. - Per level, top-down:
- Build the
Invocation(hash, state dir, tmp dir, sockets). - Take the level's
flock. - Probe a running
alphaon the socket.- Healthy + same config hash (non-invocation level) → reuse, accumulate its resolved services into the parent context, move on.
- Drift (hash changed) or the invocation level → ask the old alpha to stop, wait for the socket to go away.
- Resolve the Alphasfile with the accumulated parent context.
- Spawn
alpha, wait for its READY handshake. - Send Configure (resolved services, config hash, parent
dotenv) and stream events until
Done. - Accumulate this level's services → context for the next level.
- Locks release in reverse order;
zordondetaches.alphaprocesses keep running in the background.
Per-service bringup (inside alpha)¶
For each service in Configure order:
- Skip if unchanged — already running and neither its code nor manifest changed since the last Configure → left untouched (another service changing does not rebuild it).
- Prepare
git/src: ensure the primary (bare-clone on first use), thengit worktree addinto…/src/<svc>on branchzordon/<wt>(sparse cone if aworkspace { sparse = … }block is set).crate: no checkout.- Build the toolchain default (or
build { cmd = [...] }override) intofs::bin()— see Services. - Materialize files —
file{}blocks written atomically (temp + rename). - Start the process (
runtime { cmd }if given, else the built binary), cwd = the service working dir (<checkout>/<exe>), environment composed (see below). - Readiness — a
readinessprobe (http {…},exec {…}, ortcp {…}) gates READY; without one, "alive for--stabilization" (default1s) counts as ready. - Stdout/stderr stream back to
zordonas events. On--failfast(default on) a failed bringup aborts the rest and shuts the level down.
Environment precedence¶
Lowest to highest, last wins:
alpha's process environment, filtered bysysenv(closed-world whitelist — anything not listed is stripped; defaults to empty)- toolchain env —
mise env --jsonoutput for the pin the service runs under (its component's owntoolchain { }, else its package'stoolchain { }, else the top-leveltoolchain.<lang>.version), with per-language pin reinforcement (e.g.GOTOOLCHAIN=localfor Go), then the user'stoolchain.<lang>.envoverlay - federation-parent file-level
dotenv(root-first) - this Alphasfile's file-level
dotenv - file-level
env { }— the top-level inline overlay applied under every service (federation parents merged root-first with the deeper level winning, then this Alphasfile's own). The dotenv-less way to set a variable for the whole stack; inline beats the file-leveldotenvabove it, just as a service'senvbeats itsdotenv. - the service's
dotenv - the service's
env { }block, then the matching phase'senv { }(runtime/build), and finallyagent { env { } }when alpha runs in--agentmode
The toolchain is the initial environment: it lays down PATH /
GOROOT / GEM_PATH / GOTOOLCHAIN / etc. so language tooling resolves to
the pinned install. Per-service config (dotenv, env, phase env)
layers on top, so users override toolchain defaults — never the other
way around. A service's own dotenv/env likewise wins over the
file-level dotenv/env: the global tier is a default, not an override.
All of dotenv/env (file-level and per-service) are interpolated and
DAG-ordered like any other field. The file-level env and dotenv are
runtime-only — like dotenv, they do not reach the hermetic build phase
(use build { env } or toolchain.<lang>.env for builds).
Reconfigure¶
A second zordon start on a still-running level sends a fresh
Configure. alpha diffs it: services missing or whose resolved config
changed are stopped (stopServices: SIGTERM → grace → SIGKILL, only
those), new/changed services are brought up, untouched services keep
running — a change in one service never restarts another. Rust keeps
a stable CARGO_TARGET_DIR so rebuilds stay incremental.
Picking a subset¶
zordon start <pick> [...] filters the invocation level down to the
picked services plus the transitive closure of their runtime.after and
provision.after deps — federation parents are unaffected and always
come up in full (shared infra). Anything not in the closure is omitted
from the Configure payload, so on reconfigure alpha treats those as
"removed from the manifest" and stops them. A subsequent
zordon start with no picks brings the rest back up. Unknown picks
fail before contacting alpha and list the services, components and packages
that exist.
| pick | selects |
|---|---|
<svc> |
the service named svc wherever it lives, when no other service has that name |
<c> |
every service of component m |
<p> |
every service of package p |
<p>/<c> |
every service of component m of package p |
m/<svc>, p/m/<svc>, p/<svc> |
one service by its full or short name |
A pick that selects different services in two of these readings — two
services named worker in two packages, or a package and a service that
share a name — is refused with every candidate listed; pick by full name
instead.
zordon plan <svc> [...] accepts the same picks and renders exactly
that subset statically — a preflight of "just what I'm about to start"
without spawning alpha. Parents still render in full.
The selection can equally come from the --services flag or the
ZORDON_SERVICES env var (comma- or space-separated), merged with any
positional args. The env var is the only channel for injecting a subset
where you control the environment but not argv — e.g. from a Claude Code
hook driving zordon.
zordon sudo¶
Out-of-band, never during start. Walks the chain, reads each running
alpha's resolved sudo steps, and per step runs check (no sudo) —
skip if it passes — else apply via sudo then optional verify.
Idempotent: steady state prompts for nothing. See
Federation.
zordon stop¶
Stops only the invocation level (parents are shared infra). alpha
SIGTERMs every child, waits the grace period, SIGKILLs stragglers, then
unlinks the generated files and exits. zordon status reports the
whole chain regardless.
zordon stop --clean is sugar for zordon stop followed by zordon clean:
it brings the stack down, waits for it to actually exit, then runs the
teardown snippets (see below).
zordon clean¶
Runs each provision's clean
teardown snippet for the invocation level — the inverse of the side
effects bringup created (drop a database, delete a topic, remove generated
files). Like zordon stop, shared parent levels are left untouched.
Clean operates on a stopped stack. A still-running stack is refused
(zordon stop first, or use zordon stop --clean). Under the hood it spawns
a transient alpha that materializes toolchains and rebuilds each service's
env/cwd without starting the services, runs the clean snippets in
reverse declaration order (teardown is the inverse of bringup), then shuts
that alpha down. A failed clean is reported but does not abort the rest.
Provisions without a clean snippet are skipped. Snippets that reference a
provision's invoke-time arguments are run as-is — there is no per-invoke
argument context during a batch clean.