Runs and evidence
To watch runs, use bobcat ui. Every Bobcat suite publishing on the machine, live, a page per specification, and the Event Model canvas coloured by their verdicts: that is bobcat ui, part of the free bobcat tool, on http://localhost:5525.
dotnet tool install -g Bobcat.Console
bobcat uiSee The Bobcat UI for what it shows. Stoat's own /runs pages link to the same page there.
What Stoat keeps
Stoat still receives runs, on its own port, 5527, because coordination is built on them:
- a
test-run-gategoes done on a green run carrying itsBOBCAT_RUN_TAG; - a slice node's evidence is the latest verdict of each spec it binds;
- the run MCP tools (
list_runs,failing_tests,await_run_completion, …) read them; - the plan page shows a gate's live progress while its suite runs.
None of that needs bobcat ui running. A run reaches both because BOBCAT_MONITOR_URL is a list and a Bobcat publisher sends to every URL in it. stoat run-plan, stoat gate exec, stoat gate run and stoat runner add Stoat's receiver to the list for whatever they launch. For a suite you run yourself, list both:
export BOBCAT_MONITOR_URL="http://localhost:5525,http://localhost:5527"A run checks each address once at startup (GET /api/ping, with a short timeout) and stops publishing to one that does not answer. A Stoat that is down never slows or fails a test suite. Bobcat 0.60.4 and earlier read only one URL; see Ports and wire contracts.
Exports
GET /api/runs/{runId}/export?format=… on 5527 (or the button on the card in bobcat ui).
| Format | |
|---|---|
| CTRF | The default and the richest. Retries, flaky tests and each spec's steps have their own fields. |
| JUnit XML | Loses detail, but GitLab, Jenkins, Azure DevOps and CircleCI read it natively. |
| NDJSON | The raw event stream the run published, for debugging or replay. |
How long runs are kept
Two settings bound two different things.
Monitor:RetentionRuns (default 10) | How many runs the receiver lists, per job (a repository plus a suite), so a busy repository cannot push a quiet one's runs out. |
Monitor:RetentionDays (default 14) | How long an archive is kept. The only setting that ever deletes a file. |
A run that falls off the list is ejected: its archive moves to ejected/ and is not deleted. A live run is never ejected automatically, since its publisher's next event would bring it back. An orphaned run, whose publisher has gone, can be.
Correlating a run to work
BOBCAT_RUN_TAG is an opaque string Bobcat stamps on a run and never interprets — a plan node, a ticket id, a build number:
BOBCAT_RUN_TAG=critter-parity/fisher-1-10 dotnet testGET /api/runs?tag=… filters on it, and a plan's test-run-gate node reads through it, so the evidence that closes a node is the run itself.
That bare {plan}/{node} tag is for a gate that declares no steps. A gate with steps reads each step's runs under {plan}/{node}/{step}, and a run under the bare tag counts for none of them; a step whose command starts a Bobcat Gate publishes one run per unit under {plan}/{node}/{step}/{project}.{framework}. Running the steps with stoat gate run sets all of this for you. See How a step is decided.
Run evidence joins back to the model
A Bobcat run records the types each scenario actually touched: the aggregate it arranged, the command it dispatched, the events the stream gained, the messages sent and the read model loaded. It never records what an assertion merely names. A scenario that expects a refusal records its rejected command and no event type.
That is how a run joins back to the Event Model's slices, and what lets slice_coverage tell "never specified" apart from "specified but never exercised".
See also
- The Bobcat UI — the run board and the canvas
- The Event Model
- Ports and wire contracts
- What a run publishes — the publisher's side, in Bobcat's docs