ProbeRun

Docs / Using ProbeRun

Scenarios

A Scenario is a plain-English description of one expected behaviour, scoped to a Project. It carries the user_prompt the agent receives and the expected_behavior the judge grades against. Scenarios live under a Project and can carry tags.

Add and edit Scenarios in the dashboard

A Developer or Admin adds a Scenario from the dashboard: Add a Scenario opens a form for its Project, name, user prompt, expected behaviour and tags. Create saves it and opens the Project, where the Scenarios list shows it. A Viewer cannot add or edit. This is the no-CLI way to give a Project Scenarios for Run on ProbeRun. Deleting a Scenario asks first. The Project page links each Scenario to its page, and the Scenario page’s Back link returns to its Project.

How a Run passes, on the same form when creating or editing, is one choice:

  • A model judge decides: no assertion set. A model reads the expected behaviour and grades each Run. It uses credits (or your own model key), and the Scenario cannot be monitored nightly.
  • Checks decide: rows, each one exact rule: Calls these tools (any order, other tools allowed), Calls these tools in this order, Calls no tools but these, The conversation contains, The conversation never contains. Tool names are comma-separated. Under the rows the form says in words what a Run will check. Checks are free, give the same answer every time, and are what Monitoring needs.

Edit as JSON opens the same assertion set as text. A set that uses keys the rows cannot show (for example must_modify or a tool call with arguments) opens as JSON and is saved exactly as it was unless you change it.

Opening a Scenario edits it. Save replaces every field, including the assertion set in the editor beside the form; clear the editor (or leave {}) to save without checks. A Scenario stays in its Project. When a save is refused the form shows the API’s reason and keeps what you typed: for example a name another live Scenario in the Project already has, or any edit in the Example Project, which is read-only.

Renaming keeps every Run with the Scenario. Pushes match Scenarios by name, though, so a push that still uses the old name no longer reaches it: a results-only push reports the name as unknown, and a bundle that declares the Scenario creates a new one under the old name. The form says so before you save a rename. An assertion-set edit changes how future Runs are graded; Verdicts already recorded keep their Judge and do not change. Every saved edit is recorded in the audit log as scenario.updated, with the fields that changed.

Assertions, and previewing them before you commit

A Scenario may also carry an assertion set: a short structural contract the deterministic judge grades without any model call — which files must or must not change, what the Trajectory must or must not contain, an expected tool-call path, pinned Policies. The Scenario page has an assertion editor whose output panel is locked to a preview: it shows exactly which bytes were graded (the Run or recording you chose, never an implicit “latest”), one line per assertion, and a Score as passed / counted beside a count of assertions that could not be evaluated. A preview writes nothing. An assertion set must contain at least one check — a vacuous “pass” is refused at authoring time.

Bringing a trace in instead of writing a Scenario

If your agent is already traced, probe import --source <format> --input <export.json> turns one exported trace into a reviewable recording plus a drafted Scenario, with no server and no database. Three formats are read, and you name yours: openinference (OpenInference over collector OTLP/JSON), genai (the OpenTelemetry GenAI semantic conventions over the same envelope, as OpenTelemetry’s OpenAI instrumentation and OpenLLMetry write them), and langfuse (one trace saved from Langfuse’s public API, no collector). Anything the trace could not evidence is written into the recording as a named gap rather than filled in: a GenAI trace recorded with message content off keeps its model calls as empty Steps and drafts no prompt. A trace that does not show the run finished is refused unless you say so explicitly, and the override is recorded where a reviewer will see it; Langfuse records no success status, so a Langfuse import always needs that override. How to produce each export is in Import a trace from OpenTelemetry GenAI, Phoenix or Langfuse.

In the dashboard, Add a Scenario offers Start from a trace: paste one OpenInference export (OTLP JSON, or JSON lines from a collector, up to 5 MB) or choose the file, and press Draft from trace. ProbeRun fills the name and the user prompt the trace records and saves nothing. The expected behaviour stays empty for you to write: a trace shows what happened, not what should. The tools the agent called are shown as a suggested path with its mode, and become a check only when you press Use as a check; one trace is one path the agent took, not the path it must always take. An export holding several traces asks which one; a run the trace does not show finishing is flagged. The same drafting as probe import runs behind it (POST /scenarios/draft-from-trace, Developer or Admin).

The drafted tool path

probe import and probe capture both end the drafted scenario.yaml with a commented-out expected_path: the tools the recording called, as a contract you can accept. It is YAML comments, so no run, gate or --write-config sees it until you review each call and delete the leading # (the block and the path_tolerance: line under it). probe capture previews it as “expected_path draft (not active)” and prints the line numbers to uncomment; after an import, probe preview --against fixture:<dir> checks it once accepted. Tool names only, never arguments: those carry ids and timestamps that change every run.

--path-tolerance picks the draft’s strictness:

--path-tolerance Drafted mode What it lists
actions (default) in_order calls other than read-only lookups (Read, Glob, Grep, LS, NotebookRead, WebFetch, WebSearch), in order; extra calls allowed
exact exact every call, in order, nothing else
any_order any_order every call, any order, nothing else
tool_set contains_all each distinct tool once, any order, extras allowed

A recording of only read-only lookups, or one over 25 entries, falls back to contains_all over the distinct tool names, and the draft’s header says why. No tool calls means no draft, and the output says so. An agent that only ran tools and changed no files still gets a scenario.yaml from probe capture, with the drafted path as its only (commented) assertion.

One limit to know: the accepted path fails only when the agent actually runs. A replayed candidate, the default probe gate candidate, replays the recording the draft came from and passes it every time; the path catches a change on live Runs (probe run --mode live, a drift sentinel’s live leg, managed monitoring).

Triage exported traces before you import them

When you have a directory of exports rather than one, probe triage --source openinference <files or directories> (a directory contributes the .json files directly inside it) ranks them by how much the agent struggled, so you import the worst first. It writes nothing, needs no server, and ends with the probe import command for each of the top flagged traces (--top, default 5).

By default it counts three struggle signals straight from the trace, with no model and no network:

  • tool_loop — the same tool called with identical arguments three or more times (different arguments are not a loop).
  • action_failure — a tool call whose span carries an error status.
  • model_repeat — the same model output (20 characters or more) twice or more.

With --jev it also asks TypeSafe’s Jev four questions per trace: unresolved (was the request met by the end?), and — about the first prompt only — manipulation_attempt, abuse and spam. This sends the trace to TypeSafe under your TYPESAFE_API_KEY — the first user prompt, every model output, and every tool call’s arguments and results, which is where secrets and personal data usually sit in a trace. It says so before the first request; TypeSafe is then a subprocessor for that data, as it is for the jev compliance judge (see “Who judges plain-language Policies” below). Each answer lands in a band: flag at 0.7 or above, clear at 0.3 or below, uncertain in between — uncertain is shown, never rounded away. A correctly refused prompt injection or spam message is not counted as unresolved: that signal is reported as not applicable.

One file ProbeRun cannot read — not JSON, nested too deeply, or shaped in a way the reader does not expect — is listed as refused and the rest of the batch is still ranked. Symlinks inside a directory are not followed. There is no size cap on a single file, so a very large export is read into memory whole.

A signal that could not be checked is never reported as clear. Without --jev the four Jev signals are not checked; with it, a trace Jev refuses as too long, a service still unavailable after three attempts, or an answer that does not parse or comes from a model other than the pinned jev-1.13.0 becomes a named gap on that trace, and the rest of the batch is still scored. A key Jev rejects stops the run.

Exit codes match probe import: 0 every file was ranked (flags are not failures — triage ranks, it does not gate), 1 at least one file was refused the way probe import would refuse it, 2 a usage or configuration error. A batch larger than --limit (default 1000) is refused whole rather than silently cut.

Limits: triage scores what the agent did. An imported trace keeps only the first user message, so user-side friction across later turns (repeating themselves, getting frustrated) is not scored. Jev’s values can differ slightly between identical runs (0.46 and 0.42 for the same trace on 2026-09-28), so treat a value near a band edge as a reason to read the trace, not as a measurement. Jev reads the trace itself, so text written into a trace can steer its answers; the structural signals cannot be steered that way. The suggested probe import commands are shell-quoted, and names from a trace are printed with control and invisible formatting characters removed.