---
name: ironbee-manage-scenario
description: "Add, update, or delete a reusable IronBee verification scenario. Authors the script in the devtools format and saves it to the scenario store (or finds and updates/deletes an existing one) using the ibdt_scenario-* MCP tools."
disable-model-invocation: true
---

# IronBee — Manage scenario

> **⚠️ Run this inline — do NOT delegate.** Do every step yourself, in this conversation;
> do **not** use the `Task` tool or spawn a sub-agent. A sub-agent runs as a separate session,
> so its tool calls are recorded elsewhere and the work won't be tracked correctly.

Add / update / delete a reusable verification **scenario** using the devtools `ibdt_scenario-*` MCP
tools directly. A scenario is a named, parameterizable script (`callTool('<tool>', {...})` JS) that
drives one OR MORE platforms' tools (all enabled platforms are served by the single
`ironbee-devtools` compose server; a single script may mix `callTool('bdt_…')` + `callTool('bedt_…')`).
This is NOT a verification cycle — it submits no verdict and does not gate completion.

## Steps
1. **Resolve intent.** Content to save (inline text or a file path you read) → add/update. A target
   only described → delete.
2. **Add vs update (never duplicate).** Before adding, `ibdt_scenario-search` / `ibdt_scenario-list` to
   check for a same-name / clearly-the-same scenario in the store; if it exists → update
   it instead of creating a duplicate.
3. **Author the script** (see "Script format") and call `ibdt_scenario-add` / `ibdt_scenario-update` on the one
   compose server. The script may drive a single platform OR span several in one flow — author a
   cross-platform flow as ONE scenario; do NOT split it per platform. Declare the platform(s) it
   drives in the first-class **`requiredPlatforms`** field (§Script format) — that is what gates the
   run AND labels the scenario; there is no separate metadata platform key to stamp.
4. **Delete is destructive — always confirm.** Resolve the target, show the matched
   **name + description + platform**, and ask the user before deleting. Multiple / low-score
   candidates → list them and ask which. An **update resolved by fuzzy description** also confirms
   (the script is overwritten); an exact-name update proceeds without confirm.
5. **Scope**: pass `scope: "project"` (default) unless the user asked for `global`.

## Live authoring (default for add / update) — build it against the running app

Don't author a runtime scenario from source guesses (source rarely matches the running system exactly). By **default, drive the app to
understand it — exactly what you'd do when verifying** (exercise the relevant flow through this platform's tools, whatever it takes) — author from what you actually observe, then validate by running it. Do this
entirely through the `ibdt_scenario-*` tools (run discovery via `ibdt_scenario-run`, don't call the platform
tools directly: that keeps it gate-orthogonal — no `verification_id`, can't false-block a later edit).

1. **`draft` → skip:** if the request begins with `draft` (or says "source only"), author from source,
   save, note *"not live-validated — run it to verify"*. Done.
2. **Start the app only if it isn't already running** (track whether YOU started it). Can't start it
   (missing env/DB/secrets, broken build) → **source-only draft + say so**, don't fail.
3. **Understand it by running probe scenarios:** `ibdt_scenario-add` the draft **under the FINAL scenario
   name** (step 4 then iterates that SAME entry via `ibdt_scenario-update` — do NOT spawn a separate
   `*-probe` / throwaway scenario in the store) and `ibdt_scenario-run` it to exercise the relevant flow —
   whatever it takes to learn how the real system behaves — and read the returned snapshots/results.
4. **Author the full flow** from what you observed → `ibdt_scenario-update`. Make it a **verification flow**,
   not a superficial run: exercise the cycle's evidence tools, capture their output with
   `returnOutput: true`, and assert / return the expected outcomes — so a later run via
   `/ironbee-run-scenario <name>` can judge it.
5. **Validate:** `ibdt_scenario-run` end-to-end; fix the **SCRIPT** + update until it runs cleanly, and
   **assert the real terminal outcome — not an optimistic intermediate signal**. Same app/env
   considerations as any verification run (use a test/staging target for flows with real side effects).
6. **Teardown — leave a clean store:** `ibdt_scenario-delete` ANY temporary / probe / throwaway scenario you
   added this session (anything named `*-probe`, a draft you decided not to keep, an exploratory copy);
   the store must end with ONLY the finished deliverable scenario(s), never a leftover probe. THEN stop
   ONLY the app / processes you started.

> **A genuine defect is a STOP, not a workaround.** If validating shows the flow can't legitimately
> succeed — a real bug makes the expected outcome unreachable (an error, a failed state, wrong
> resulting data) — do NOT engineer the scenario around it: don't cherry-pick inputs / args / data that
> dodge the bug, and don't weaken the assertion to an optimistic intermediate signal instead of the
> real terminal outcome. That yields a green scenario that masks a broken flow and produces a FALSE
> PASS when it's later run to verify. Instead STOP and report the defect to the user **in your summary,
> not inside the scenario** — keep the saved scenario a clean verification flow (it asserts the real
> outcome and will simply fail until the bug is fixed; that's it doing its job). Do NOT bake bug /
> defect commentary into the scenario's `description` or metadata; `liveValidated: false` is the only
> signal needed when you couldn't get a passing run — or leave the scenario unsaved. ("Fix until it
> passes" means fixing the SCRIPT, never working around the app.)

## Script format
JS run in the devtools sandbox (async — top-level `await`/`return` work); reads its inputs from `args`:

```js
const { baseUrl } = args;            // declared in the scenario's `params` contract
const result = await callTool('<bare-tool-name>', { /* tool input */ });
return { ok: true };
```

Discover the available `callTool` tool names for a platform from your connected MCP schemas — don't
guess. Declare each input via the first-class **`params`** contract (§Parameters), not `argsSchema`.

**`group` — name the phases a reader thinks in.** A step-ful scenario may nest steps
in `group` containers (`{ "type": "group", "name": "signin", "steps": [ … ] }`, groups
inside groups allowed). A group runs NOTHING — it exists so the run can be shown at
the level a reader cares about rather than as one long list. Its `name` is required
and must be unique among siblings; an `include` contributes a level the same way.

**`requiredPlatforms` — declare what the scenario needs.** A scenario that drives
`bedt_*` on a server exposing only `browser` used to fail deep inside a step with
"tool not found", after `setup` had already booted an app. Declare the platforms
any phase touches and the run refuses up front instead:

```jsonc
"requiredPlatforms": ["browser", "backend"]
```

- Derive it from the steps you just wrote: every `tool-call`'s `tool` prefix and
  every `callTool('<prefix>_…')` in a script, across **setup, body AND teardown**.
- An included scenario's requirements are unioned in automatically — declare only
  what THIS scenario touches.
- When a run is refused, devtools names the missing platform(s); the fix is
  `ironbee <platform> enable` in the project and then a NEW session (the MCP server
  reads its platform set at startup). Report that to the user rather than editing
  the scenario to dodge the requirement.

## Setup / teardown — what the scenario needs, and undoing it
A step-ful scenario (one with a `body.steps`, `formatVersion: 2`) may bracket its body with two
lifecycle sections, each an ordered list of **`script` / `command` steps only**:

```jsonc
"setup":    { "steps": [ { "type": "command", "command": "npm start > .ironbee/app.log 2>&1 & echo $! > .ironbee/app.pid" } ] },
"body":     { "steps": [ … ] },
"teardown": { "steps": [ { "type": "command", "command": "kill $(cat .ironbee/app.pid) 2>/dev/null; true" } ] }
```

- **Once the run has begun, teardown runs** — after a pass, after a failure, and after a setup that
  failed halfway. A setup failure skips the body and fails the run.
- **Teardown does not fail fast** and its failures are reported (`teardownErrors`) WITHOUT changing
  the run's verdict. Report a failed cleanup; do not report it as a failed scenario.
- **Redirect a backgrounded process's output** as above, or the step hangs until it times out — a
  server that inherits the step's stdout pipe holds it open. A `command` string reaches the shell
  VERBATIM (it cannot read `ctx`), so hand the pid over through a file.
- A section of a scenario that is `include`d elsewhere is **skipped by default** (so two scenarios
  that both want the app up don't start it twice); opt in with `"whenReused": "run"`.

## Parameters (`params`) — typed, defaulted, validated
Declare a parametric scenario's inputs via the first-class **`params`** array on
`ibdt_scenario-add` / `ibdt_scenario-update` (top-level field, NOT metadata — supersedes the old `argsSchema`
convention). Each entry: `name` (required — the `args` key the script reads), `description`, `type`
(`string`/`number`/`boolean`/`object`/`array`; `object`/`array` shallow-checked at the top level),
`default` (applied when the arg is omitted — **capture it from the live-authoring run** so the
scenario re-runs "as captured" with zero args), `example` (doc-only shape when there's no `default`),
`required` (reject the run when there's no value AND no `default`). `ibdt_scenario-run` applies defaults,
enforces `required`, shallow-validates declared types, and surfaces `params` in list/search/run
output. Pass `args` only to OVERRIDE a default. `ibdt_scenario-update` shallow-replaces `params` (re-send
the full array; omit to keep the stored contract).

## Metadata conventions (stamp on add/update)
**Shape — a NESTED object** under the scenario's `metadata` field: `metadata: { ironbee: { coveredPaths: [...], commit: "…" } }`. `ironbee.<key>` names a key INSIDE that nested object — do NOT write a literal dotted key (`"ironbee.coveredPaths"`) and do NOT JSON-stringify it. The platform(s) are NOT metadata — they are the top-level `requiredPlatforms` field.
- `ironbee.coveredPaths` — source paths exercised (array), when derivable.
- `ironbee.group` / `ironbee.order` — for a multi-step flow kept as SEPARATE scenarios that must run in sequence (NOT a per-platform split — a cross-platform flow is ONE scenario).
- `ibdt_scenario-update` applies metadata as a **JSON Merge Patch (RFC 7386)** — send ONLY the keys
  you are changing (objects merge recursively, `null` DELETES a key, an array/scalar replaces it,
  omitted keys survive). Never read-then-resend the whole object: metadata is shared space (the
  user's own keys live beside `ironbee.*`). `params` is different — a provided array REPLACES the
  stored one wholesale; omit it to keep the contract. The typed input contract is the first-class
  `params` field (§Parameters), not a metadata key.

The platform sections below give each enabled cycle's tool prefix + what to test. All cycles share ONE `ironbee-devtools` server, the shared `ibdt_scenario-*` tools, and the flat `.ironbee/scenarios` store (you pass `scope`; the server resolves the path — no per-prefix subdir). A single scenario script MAY call tools across platforms in one flow — author it as ONE scenario, not split per platform.

<!--IRONBEE:PLATFORM:browser-->
<!--/IRONBEE:PLATFORM:browser-->

<!--IRONBEE:PLATFORM:node-->
<!--/IRONBEE:PLATFORM:node-->

<!--IRONBEE:PLATFORM:python-->
<!--/IRONBEE:PLATFORM:python-->

<!--IRONBEE:PLATFORM:backend-->
<!--/IRONBEE:PLATFORM:backend-->

<!--IRONBEE:PLATFORM:android-->
<!--/IRONBEE:PLATFORM:android-->

<!--IRONBEE:PLATFORM:terminal-->
<!--/IRONBEE:PLATFORM:terminal-->
