Skip to main content

Clones

A clone is one running instance of a template. It has:
FieldExampleWhat it is
idslack-a1b2Stable handle. You pass it to every command. <template>-<random hex>.
templateslackWhich template it came from.
modeapiWhich set of services is running (see below).
composeProjectasym-slack-a1b2The Docker Compose project for its containers.
databaseclone_slack_a1b2Its own database inside shared Postgres.
redisPrefixslack-a1b2:Its key namespace inside shared Redis.
ports{ backend: 3001 }Host ports per service.
staterunningLifecycle state (see below).
seed{ fixture: "acme-corp" }The last seed applied — replayed on reset.
tokens{ botToken, userToken, … }App credentials minted at spin (when the template provisions them).
image / imageDigest…slack-backend:latest @ sha256:…The published image and pinned digest the clone runs from.
exposefalseWhether services bind 0.0.0.0 or localhost-only.
A clone does not store which environment it belongs to. Membership lives on the environment (Environment.members) as the single source of truth; status derives a clone’s environment by reverse lookup.

Modes

A template declares modes — named sets of services. The first declared mode is the default. Every template ships an api mode (backend only — the lightest clone, enough for an agent that talks to the API or MCP server). The Slack template additionally ships a full mode that adds the frontend UI; the API-only templates (Stripe, Notion, HubSpot, GitHub, Linear) have api alone. Pick one with --mode:
asymmetric spin slack --mode full
Omit it and you get the template’s first declared mode.

Tokens

When a clone’s template has a provision surface (Slack today), spin calls its bootstrap endpoint once the backend is healthy and mints a default app with a bot token and a user token. They’re shown once at spin time and stored in the registry (the clone DB keeps only hashes). Reprint or rotate them with asymmetric tokens <id>. Templates without a provision surface simply have no tokens.

States

A clone moves through a small state machine:
  creating ──▶ running ──▶ stopped ──▶ running ──▶ destroying ──▶ gone

                  └──▶ unhealthy / failed
StateMeaning
creatingBeing built and started.
runningHealthy and serving.
unhealthyContainers up but the health check is failing.
stoppedContainers stopped, data preserved (asymmetric stop).
failedSpin or a lifecycle step errored.
destroying / goneBeing torn down / removed.
asymmetric doctor flags clones in non-terminal states that need a live check.

Environments

Clones compose into environments — the top-level primitive you point an agent at and score. An environment has a name, a member list (the source of truth for membership), and a rolled-up state (running / partial / stopped / failed). Spin clones straight into one:
asymmetric spin slack stripe --group acme   # both clones join environment "acme"
Or declare them in an environment.yaml and asymmetric env spin it. See Environments for the full story.

The registry

Everything above is persisted to ~/.asymmetric/registry.json — a versioned JSON file holding clones and environments, rewritten on every create / destroy / reset / seed / start / stop. It’s plain JSON and safe to read (tokens are redacted from status/ls):
asymmetric status --json
The schema is versioned and migrated on read: opening an older registry upgrades it in place (v1 → v2 renamed groups to environments and rebuilt environments from any legacy per-clone group tags, so no membership is lost on upgrade). Because the registry is the source of truth, status, doctor, and db work without touching Docker at all — they just read it. If it ever gets corrupted, asymmetric doctor is the recovery path.
Override the registry location with ASYM_HOME. Point CI at a throwaway directory so its clones never collide with your local ones.