Overview
A simulation you have to remember to run is a test suite nobody runs. Put it in your pipeline and every change to your agent is called by simulated customers, scored against your metrics, and turned into a pass or a fail before it reaches production. Three pieces make that work, and only the first is required:- The GitHub Action starts a run, waits for it, and exits non-zero when the run misses its criteria.
- Success criteria on the run plan decide what “missed” means. Roark makes that decision, so the dashboard, the CLI and your pipeline never disagree about whether a run passed.
- Config as Code optionally keeps the test suite itself (customer flows, personas, metrics, collectors) in your repo, so the thing being run is versioned alongside the agent it tests.
Gate a pipeline in one step
The API key needs permission to start simulations and read their results. Store it as a repository secret. See Using the CLI in CI for how credentials resolve.
variables, one KEY=VALUE per line:
How the pass/fail decision is made
Every check is judged on its own. A check is one pass/fail metric: a yes/no metric, or a threshold on another metric the plan collects. Each one has a minimum pass rate, the share of the run’s simulations that must pass it, and the run passes only when every check clears its own minimum.
The run fails.
word count missed its own bar, and no amount of success elsewhere makes up for it. There is no pooled average to clear, because averaging would let a check at 0% hide behind checks at 100%, which is the exact failure a gate exists to catch.
A check that sets no minimum of its own is held to 80%. Set the minimums on the plan, under Advanced → Success criteria, or over the API. See Success criteria.
Criteria are pinned to each run when it starts, so editing a plan never rewrites the verdict of a run that already happened.
Score is not the decision
A run also reports a score: the mean of the check rates, 0 to 100. It is for dashboards and trend lines only. Nothing is gated on it, and you should not gate on it either. A run can score 95 and fail (one non-negotiable check missed its bar) or score 40 and pass (every check cleared a deliberately low bar).Three outcomes, not two
A gate answers one question: did this change make the agent worse? Only a run that actually ran can answer it, so the action reports three outcomes.
A check falling below its minimum is the only thing that turns your build red. Problems on our side are annotated loudly, reported as
verdict: SKIPPED with a skip-reason, and then they get out of your way.
That is deliberate. If our infrastructure can turn your pipeline red, the first thing your team learns is to re-run until green, and from then on nobody reads the gate at all, including on the run where the agent really did regress.
Operational problems poison the verdict rather than sitting beside it. If half a run’s simulations never completed, a check reported at 40% is not evidence of a regression, it is evidence we could not measure. So any operational failure makes the whole run SKIPPED, even when a check also fell short.
Two exceptions, both deliberate:
- A plan with no checks fails the build. That is not an outage, it is a plan that can never gate anything, and skipping it would leave a green check next to a permanent no-op. Add a threshold to one of its metrics, or attach a yes/no metric.
- A rejected request fails the build. A bad token, an unknown
plan-id, or a config the API refuses is something only you can fix, and a gate that skips on a typo never gates again. Only errors that read as ours (5xx, rate limits, dead sockets) are skipped.
fail-on-run-error: true if you would rather block than proceed unmeasured.
Keep the run config in your repo
Instead of a saved plan, point the action at a YAML file. The run is described in the same commit as the agent change it tests:.roark/checkout-regression.yml
minPassRate is the only thing a plan says about a check, because it is the only part that is the plan’s business. What counts as passing a single simulation belongs to the metric: a “did the agent leak PII?” check passes on FALSE because the metric carries a threshold of EQUALS false, stated once in Roark rather than re-stated by every plan that uses it. The API rejects keys it does not recognise, so a stray field here fails the run rather than being quietly ignored.
This runs as a one-off: nothing is added to your saved plans. Set save-as-plan: true to keep it instead, and read the new plan’s id off the plan-id output so later runs can reference it directly.
Hold one branch to a higher bar
min-pass-rate holds every check to at least that minimum for this pipeline only, leaving the shared plan alone:
Inputs
Outputs
Simulations take minutes
Real calls take real time, so a gated run occupies a runner while it waits. Four ways to keep that predictable:- Run it where it matters. Gate
mainor your release branch rather than every push to every branch. - Let our slowness warn rather than block. An overrun is a warning by default (
fail-on-timeout: false), while a genuine check failure still fails the build. - Cancel superseded runs. A
concurrencygroup stops an old push from holding a runner while a newer one is already testing the same branch. The action cancels the Roark run too, so the abandoned simulation stops placing calls you would otherwise be billed for. - Keep a job-level backstop.
timeout-minuteson the job is the last line of defence if the step itself wedges.
Sync your test suite first
If your flows, personas and metrics live in git, apply them before the run so the suite being executed matches the commit:apply exits non-zero if the bundle is invalid, so a broken config fails the build on its own. Run roark config diff ./roark on pull requests to preview changes before they land. See Config as Code for the full workflow.
Outside GitHub Actions
The action is a convenience, not the mechanism. Any CI system can gate on the same verdict with the CLI or a pair of HTTP calls: start a run, poll until it reaches a terminal status, then readverdict.passed.
GET /v1/simulation/plan/job/{jobId} once the run has ended:
failures is a typed list rather than a rendered message, so your pipeline can tell the two kinds apart and treat them differently:
inherited: true means the check was judged against the 80% default rather than a minimum it set for itself.
Related
Success criteria
Set the minimum each check has to clear
Config as Code
Define your test suite as YAML in git
CLI
Install, authenticate, and use the CLI in CI
Running simulations
Launch runs and read the report when they settle