How it works
The architecture behind a clone — CLI, contract, providers, shared infra, and templates.
Tensile Environments have four moving parts. Understanding how they fit makes every command predictable.
you ──▶ hone CLI ─────────────────────┐
(apps/cli) │ speaks
│ ▼
│ @repo/clone-contract
│ (CloneProvider, types, errors)
▼ ▲
┌──────── provider ───────┐ │ implements
│ LocalDockerProvider │──────┘
│ (shells to docker) │
└──────────┬───────────────┘
│ docker compose
▼
┌──────────────────────── asym-shared network ──────────────────────┐
│ │
│ shared Postgres shared Redis per-clone services │
│ db: clone_<id> prefix: <id>: compose: asym-<id> │
│ │
│ ┌─ slack-a1b2 ─┐ ┌─ stripe-c3d4 ┐ ┌─ … ─┐ │
│ │ backend :3001 │ │ backend :3003 │ │
│ │ frontend … │ │ (api-only) │ │
│ └───────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────────────┘The CLI (apps/cli)
The hone env command group. 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. Tensile Environments run
one shared Postgres and one shared Redis on a Docker network called
asym-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).
This keeps a fleet of clones cheap to run while keeping their data fully separate.
Templates
A template is a directory with a clone.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
hone env spin slack runs this sequence:
- Allocate an id
slack-<random hex>, unique across your registry. - 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.
- Ensure shared infra
Start shared Postgres + Redis on
asym-sharedif they aren't up. - Create the database and migrate
CREATE DATABASE clone_<id>, then run every.sqlmigration in order. - 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. - Wait for health
Poll the backend's health endpoint until healthy, or fail after 90s.
- 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.
- Record it
Write the clone to
~/.asymmetric/registry.json. On any failure, roll back: compose down-vand drop the database, so you never get a half-clone.
spin takes one or more templates (hone env spin slack stripe) and can
drop them straight into a named environment with
--group.
Next: Clones, environments, and the registry and Environments.