# Shapes and workspaces

> Why metro works in shapes, and sandbox versus production.

Source: https://metrocli.com/docs/concepts/shapes-and-workspaces/ · Docs index: https://metrocli.com/llms.txt

metro has two ways to describe a change: **shapes**, which are checked offline, and **ops**, which read your tenant and write a plan. **Workspace packs** decide which billing intents metro knows about. And every live call names a **tenant**.

## Shapes

A shape is a reviewed, local contract for one kind of change, in JSON or YAML. metro validates it before any Metronome client loads, so a malformed payload never reaches the API.

```bash
metro shape list
metro shape explain free-credit-trial
metro validate --shape free-credit-trial --file trial.json --json
metro package preview --shape free-credit-trial --file trial.json --sandbox --json
```

A shape encodes the traps for you. `free-credit-trial`, for example, enforces `commit_duration.unit: PERIODS`, a string `duration.value`, `starting_at_offset` and proration, "so agents never hand-craft this payload".

Each shape has a maturity and blocked modes. Today every shape is `local-validated`: explain, validate and preview work; apply through the shape is blocked. Live writes go through ops instead. The full list is on [Shapes](/docs/reference/shapes); the rules they check are on [Validators](/docs/reference/validators).

## Ops

An op is a live command with a `preflight` and an `apply`: `metro customer create`, `metro contract move-plan`, `metro ledger-adjustment` and the rest. Preflight reads the tenant, so it can check things a shape can't: that an alias is free, that a balance exists, that a contract doesn't overlap. See the [safety contract](/docs/agents/safety-contract).

| | Shape preview | Op preflight |
|---|---|---|
| Talks to Metronome | No | Reads only |
| Output | A payload plan and a `sha256:` hash | A plan file with exact requests and a `payloadHash` |
| Can apply | Not in this release | Yes, with `--confirm-hash` and a typed token |

## Workspace packs

metro is split in two. `metro-core` knows the Metronome API, the shapes and the evidence gates. A **workspace pack** adds billing intents on top, like "move a customer to another package". The public build ships the `generic` pack.

```bash
metro workspace list
metro workspace current
metro plan list
```

`metro plan list` shows the pack's intents, and `metro plan <intent> --sandbox --json` maps one to its best shape, the paths it rejects, and the commands to run. The generic pack binds to your own catalog through `metro catalog bind`; it has no hardcoded IDs.

The active pack resolves in this order: `metro workspace use`, then `METRO_WORKSPACE_PACK`, then the session, then the profile, then `~/.metro/config.json`, then the built-in default.

## Sandbox and production

Sandbox and production are two Metronome tenants behind one base URL, `https://api.metronome.com`. The key picks the tenant.

| | Sandbox | Production |
|---|---|---|
| Key | `METRONOME_SANDBOX_KEY` | `METRONOME_PROD_KEY` |
| Flag | `--sandbox` | `--prod` |
| Typed token for a write | `--confirm-live-apply=I_UNDERSTAND_THIS_WILL_WRITE_LIVE_METRONOME_SANDBOX` | `--confirm-prod-apply=I_UNDERSTAND_BILLING_SIDE_EFFECTS` |
| When | Always first | After the sandbox readback passes, with a typed yes for that change |

metro refuses a sandbox key equal to the production key, in either direction. A plan's hash covers its tenant, so a sandbox plan can't be applied to production.

## Tenant snapshots

`metro catalog bind --sandbox --write` saves your IDs to `~/.metro/profiles.json`, and a versioned model to `~/.metro/snapshots/sandbox.json`. `metro tenant snapshot diff --sandbox --markdown` shows what changed since the last bind, and `--against=production` compares the two tenants.
