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

# Environments and promotions

> Run the same app at different versions in isolated environments, and promote a tested version forward when it's ready.

A new app starts with one environment, `dev`, that tracks your branch HEAD live. Create `staging` and `prod` to run the same code at pinned, immutable versions, then **promote** from one environment into another when a version is ready to ship.

For the model behind this (what "pinned" means, why `dev` is special, and the guarantee that a push to `dev` never silently changes `prod`), see [Environments](/concepts/environments). This page covers the steps.

## The `dev` environment

Every app has exactly one default environment, named `dev`, created automatically with the app. Three behaviors matter when you work with it:

* **It tracks branch HEAD live.** Every push to your connected branch deploys to `dev` through the normal [deployment pipeline](/deploy/deployment), so the build cache, handler detection, and commit-message overrides all apply here.
* **You can't promote into it.** `dev` is the source of promotions, never the target, so the dialog never lets you select it as a target.
* **You can't delete it.** The default env stays locked for the lifetime of the app.

`dev` doesn't get its own dedicated runtime until something needs one; it runs through the app's primary deployment machinery. Non-default envs each get their own isolated runtime.

## Creating an environment

Open the environment dropdown in the breadcrumb at the top of the Dashboard and click **New environment**.

<img src="https://mintcdn.com/puzzlet-9ba7bb98/2-DXfPaZM6zlgeng/images/platform/deploy/environments-and-promotions/env-dropdown-open.png?fit=max&auto=format&n=2-DXfPaZM6zlgeng&q=85&s=fd04b1735299d6cc143fb94d626b20a9" alt="Breadcrumb environment dropdown open from a non-default env, listing dev with the default suffix, staging with the no-pin suffix, prod with the v1 suffix, plus the New environment, Promote to staging, and Roll back menu items" className="w-full rounded-xl border border-gray-800 shadow-2xl mb-8" width="1440" height="900" data-path="images/platform/deploy/environments-and-promotions/env-dropdown-open.png" />

The dropdown shows every env on the app suffixed with its pin state: `default` for `dev`, `no-pin` for a freshly created env, or the version number (`v1`, `v3`) for a pinned env. The same menu hosts **New environment**, plus **Promote to {env}** when the selected env isn't `dev` and you hold `environment.promote`.

<Note>
  Roll back (re-pinning an env to one of its prior versions) is rolling out behind a feature flag: if the **Roll back** menu item shown in the screenshot isn't visible, it isn't enabled for your account yet, and the rollback API route responds 404 while the flag is off.
</Note>

Names must match `^[a-z][a-z0-9-]{1,39}$`: lowercase letters, digits, and hyphens, starting with a letter, 2–40 characters. Common choices are `staging`, `prod`, `preview`, `eu`, `tenant-acme`.

<img src="https://mintcdn.com/puzzlet-9ba7bb98/2-DXfPaZM6zlgeng/images/platform/deploy/environments-and-promotions/create-env-dialog.png?fit=max&auto=format&n=2-DXfPaZM6zlgeng&q=85&s=51ae64c606c25443425d8c8ae3ee19e4" alt="Create environment modal dialog with a single Name field and helper text reading 'Lowercase letters, digits, and hyphens. Must start with a letter. 2–40 chars.', plus Cancel and Create actions" className="w-full rounded-xl border border-gray-800 shadow-2xl mb-8" width="1440" height="900" data-path="images/platform/deploy/environments-and-promotions/create-env-dialog.png" />

A newly-created env starts in the **no-pin** state: AgentMark provisions the runtime, but you haven't promoted any version into it yet. Until the first promote, the env serves nothing.

<Tip>
  Creating an env doesn't auto-mint an API key. The post-create dialog links straight to the [API keys](/deploy/api-keys) page filtered to the new env so you can mint one scoped to it. A key pinned to one env can't reach another env's traces, templates, or datasets, so a `prod` key can't reach `staging`. A key scoped to environment **kinds** instead reaches every env of those kinds (including future PR previews) and picks the target per request: see [API key environment scope](/api-reference/authentication#api-key-environment-scope).
</Tip>

<img src="https://mintcdn.com/puzzlet-9ba7bb98/2-DXfPaZM6zlgeng/images/platform/deploy/environments-and-promotions/post-create-cta-dialog.png?fit=max&auto=format&n=2-DXfPaZM6zlgeng&q=85&s=c5ec9f9f2512e19f8a23b051ff874be2" alt="Environment created dialog appearing after a successful create, naming the new environment (preview), with an info note that AgentMark created no API key automatically and a primary Create an API key for this environment button alongside a Later button" className="w-full rounded-xl border border-gray-800 shadow-2xl mb-8" width="1440" height="900" data-path="images/platform/deploy/environments-and-promotions/post-create-cta-dialog.png" />

<Warning>
  Environments per app are tier-limited: 1 on Free, 3 on Growth, 5 on Team, and configurable on Enterprise. The default `dev` env counts toward the limit, so on Free it consumes the only slot and creating any additional environment fails with a message naming your current plan. See [Billing and usage](/deploy/billing-and-usage#usage-limits).
</Warning>

## Promoting between environments

A promotion copies one environment's current content and rebuilds its code into another environment. The target env's version counter advances by one, its runtime rebuilds, and its pin moves to the new version.

### Opening the dialog

Promote is always launched from the **target**, the env you're promoting *into*:

1. In the breadcrumb, switch to the env you want to promote into (`staging`, `prod`, etc.).
2. Open the env dropdown and click **Promote to {env}**.
3. In the dialog, pick the **source** env from the dropdown and (optionally) add a note explaining why.
4. Click **Promote to {env}**.

The dialog always shows the direction as a banner: `source → target`. You can change the source, but the env you triggered the action from fixes the target. This is intentional: promotions have a direction, and the UI never lets it go ambiguous.

<img src="https://mintcdn.com/puzzlet-9ba7bb98/2-DXfPaZM6zlgeng/images/platform/deploy/environments-and-promotions/promote-dialog-source-open.png?fit=max&auto=format&n=2-DXfPaZM6zlgeng&q=85&s=7c7aba211404a04af429e54a2ed69767" alt="Promote to staging dialog with the Select source → staging direction banner across the top, the Source environment dropdown expanded to show two options annotated with their pin state ('dev (unpinned)' and 'prod (v1)'), and an empty Note (optional) field below" className="w-full rounded-xl border border-gray-800 shadow-2xl mb-8" width="1440" height="900" data-path="images/platform/deploy/environments-and-promotions/promote-dialog-source-open.png" />

The source dropdown annotates each option with its current pin state (`dev (unpinned)`, `prod (v1)`) so you can see at a glance which commit each source would send. The source list excludes the target env, since an env can't promote into itself.

### What gets promoted

A promote takes the source's **current content** and the **commit its build came from**, and applies both to the target:

* **Content.** Prompt templates (`.prompt.mdx`), components (`.mdx`, `.md`), datasets (`.jsonl`), and schemas. AgentMark writes a fresh env-keyed snapshot for the target, so its content is fully independent of the source going forward.
* **Code.** The target's runtime is rebuilt at the source's commit, and the build injects the **target** environment's [environment variables](/deploy/deployment#environment-variables). The target inherits any variables that target its [kind](/concepts/environments) or **All Environments**, with a variable set on the specific environment taking precedence. So a first promote into a fresh `staging` or `prod` already has the provider keys it inherits from its kind, and you only need to set environment-specific variables when one environment needs a different value. The same build cache and handler detection rules from the [deployment pipeline](/deploy/deployment) apply.

The source supplies the commit, not the latest of its branch, so what you tested is what gets promoted:

| Source state                             | Commit promoted                 |
| ---------------------------------------- | ------------------------------- |
| Pinned env (for example `staging` at v3) | The env's current pinned commit |
| `dev` or any no-pin env                  | The branch's current HEAD       |

### Rules

* **Target can't be `dev`.** The default env tracks branch HEAD; pinning it would break that invariant. The dialog hides `dev` from anywhere it would appear as a target.
* **Source can't equal target.** AgentMark rejects promoting an env into itself; the dialog drops the target from the source dropdown.
* **The app needs a git connection.** Promote builds code against a commit, so the app must have a [linked repository](/deploy/deployment#setup). Promoting from an app with no git connection returns 400.
* **Permission required.** Only roles that hold `environment.promote` see the action.

### Versions

Each non-default env has a monotonic `current_version` counter. A successful forward promote advances it by one: `v1`, `v2`, `v3`. The breadcrumb dropdown shows each env's current pinned version next to its name (`prod · v3`).

`dev` is always at `current_version: 0`, since it has no pin to count.

## Watching a promotion run

When you submit a promotion, the API returns immediately as soon as it writes the content snapshot and commits the env pointer. The code build runs asynchronously after that. The dialog stays open and shows a live progress view with two sub-status rows:

* **File sync** writes the env-keyed content snapshot. It completes before the API returns, so the dialog shows this as already done.
* **Code build** is the managed build that produces the target's new runtime. This is what the progress view is actively polling.

<img src="https://mintcdn.com/puzzlet-9ba7bb98/2-DXfPaZM6zlgeng/images/platform/deploy/environments-and-promotions/promote-dialog-progress.png?fit=max&auto=format&n=2-DXfPaZM6zlgeng&q=85&s=5c2dd9cc5a0b21d21f81ee74b97aa0f8" alt="Promotion in progress dialog showing the dev → staging direction banner, a blue 'In progress' chip, the line 'Deploying version v1 at commit a1b2c3d', and two sub-status rows (File sync with a green 'Synced' pill and Code build with a blue 'Building…' pill), and a single Close action" className="w-full rounded-xl border border-gray-800 shadow-2xl mb-8" width="1440" height="900" data-path="images/platform/deploy/environments-and-promotions/promote-dialog-progress.png" />

Closing the dialog mid-build doesn't cancel the promotion; the build keeps going. While a promotion is in flight, the env shows a **Deploying…** badge next to its name on the **Settings → General** page, so you can come back to it from anywhere.

<img src="https://mintcdn.com/puzzlet-9ba7bb98/2-DXfPaZM6zlgeng/images/platform/deploy/environments-and-promotions/env-settings-inflight.png?fit=max&auto=format&n=2-DXfPaZM6zlgeng&q=85&s=169fcb2f5b298f852fbfc71df8c70eec" alt="Settings → General page showing the Environment card for staging with the env name, a 'No pin' chip, a blue 'Deploying…' badge, and the version label 'v1', followed by a Delete environment affordance" className="w-full rounded-xl border border-gray-800 shadow-2xl mb-8" width="1440" height="900" data-path="images/platform/deploy/environments-and-promotions/env-settings-inflight.png" />

The overall status collapses onto one of three values:

| Status          | Meaning                                                                                        |
| --------------- | ---------------------------------------------------------------------------------------------- |
| **In progress** | Either the env-level commit or the code build is still working.                                |
| **Deployed**    | The content commit succeeded and the build settled (either deployed or skipped via cache hit). |
| **Failed**      | Any one of the steps failed; the env stays on its previous version.                            |

A failed promotion doesn't change the target's pin or its `current_version`. You can fix the underlying cause and click **Retry** to re-run the same promotion, or close the dialog and trigger a fresh one.

## Deployment history

The **Deployments** tab on each env shows every promotion targeting that env in reverse chronological order. Each card surfaces:

* The short commit SHA the build ran against
* A **Promotion** type chip and the deployment status pill
* The two pipeline steps, **File Sync** and **Code Deploy**, with each step's outcome
* The actor who triggered the promotion and how long ago it ran

<img src="https://mintcdn.com/puzzlet-9ba7bb98/2-DXfPaZM6zlgeng/images/platform/deploy/environments-and-promotions/deployments-history.png?fit=max&auto=format&n=2-DXfPaZM6zlgeng&q=85&s=580b5d9e80a38e5b812c76e209613287" alt="Deployments tab scoped to the prod env via the breadcrumb, showing two reverse-chronological cards (the newer 7f3c9a2 promoted 20 minutes ago and the older e2e0fix promoted 1 day ago), each with a 'Promotion' type chip, a green 'Success' status, and File Sync and Code Deploy step rows marked Files synced and Code deployed" className="w-full rounded-xl border border-gray-800 shadow-2xl mb-8" width="1440" height="900" data-path="images/platform/deploy/environments-and-promotions/deployments-history.png" />

This is the audit log for "what version did `prod` run between Tuesday and Friday?". It's per-env: history entries belong to the env they targeted and stay on it for the life of the app.

<Tip>
  The `GET /v1/environments/{id}/deployments` endpoint returns more detail than the cards show, including the pinned `env_version`, the source env id, and the promotion note. Use it when you need the full audit record programmatically.
</Tip>

## Permissions

These per-app permissions gate environment actions:

| Permission            | Action                                                          |
| --------------------- | --------------------------------------------------------------- |
| `environment.read`    | See the env dropdown and per-env settings                       |
| `environment.insert`  | Create a new environment                                        |
| `environment.promote` | Promote into an environment                                     |
| `environment.delete`  | Delete a non-default environment (with typed-name confirmation) |

See [Users and access control](/deploy/users-and-access-control) for how permissions roll up into roles, and [API keys](/deploy/api-keys) for scoping a key to a specific env.

## API reference

The same actions are available over the API. Every endpoint operates on the app passed in the `X-Agentmark-App-Id` header. See the [API reference](/api-reference/overview) under the Environments tag for full request and response schemas.

| Method   | Path                                | Description                                                                                     |
| -------- | ----------------------------------- | ----------------------------------------------------------------------------------------------- |
| `GET`    | `/v1/environments`                  | List environments for the app (default env first)                                               |
| `POST`   | `/v1/environments`                  | Create a non-default environment                                                                |
| `GET`    | `/v1/environments/{id}`             | Get one env, including a cascade-preview for delete                                             |
| `DELETE` | `/v1/environments/{id}`             | Delete a non-default env (requires typed-name confirmation in the body)                         |
| `POST`   | `/v1/environments/{id}/promote`     | Promote a source env into this one; returns the new deployment                                  |
| `POST`   | `/v1/environments/{id}/rollback`    | Re-pin this env to a prior version (feature-gated; responds 404 while the rollback flag is off) |
| `GET`    | `/v1/environments/{id}/deployments` | List promotion history for an env                                                               |

A `POST .../promote` returns `202 Accepted` with the new `deployment_id` and `env_version`. The content snapshot and env-pointer commit are already done by the time the response lands; the build runs asynchronously. Poll `GET /v1/deployments/{deploymentId}` and watch `code_status` until it reaches `deployed` (success) or `failed`.

```bash theme={null}
# Promote staging into prod
curl -X POST https://api.agentmark.co/v1/environments/$PROD_ENV_ID/promote \
  -H "Authorization: Bearer $AGENTMARK_API_KEY" \
  -H "X-Agentmark-App-Id: $AGENTMARK_APP_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "source_environment_id": "'"$STAGING_ENV_ID"'",
    "note": "Release 2026-05-29 — sign-off in #releases"
  }'
```

## Related reading

* [Deployment](/deploy/deployment): the underlying pipeline that promote dispatches a build into
* [API keys](/deploy/api-keys): each environment has its own keys
* [Users and access control](/deploy/users-and-access-control): env permissions and roles
* [Regression gates](/deploy/regression-gates): block a promotion candidate before it ships

<div className="mt-8 rounded-lg bg-blue-50 p-6 dark:bg-blue-900/30">
  <h3 className="font-semibold mb-3">Have questions?</h3>
  <p className="mb-4">Reach out any time:</p>

  <ul>
    <li>
      Email the team at <a href="mailto:hello@agentmark.co" className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200">[hello@agentmark.co](mailto:hello@agentmark.co)</a> for support
    </li>

    <li>
      Schedule an <a href="https://cal.com/ryan-randall/enterprise" className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200">Enterprise Demo</a> to learn about AgentMark's business solutions
    </li>
  </ul>
</div>
