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

# Scenario reference

> Every field of a scenario, the two step types, and the shell rules commands run under

A scenario is one JSON document. The Command Center form edits it section by section - every section with a **Form / JSON** toggle shows the same JSON - and the [API](/0.17/benchmark/api) sends and returns it whole.

```json theme={null}
{
  "title": "Nginx Stopped",
  "description": "Setup stops nginx. Correct fix: start it again. Never shown to the agent.",
  "hosts": ["web-01"],
  "agents": ["Web Server Manager - web-01"],
  "vars": { "service": "nginx" },
  "ticket_title": "web-01: website down",
  "ticket_body": "Users report https://web-01 returns \"connection refused\" since 10:00.",
  "prepare": [
    { "name": "Resetting the service", "run": "sudo systemctl start ${service}" },
    { "name": "Stopping the service", "run": "sudo systemctl stop ${service}" },
    { "name": "Confirming it is down", "run": "! systemctl is-active --quiet ${service}" }
  ],
  "validation": [
    { "label": "Job resolved successfully", "validator": "job_resolution_status", "statuses": ["success"] },
    { "label": "Nginx serves again", "validator": "host_state", "run": "curl -sf -o /dev/null http://localhost" }
  ],
  "restore": [
    { "name": "Starting the service", "run": "sudo systemctl start ${service}" }
  ],
  "status": "draft"
}
```

## Fields

| Field | Form section | Notes |
| - | - | - |
| `title` | Scenario Definition → Name | Unique within the organization. Renaming keeps the scenario's run history. |
| `description` | Scenario Definition → Description | For humans: what setup breaks and what a correct fix looks like. Never shown to the agent. |
| `hosts` | Evaluated Agents | Host names. **Order matters**: the first host is the default target of every step and check. In the form, hosts come from the selected agents. |
| `agents` | Evaluated Agents | Agent names, not specialties. List **every** agent that could receive the work - see [Agents](#agents). |
| `vars` | Variables | Name/value pairs. `${name}` is replaced in commands, file paths, and file content before anything runs. Names use letters, digits, and `_`. A `${NAME}` that is not declared is left for the shell, so `${HOME}` still works. |
| `ticket_title`, `ticket_body` | Scenario Definition → Ticket Title, Ticket Description | What the agent receives, exactly as a real ticket. |
| `prepare` | Target Setup | Steps that break the machine. |
| `validation` | Validation | Rules that grade the run - see [Validation rules](/0.17/benchmark/validation). |
| `restore` | Target Restore | Steps that put the machine back. |
| `status` | Status | `draft`, `published`, or `disabled`. Draft and published scenarios run; a disabled one is refused at launch. |

Hosts and agents are referenced **by name** and resolved when you save: an unknown name is rejected. A host or agent used by a scenario cannot be deleted until no scenario targets it.

### Agents

Only the agents the scenario lists take part in the run. If the gateway routes the ticket to another agent - for example a second agent on the same host - its task fails with *"not part of this benchmark run"*. List every agent that could plausibly receive the work.

A scenario with **no agents and no hosts** grades the gateway alone: whether it takes the ticket, routes it, or refuses it. See the [gateway-only example](/0.17/benchmark/examples#gateway-only-no-task-for-an-out-of-scope-ticket).

## Steps

`prepare` and `restore` hold steps of two kinds. Every step can have a `name`, shown in run records and errors, and an `on`, the host it runs on (one of `hosts`, default the first).

### Run a command

```json theme={null}
{ "name": "Blocking the PostgreSQL port", "on": "db-01", "run": "sudo iptables -I INPUT 1 -p tcp --dport 5432 -j DROP" }
```

The step fails when the command exits non-zero.

### Write a file

```json theme={null}
{ "name": "Installing the fixture script", "write": "/usr/local/bin/log-flood", "sudo": true, "content": "#!/bin/sh\nwhile true; do echo flood >> /var/log/flood.log; done\n" }
```

A write **replaces the whole file**; it does not append or patch. `sudo` writes as root (Linux only). Use it for anything longer than a one-liner instead of an `echo` with fragile quoting.

### Order and failure

Steps run one at a time, in order. **The first failing step stops the phase.** For setup, that means no ticket is sent and the run ends in Error. For restore, the remaining restore steps are skipped and the run says **Restore failed**.

## How commands run

A command runs on the host through its agent's connection - the same SSH or WinRM credentials, jump hosts, shell, environment, and user the agent uses. Nothing runs as root unless you ask for it.

**Every step is a fresh shell.** No variable, working directory, or environment carries over from one step to the next. Recompute what you need inside each step:

```json theme={null}
{ "run": "loop=$(sudo losetup -j /var/tmp/data.img | cut -d: -f1); sudo mount \"$loop\" /opt/data" }
```

### Linux

* Commands start with `set -u`: an unset variable is an error, not an empty string. Write `${VAR:-}` when empty is acceptable.
* `set -e` is **not** set. In a multi-command step only the last exit code counts, so chain with `&&` when every part must succeed.
* Write `sudo` and `timeout` into the command itself.

### Windows

* Commands run in PowerShell with `$ErrorActionPreference = 'Stop'` and `Set-StrictMode -Version Latest`.
* The step fails when the **last statement failed, even silently**. `Remove-Item C:\x -ErrorAction SilentlyContinue` on a missing file exits non-zero. Guard it (`if (Test-Path C:\x) { Remove-Item C:\x }`) or end idempotent cleanup with `; exit 0`.
* Escape backslashes in JSON: `"C:\\bench\\run.ps1"`. If you generate scenarios with a script, check it did not turn `\b`, `\n`, `\r`, or `\t` in a path into control characters.
* Keep commands short. For a long script, use a `write` step to drop a `.ps1` file, then a short step that runs it.

### Secrets

Write `${secret:name}` to use a credential from your organization, by name or id. Its value is exported as an environment variable for that command, never inlined, and scrubbed from the recorded output. Never paste a secret value into a scenario.

```json theme={null}
{ "run": "PGPASSWORD=${secret:pg-admin} psql -h localhost -U admin -c 'select 1'" }
```

## Multi-host scenarios

List one host and one agent per machine. Set `on` on every step and `host_state` check that does not target the first host.

```json theme={null}
"hosts": ["app-01", "db-01"],
"agents": ["App Manager - app-01", "Database Manager - db-01"],
"prepare": [
  { "name": "Blocking PostgreSQL", "on": "db-01", "run": "sudo iptables -I INPUT 1 -p tcp --dport 5432 -j DROP -m comment --comment bench-pg-block" }
]
```

When each agent should work only on its own host, add a required negated rule that forbids SSH between the hosts - see [Designing scenarios](/0.17/benchmark/designing#policy-what-the-agent-must-never-do).

## Versions

Every save creates a new version, visible in the scenario's **History** while editing. A run is always graded against the version it launched with, so editing a scenario never changes a run already queued or a past result.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.