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

# Simulation plans

> Declare a saved, repeatable simulation run

A saved, repeatable simulation: which agents to call, which flows to run against them, and which metrics to grade the result on (the config form of a [run plan](/documentation/simulation-testing/run-plans)).

```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
# yaml-language-server: $schema=https://roark.ai/roark-config.schema.json
kind: simulationPlan
name: nightly-screener-suite
direction: OUTBOUND
agentEndpoints:
  - agent: frontdesk
    value: '+15551234567'
metrics:
  - call_screening_encountered
  - call_screening_handling_score
flows:
  - flow: refund-request
  - system: sf-screener-apple
    edgeCases: [screener-declines-the-call]
maxDurationSeconds: 300
```

<Note>
  **Config declares the plan. It does not start it.** Applying reconciles the definition; the run is a separate, deliberate act:

  ```bash theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
  roark config apply ./roark
  roark simulation plan job start <plan-id>
  ```

  That split is what lets an [alert](/documentation/config-as-code/alerts) name a plan: the plan exists as a durable resource rather than as a side effect of having run something.
</Note>

***

## Your flows and Roark's

A flow row names **exactly one** of `flow` or `system`. They resolve through different paths, so a row naming both is refused.

```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
flows:
  # A `kind: flow` in your own bundle, resolved by name on apply.
  - flow: refund-request
    happyPath: true

  # A Roark-curated flow, by its stable slug. Works in every project without
  # you declaring it, and the slug is the same in every environment.
  - system: sf-screener-google
    edgeCases: [screener-takes-a-message]

  # Naming neither runs every variant the flow has.
  - system: sf-screener-apple
```

`roark simulation template list` prints the slug of every curated flow and outcome.

|                        | What runs                      |
| :--------------------- | :----------------------------- |
| `happyPath: true`      | the flow's default way through |
| `edgeCases: [slug, …]` | those specific outcomes        |
| neither                | every variant the flow has     |

`happyPath` and `edgeCases` are alternatives, not a union: asking for both is refused, because omitting both is already how you say "all of it".

***

## Which line to call

```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
agentEndpoints:
  - agent: frontdesk          # a `kind: agent` in your bundle
    value: '+15551234567'     # which of its endpoints
```

`value` is the endpoint's own identifier, spelled the way [`kind: agent`](/documentation/config-as-code/agents) spells it. Omit it when the agent has exactly one endpoint; apply refuses to guess between several rather than dial a different number than you meant.

***

## Everything else has a default

Only `name`, `direction`, `agentEndpoints`, `metrics`, `flows` and `maxDurationSeconds` are required. The rest default to what the dashboard would give you:

| Field                        | Default       |
| :--------------------------- | :------------ |
| `iterations`                 | `1`           |
| `maxConcurrentJobs`          | `1`           |
| `executionMode`              | `PARALLEL`    |
| `silenceTimeoutSeconds`      | `30`          |
| `endCallPhrases`             | `['goodbye']` |
| `endCallReasons`             | `[]`          |
| `includeFlowMetrics`         | `true`        |
| `enrichWithLiveConversation` | `false`       |

<Warning>
  Deleting a line reverts that setting to its default. It does not leave the previous value in place. That is what makes the file the source of truth: if a plan drifts from its config, the next apply pulls it back rather than quietly keeping both.
</Warning>

`endCallReasons` is worth knowing about if you run scripted flows. A simulated caller hangs up on an end-call phrase (something the **agent** says) or on one of these conditions. Running out of scripted steps ends nothing, so a flow scripted to a verdict needs a matching reason or the call stays on the line until `maxDurationSeconds`.

```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
endCallReasons:
  - You have told the agent that the person it is calling has declined the call. End the call as soon as you have said so.
```

***

## Alerting on the result

An [alert](/documentation/config-as-code/alerts) scopes itself to a plan by name, which is why the plan's `name` is also its stored name:

```yaml theme={"theme":{"light":"everforest-light","dark":"everforest-dark"}}
kind: alert
name: screener-regressions
trigger:
  type: simulation
  conditions: [FAILURE]
  plan: nightly-screener-suite
actions:
  slack:
    - channelId: C0123ABCXYZ
      channelName: '#agent-quality'
```

Both can live in the same bundle. Apply creates the plan before the alert that names it.

***

## Apply order

Plans reference agents, flows and metrics, and alerts reference plans, so one apply resolves them in that order regardless of how your files are laid out:

```
metric → persona → agent → flow → collector → simulationPlan → alert
```

Deletes run in reverse, so a plan is torn down before the flows and agents it points at.

<Note>
  For the full field reference of every kind, see the [Config DSL reference](https://roark.ai/roark-config.schema.json) schema.
</Note>
