Skip to main content
An environment is the thing you reason about and evaluate against. A clone is the component it composes.
ENVIRONMENT  (named, version-controlled via environment.yaml)
  └── members: clone ids        ← the single source of truth for membership
        └── CLONE                isolated SaaS-app instance
              ├── backend        per-clone database + Redis prefix
              └── frontend       optional (full mode, Slack only)
Two nouns at two altitudes: the clone is the unit you build and compose; the environment is the unit you point an agent at and score. Membership lives on the environment (Environment.members), never on the clone — one source of truth, so the two can’t drift.

Shape

FieldExampleWhat it is
nameacmeThe handle you pass to env commands.
members["slack-a1b2", "stripe-c3d4"]Clone ids in this environment. The source of truth.
specacme.env.yaml or nullPath to the environment.yaml that produced it, or null for ad-hoc (spin -g) environments.
staterunningRolled up from members: running (all up), partial (some up), stopped, failed.
createdAt / updatedAtISOTimestamps.

Two ways to create one

Declaratively, from a version-controlled spec. List as many clones as you want — env spin brings them up in declared order:
# acme.env.yaml
name: acme
clones:
  - template: slack
    mode: full
    seed: acme-corp
  - template: stripe
    mode: api
asymmetric env spin acme.env.yaml
Ad-hoc, by tagging clones as you spin them — each clone joins the named environment:
asymmetric spin slack stripe --group acme   # both clones land in environment "acme"
Either way, operate the environment as a unit:
asymmetric env status             # list environments and their members
asymmetric env reset acme         # reset every member to its clean seeded state
asymmetric env destroy acme       # tear down every member
See the env command reference for the full surface.

How env spin behaves

The spin is sequential and continue-all: each clone is created and joined to the environment before the next is started, and one member’s failure doesn’t abort the rest.
  • Every create() self-persists, so a crash mid-spin still leaves a real environment holding the members spun so far.
  • A failed member is rolled back (never recorded), and the environment is stamped partial; the command exits non-zero.

The schema is strict

The environment.yaml schema rejects anything it can’t actually honor, loudly, rather than parsing it and silently doing nothing:
In the specToday
One or more clones entriesSpun, in order.
identity: (shared users/org across clones)Hard error — not supported yet.
data: (bring-your-own Postgres)Hard error — not supported yet.
An unknown keyHard error, with a “did you mean” hint.
Declared-but-unbuilt features fail loudly on purpose. For an eval product, a silently-ignored identity: block would make results quietly wrong. A clear error is the honest behavior until the feature is real.

On the roadmap

A shared cross-clone identity graph (one set of users/org spanning every member) and bring-your-own Postgres data are reserved in the schema but not yet enforced. The data model and the environment.yaml schema already make room for them, so nothing about today’s shape has to change when they ship.

The registry

Environments and clones are both persisted to ~/.asymmetric/registry.json (schema-versioned, migrated on read). See Clones, environments, and the registry.