The CLI (apps/cli)
The asymmetric command. Each verb lives in its own commands/* module and
calls into a service layer that holds a single provider. Every error funnels
through one renderer, so you get an actionable sentence and a meaningful exit
code — never a stack trace.
The contract (@repo/clone-contract)
The seam. It defines the CloneProvider interface
(create / start / stop / destroy / status / logs / seed / reset),
the data shapes (CloneRecord, CloneInstance, CloneStatus, CloneTokens,
SeedSpec, Environment, FidelityScore), the TemplateManifest, the on-disk
registry shapes, and one named error per failure mode.
The CLI depends on the contract, not on any clone. That’s what lets the same
commands drive a local Docker engine today and a remote control plane later.
See the contract reference.
Providers
A provider is the engine that actually does the work behind each verb.LocalDockerProviderruns inside the CLI and shells out todockeranddocker compose. No server, no daemon — this is the default and the only one today.CloudProvider(planned) will be a thin client to a control-plane API, implementing the same interface. Swap the engine, keep the commands.
Shared infrastructure
Spinning a clone does not start a fresh database per clone. asymmetric runs one shared Postgres and one shared Redis on a Docker network calledasym-shared, and isolates clones inside them:
- Each clone gets its own database, named
clone_<id>(hyphens in the id become underscores —slack-a1b2→clone_slack_a1b2). - Each clone gets a Redis key prefix,
<id>:. - Each clone’s services run as their own compose project,
asym-<id>, on host ports drawn from your configured range (default3000–3999).
Templates
A template is a directory with aclone.manifest.json that declares the clone’s
services, run modes, database/migrations, Redis prefix, and seed sources. Six
templates ship today — Slack, Stripe, Notion, HubSpot, GitHub, and Linear.
The CLI reads the manifest to know what to run, which ports to expose, where the
migrations live, and how to health-check the clone. See
Templates.
Backends ship as published images (ghcr.io/asymmetric-ai/…), so the CLI
pulls and runs them with no source present and pins the resolved digest — that
digest pin is what makes reset and re-spin byte-identical even as :latest
moves.
What spin does, end to end
asymmetric spin slack runs this sequence:
Resolve the template and mode
Read
clone.manifest.json; validate the requested mode (default: the
template’s first mode).Pull the image
Pull the backend’s published image and resolve it to a content digest, which
is pinned on the clone so reset/re-spin stay byte-identical.
Allocate ports
One host port per service in the mode, from your port range, avoiding
collisions with existing clones.
Compose up
docker compose -p asym-<id> up -d, injecting the database URL, Redis
URL/prefix, a random JWT secret, and the bind address. The backend runs from
the pinned image — no local build.Provision tokens
Call the clone’s bootstrap endpoint to mint a default app and its bot + user
tokens (the DB stores only hashes). Templates without a provision surface
skip this step.
spin takes one or more templates (asymmetric spin slack stripe) and can
drop them straight into a named environment with
--group.
Next: Clones, environments, and the registry and
Environments.