> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getasym.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# @repo/clone-contract

> The shared contract — the seam every part of asymmetric is built against.

`@repo/clone-contract` is the internal contract the CLI and the (planned) control
plane are built against. It defines what a clone *is* and how a provider drives
one, with no runtime or Docker specifics — that's what lets local and cloud share
one command surface. This page documents that contract for reference, so you
understand how clones behave when you point an agent at one.

## The `CloneProvider` interface

The seam. Every method maps to a CLI verb, and implementations must throw a
[named error](/reference/errors), never a bare `Error`.

```ts theme={null}
interface CloneProvider {
  readonly kind: 'local' | 'cloud';

  create(spec: CloneSpec): Promise<CloneInstance>;
  start(id: string): Promise<void>;
  stop(id: string): Promise<void>;
  destroy(id: string): Promise<void>;
  status(id: string): Promise<CloneStatus>;
  logs(id: string, opts?: LogOptions): AsyncIterable<LogLine>;
  seed(id: string, spec: SeedSpec): Promise<SeedResult>;
  reset(id: string): Promise<void>;

  // Phase 2, optional — LocalDockerProvider may omit:
  snapshot?(id: string, name: string): Promise<void>;
  restore?(id: string, name: string): Promise<void>;
}
```

Two implementations are envisioned: `LocalDockerProvider` (today — runs in the
CLI, shells to Docker) and `CloudProvider` (planned — a thin client to the
control-plane API). Same interface, different engine.

## Core types

### `CloneSpec` — input to `create`

What the caller wants; the provider fills in the allocated fields.

| Field        | Type        | Notes                                                                      |
| ------------ | ----------- | -------------------------------------------------------------------------- |
| `template`   | `string`    | Must exist in the catalog.                                                 |
| `mode?`      | `CloneMode` | Defaults to the template's first mode.                                     |
| `name?`      | `string`    | Else a stable id is generated.                                             |
| `group?`     | `string`    | [Environment](/concepts/environments) to join, by name; creates it if new. |
| `seed?`      | `SeedSpec`  | Seed on create; omit to start empty.                                       |
| `portRange?` | `PortRange` | Override the default range.                                                |
| `expose?`    | `boolean`   | Default `false` (localhost-only).                                          |

### `CloneRecord` / `CloneInstance`

`CloneRecord` is the JSON-serializable row persisted to
`~/.asymmetric/registry.json`: `id`, `template`, `mode`, `composeProject`,
`database`, `redisPrefix`, `ports`, `state`, `seed`, `expose`,
`image` / `imageDigest`, `tokens`, and `createdAt` / `updatedAt` / `syncedAt`. A
clone does **not** carry its environment — membership lives on the
[`Environment`](#environments) as the single source of truth. `CloneInstance` is a
`CloneRecord` plus resolved `endpoints` (`api`, `web`) — what `create` returns.

Three fields are optional and additive (older records and templates without them
simply omit them):

* `image` / `imageDigest` — the published image ref the clone was spun from and
  the content digest (`<repo>@sha256:…`) resolved at spin time. Pinning the digest
  keeps `reset` byte-identical even as `:latest` moves; `asym upgrade` repins to the
  current image. `imageDigest` is `null` for local builds with no registry digest.
* `tokens` — programmatic-access credentials minted by the clone's default app at
  provision time (`appId`, `workspaceId`, `botUserId`, `adminUserId`, plus a
  Slack-shaped `botToken` / `userToken`). The clone DB stores only hashes, so these
  are persisted here (in the 0600 registry) for `asym tokens <id>` to reprint.
  They're **redacted** from `status` / `ls` output and shown by `spin` / `tokens`.

### `CloneState`

```
creating → running → stopped → running → destroying → gone
running → unhealthy → running   (health flaps)
creating → failed
```

`gone` is terminal and never persisted — it lets `status` report a clone that
vanished outside the CLI, which `doctor` reconciles.

### `SeedSpec` / `SeedResult`

```ts theme={null}
interface SeedSpec {
  fixture?: string;              // deterministic SQL pack
  ai?: AiSeedSpec | boolean;     // true = template defaults
}

interface AiSeedSpec {
  entities?: string[];                                  // e.g. ["users","channels"]
  volume?: 'small' | 'medium' | 'large' | number;
  model?: string;                                       // e.g. "claude-haiku-4-5"
  prompt?: string;                                      // steering brief
}
```

`SeedResult` reports `written`, `rejected`, a `bySource` breakdown, and
`warnings[]`. AI partial failures are **reported here, not thrown** — a few bad
records don't fail the seed.

### `CloneStatus` / `ServiceStatus`

The live health view (distinct from the persisted `CloneState`): per-service
runtime `state` (`running` / `exited` / `unhealthy` / `missing`), `port`, and
`healthy`, rolled up into a clone-level `healthy` boolean.

### Environments

`Environment` is the top-level composition primitive: `name`, `members` (clone ids,
the source of truth for membership), `spec` (path to the `environment.yaml`, or
`null` for ad-hoc), `state`, and timestamps. `EnvironmentState` is
`running` / `partial` / `stopped` / `failed`. `EnvironmentResult` (per-member
outcome) is reserved for the deferred multi-clone engine (e.g. an `env reset` loop).
See [Environments](/concepts/environments).

### `RegistryFile`

The on-disk shape of `~/.asymmetric/registry.json`: a `version`, an array of
`clones` (`CloneRecord[]`), and an array of `environments` (`Environment[]`).
`REGISTRY_VERSION` is `2`; the CLI migrates older files on read (v1 → v2 renamed
`groups` to `environments` and rebuilt environments from legacy per-clone group
tags, so upgrades lose no membership).

### `FidelityScore`

Surfaced by [`verify`](/cli/login#verify) and produced by the `/verify-api`
workflow: `score` (0–1 coverage), `operations` (`ours` / `theirs` / `matched`),
`checkedAt`, and `source`. Cached under `~/.asymmetric/fidelity/<template>.json`,
and never hard-gates `spin`.

## The `TemplateManifest`

How a template declares its services, modes, database/migrations, Redis prefix,
and seed sources — a `clone.manifest.json` at the template's app root. Walked
through in [Templates](/concepts/templates). This reference is provided so you
understand how clones are structured; templates themselves are built and
maintained by asymmetric.

Each service declares either a `build` context (built locally) or a published
`image` ref (pulled, with no source present) — `image` is the distribution
default, e.g. `ghcr.io/asymmetric-ai/asymmetric-slack-backend:latest`. A service
marked `optional: true` is only run in the modes that name it.

Six templates ship today, all on the same manifest shape:

| Template | `realProduct` | Modes         | AI seed entities                      |
| -------- | ------------- | ------------- | ------------------------------------- |
| Slack    | `slack`       | `api`, `full` | users, channels, messages             |
| Linear   | `linear`      | `api`         | users, teams, issues, projects        |
| Stripe   | `stripe`      | `api`         | customers, products, prices, invoices |
| Notion   | `notion`      | `api`         | users, databases, pages               |
| HubSpot  | `hubspot`     | `api`         | contacts, companies, deals            |
| GitHub   | `github`      | `api`         | users, repositories, issues           |

Slack is the only one with a `full` mode today — it ships a `build`-based
`frontend` service alongside its image backend. The other five are backend-only
(`api`) services pulled as images.

## Design rules

<CardGroup cols={2}>
  <Card title="Providers run client-side" icon="microchip">
    `LocalDockerProvider` shells to Docker from the CLI. `CloudProvider` is a thin
    client. The interface is the only shared surface.
  </Card>

  <Card title="Throw named errors" icon="triangle-exclamation">
    Never a bare `Error`. The CLI switches on the error `code` to render a clear
    message. `if (isAsymmetricError(e)) ...`
  </Card>

  <Card title="Records are JSON" icon="brackets-curly">
    Everything in `CloneRecord` is JSON-serializable — it lands verbatim in the
    registry file.
  </Card>

  <Card title="One label" icon="tag">
    Docker resources carry the `asym.clone` label so they're discoverable and
    reconcilable.
  </Card>
</CardGroup>
