Consumer Agent Testing Tool (CATT) tests how an assistant uses your service.
Run a scripted or model-driven agent against an MCP server, an MCP app, a
website or an HTTP API. Check what the service did, inspect the evidence, and
compare results across builds.
**Muse and Instinct are supported surrogate emulation targets.** Muse remains
the default. Both use the [shared connector testing contract](docs/surrogate-contract.md),
including routing, memory, payments, browser/HTTP tasks and scheduling.
See the [Instinct research](docs/instinct-emulation.md) for its sources and
limits. The model that drives a trial is separate from the assistant being emulated.
[Contributing](CONTRIBUTING.md) | [Security](SECURITY.md) | [Changelog](CHANGELOG.md) | [MIT license](LICENSE)
> **A test host.** CATT is a surrogate: it approximates an assistant's
> host, not its private implementation. A passing test does not certify
> compatibility with Muse or any hosted assistant. Each Muse-specific behavior
> has a recorded basis: `documented`, `unofficial`, `assumed` or `unknown`.
> This project is not affiliated with or endorsed by Meta, Instinct, OpenAI or Anthropic.
## What you can test today
| Lane | Command | What it checks | Status and scope |
| --- | --- | --- | --- |
| MCP connectors | `connector-run SCENARIO` | Use the server's tools, apply account and approval rules, protect private values, and check the service's recorded outcome and the agent's answer. | Muse and Instinct surrogates: `--emulation-target muse\|instinct`. See [connector lane](docs/connector-lane.md). |
| Connector extensions | `connector-run SCENARIO` with `extensions:` | Routes, memory, custom API clients, browser actions, wallet cards, background jobs, untrusted content, channel limits and simulated users. | Supported with the lane. `user_simulator` is exploratory and cannot run in scripted tests or decision contracts. See [extensions](docs/connector-extensions.md). |
| Scenario scaffolding | `scaffold URL --output FILE`, `scaffold --from-run RUN --output FILE` | Read a tool catalog or turn an agent run into scripted calls. Calls no service tools. Replace every `TODO(scaffold)` before running. | Supported. One Streamable HTTP connector; no OpenAPI input. |
| Muse connector readiness | `check URL --suite muse-guidelines` | Review the catalog and optional manifest against the pinned connector guidelines without calling tools. | Documented constraints, not admission. See [readiness checks](docs/readiness-checks.md). |
| Shared surrogate readiness | `check URL --suite surrogate --emulation-target instinct` | Check the catalog, allowlist, schema syntax and selected projection without calling tools. | CATT's contract for either target; see [shared contract](docs/surrogate-contract.md). |
| Personal Agent Protocol | `check ORIGIN --suite poppy` | Read `poppy.json`. With a manifest, probe sessions, sign-in and one declared operation on the company's hosts. | Draft 0.1; no production Muse evidence. |
| Muse gadget registration | `check FILE --suite gadget` | Check a `link.register` message and command specs against the pinned SDK limits. Opens no connection. | Documented limits, not admission. The SDK's terms say it is not a developer platform. |
| MCP protocol contracts | `check TARGET` | Check schemas, version negotiation, sessions and operation contracts; run the pinned conformance suite. | Supported. Generic MCP evidence; see [readiness checks](docs/readiness-checks.md). |
| Embedded MCP apps | `surface-run SCENARIO` | Render the app in Chromium and check forms, tool calls, resources and service responses. An agent driver chooses from the scenario's scripted steps. | Supported. Generic MCP Apps host, not Muse. See [host scope](#what-the-mcp-apps-host-implements). |
| Websites | `browse POLICY`, `run --surface browser` | Act on accessibility snapshots under request rules. Takeover pauses the run (exit 3); the CLI has no handback. | Native Chromium, without OS isolation. Browser scenarios use only the packaged reservation portal. |
| HTTP APIs | `run --surface api-tools`, `inspect`, `check` | Bind approval to each proposed call and check backend outcomes. | Supported with a mapping. Your API also needs a loopback deployment and private control adapter. See [API scenarios](docs/api-scenarios.md) and [API trials](docs/api-behavior.md). |
| Evidence | `report`, `export`, `replay`, `diff` | Inspect reports, redact exports, verify playback and compare runs. | Supported. Playback and diff require sealed runs. |
| Development extensions | `--development-extensions COMMAND` | CLI binaries, isolated browsers, cloud workers, local Glimmer, generated OpenAPI and Prism mocks. | Outside the supported release. See [development extensions](#development-extensions). |
The connector lane records the basis for each Muse-specific behavior. Tool classes
and approval prompts follow documented guidelines. The `muse-cli` projection and
template client follow unofficial mirrors; the native projection is an assumed
generic baseline. The neutral `cli` projection supports both targets as a CATT
treatment. Instinct runs have a separate source record and label the
shared host algorithms as assumptions. Native/CLI MCP is CATT's test adapter;
no public Instinct MCP integration contract was found.
Run the local booking example under Instinct without a model or account:
```sh
uv run consumer-agent-testing-tool connector-run examples/connector/instinct-booking.yaml \
--driver scripted --script examples/connector/fixture-booking.calls.json \
--start "python -m consumer_agent_testing_tool.connector_fixture --port {port} --variant good --auth none"
```
Set `emulation_target: instinct` in your scenario or pass
`--emulation-target instinct`. Both targets use `native` or `cli` projection and the generic
MCP client. Instinct defaults to plain-text replies and does not run Muse guideline
checks. Its local wallet fixture covers payment behavior; live payment rails,
trusted-network protocol, Vault and Files remain outside the
[shared contract](docs/surrogate-contract.md).
Three quickstarts follow. The [connector lane](#quickstart-connector-lane) and
the [popular MCP server](#quickstart-a-popular-mcp-server) quickstarts run on
loopback with no account; the [Coverage Cat sandbox](#quickstart-coverage-cat-sandbox)
quickstart calls a hosted sandbox. [Test your own service](#test-your-own-service)
turns them into a workflow, and the [documentation index](docs/README.md) lists
every page.
## Setup
You need Python 3.12 or newer (CI tests 3.12 on Linux and 3.14 on macOS),
[uv](https://docs.astral.sh/uv/), and Node.js 20 or newer with npm (the
installed packages declare `>=20`; tested with 25.9). The recorded release
evidence covers macOS. From a clone of this repository, install the Python and
Node dependencies and the Chromium build the browser lanes use:
```sh
uv sync --extra openrouter --extra dev
npm --prefix tooling ci --ignore-scripts
npm --prefix tooling/payment-ui ci --ignore-scripts
PLAYWRIGHT_BROWSERS_PATH="$PWD/.cache/ms-playwright" node tooling/payment-ui/node_modules/playwright/cli.js install chromium
```
The connector lane, the readiness checks and the scripted runs need only
`uv sync`. Node and Chromium are for the MCP Apps and browser lanes.
On Linux (Debian or Ubuntu), install Chromium with its system libraries
instead. Linux is not covered by the recorded release evidence: the commands
below match the [CI workflow](.github/workflows/ci.yml) but have not been
qualified here.
```sh
sudo apt-get update && sudo apt-get install -y nodejs npm # or Node 20+ from nodesource or nvm
curl -LsSf https://astral.sh/uv/install.sh | sh
uv sync --extra openrouter --extra dev
npm --prefix tooling ci --ignore-scripts
npm --prefix tooling/payment-ui ci --ignore-scripts
PLAYWRIGHT_BROWSERS_PATH="$PWD/.cache/ms-playwright" \
node tooling/payment-ui/node_modules/playwright/cli.js install --with-deps chromium
```
Three features rely on macOS Seatbelt (`sandbox-exec`) and report unsupported
or blocked on Linux rather than running unconfined: the `--offline` readiness
mode, the network-denied validator workers behind `run --surface api-tools`, and
the local-model memory profile. The connector lane, the scripted runs, the
readiness checks and the Docker-backed stdio targets do not depend on it.
Check the setup with `doctor`:
```sh
uv run consumer-agent-testing-tool doctor
```
It checks Node against the floors the installed packages declare, the
`tooling/payment-ui` packages against their pinned versions, and Chromium and
its headless shell under `.cache/ms-playwright`. It reports the optional
protocol tooling, and checks Docker only for development lanes or configured
stdio MCP and CLI targets. It makes no model requests, even when an existing
configuration or `--model-profile` selects a model. Each failed or blocked check
prints one line on stderr with its reason and, for a setup problem, the command
that fixes it.
To explicitly run the provider preflight, which sends two short model requests
and may incur charges, add `--model-preflight`:
```sh
uv run consumer-agent-testing-tool doctor --config agent.yaml --model-preflight
```
Scripted runs need no model account, and the scripted commands in all three
quickstarts need no configuration file. Agent runs need a model profile and an
API key; [Model providers](#model-providers) shows how to create one. Supply
credentials through environment variables, and keep them out of scenarios and
shared evidence. `consumer-agent-testing-tool.yaml`, the default settings file,
is local and git-ignored, and no profile is shipped.
## Quickstart: connector lane
This quickstart stays on loopback and needs no account. Complete
[Setup](#setup), then start the Cedar Stays-like test service in a second
terminal. It prints one JSON line with its URLs when it is ready; Ctrl-C stops
it:
```sh
uv run python -m consumer_agent_testing_tool.connector_fixture --port 8765 --variant good --auth none
```
Replay the example's scripted calls, with no model:
```sh
uv run consumer-agent-testing-tool connector-run examples/connector/fixture-booking.yaml \
--driver scripted --script examples/connector/fixture-booking.calls.json
```
The script searches rooms, gets a quote, books and gives a final answer. The
host fills the guest's email from the scenario's `private:` values, which the
model never sees, and the scripted user answers each approval card from the
scenario's `permissions:` rules. After the run, the host checks the fixture's
private oracle (one confirmed booking, no duplicate charges) and the booking's
status through `get_booking`. It also checks that each sent call matched its
approval card (`approval.dispatches_match`, `approval.disclosure_bound`) and
that the final answer made no false claim and relayed the total and the
confirmation (`answer.no_false_claims`, `answer.relay_complete`).
The command prints the run directory and `Connector report: .../report.html`,
and exits 0 when every required check passed. Runs are sealed under
`runs/connector-/`. Share the output of `export`, not the run directory:
its `private/` folder holds host-private values.
For an agent run, create `agent.yaml` with the `init` command in
[Model providers](#model-providers), set `OPENROUTER_API_KEY` in your shell, and run:
```sh
uv run consumer-agent-testing-tool connector-run examples/connector/fixture-booking.yaml --config agent.yaml
```
The model preflight runs first and prints its own evidence path. The agent then
sees the four allowed tools as native functions, with the host-private email
removed from their schemas, and writes the other arguments itself. This uses
paid model inference. To change the treatment without editing the scenario,
`--projection cli` offers the same tools through one `exec` command for either target,
`--exposure search-only` loads their parameters through `tool_search`,
`--output-schema strict` refuses results that break a tool's `outputSchema` as
the official MCP SDK does, and `--trials N` runs independent trials and prints
pass^k. The run lock records the projection, exposure, client-profile and
output-schema overrides under `overrides`.
`connector-run` exits 0 when every required check passed; 1 when a check
failed, the answer made a false claim or an agent limit was reached; 2 for a
setup, harness or reporting error; and 3 when the trial is blocked or
unsupported, for example by an uncertain write to reconcile, an unobservable
check or a client-profile limit. To start from your own server, run
`scaffold`; to replay a recorded, shareable agent trial without a model, turn
it into a calls file with `scaffold --from-run`. The
[connector lane](docs/connector-lane.md) reference covers scenarios, flags and
evidence. The [examples](examples/connector/README.md)
list each shipped scenario with its fixture command, and
[lane extensions](docs/connector-extensions.md) covers routes, custom
connectors, the browser sub-agent, wallets, schedules and the other
extensions.
## Quickstart: a popular MCP server
The connector lane works with any MCP server over Streamable HTTP, not only the
bundled test service. This quickstart points it at "Everything"
([`@modelcontextprotocol/server-everything`](https://github.com/modelcontextprotocol/servers/tree/main/src/everything),
MIT), the reference server the MCP project maintains for client authors. Its
weather tool returns simulated data, so it needs no account or key. Start it in
a second terminal; the first run downloads the pinned release from npm:
```sh
PORT=3001 npx -y @modelcontextprotocol/server-everything@1.0.0 streamableHttp
```
See what the host infers from the live catalog. `scaffold` runs the handshake
and `tools/list`, calls no tool, and writes a scenario skeleton with suggested
tool classes from the server's annotations:
```sh
uv run consumer-agent-testing-tool scaffold http://127.0.0.1:3001/mcp --output my-everything.yaml
uv run consumer-agent-testing-tool check http://127.0.0.1:3001/mcp --suite muse-guidelines
```
The second command reviews the catalog statically against the pinned Muse
connector guidelines. Then replay the shipped example, which asks for the
weather in Chicago, first with native tools and then through the Muse-style
command-line projection over the unofficial Muse template client:
```sh
uv run consumer-agent-testing-tool connector-run examples/connector/everything-weather.yaml \
--driver scripted --script examples/connector/everything-weather.calls.json
uv run consumer-agent-testing-tool connector-run examples/connector/everything-weather.yaml \
--projection muse-cli --client-profile muse-template-unofficial-2026-10 \
--driver scripted --script examples/connector/everything-weather-cli.calls.json
```
Both exit 0. The scenario offers the model three of the server's tools; a
reviewed allowlist keeps the rest off its list, including `get-env`, which
returns the server process's environment variables, and
`gzip-file-as-resource`, which fetches any URL it is given. The server has no
private oracle, so the outcome check calls `get-structured-content` itself
after the agent stops, and the answer checks look for the temperature and
conditions the server returned. With a model profile, let an agent do the same
task three times and print pass^k (paid inference). The scenario declares no
reset URL, so `connector-run` warns that the trials share the server's state;
this weather tool keeps none:
```sh
uv run consumer-agent-testing-tool connector-run examples/connector/everything-weather.yaml \
--config agent.yaml --trials 3
```
## Readiness checks
`check` also runs readiness suites that read catalogs and documents instead of
running an agent. With the connector-lane quickstart's fixture still running,
review its catalog and the example manifest against the pinned Muse connector
guidelines:
```sh
uv run consumer-agent-testing-tool check http://127.0.0.1:8765/mcp --suite muse-guidelines \
--manifest examples/connector/readiness.yaml
```
| Suite | Command | Exit codes |
| --- | --- | --- |
| `muse-guidelines` | `check URL --suite muse-guidelines [--manifest FILE]` | 0 no fail finding, 1 fail findings, 2 setup error, 3 the client profile cannot reach the server |
| `muse-guidelines` drift | `check --suite muse-guidelines --drift-check` | 0 unchanged, 1 drifted, 2 not fetched |
| `poppy` | `check ORIGIN --suite poppy [--manifest FILE]` | 0 no fail finding, 1 fail findings, 2 setup error, 3 blocked |
| `gadget` | `check FILE --suite gadget` | 0 no fail finding, 1 fail findings, 2 the document cannot be read |
| `contract` (default) | `check TARGET [--interface-profile PROFILE]` | 0 every required check passed, 1 a check failed, 2 setup, harness or reporting error, 3 blocked or unsupported |
The guidelines review lists the server's tools and calls none; `--drift-check`
fetches the live guidelines, and only when given. Poppy discovery only reads.
Its probes run only with `--manifest` and act on the company's own hosts: they
start sessions, sign in the declared test account and confirm one declared
operation. The gadget suite reads one local file. The contract suite checks a
target named in the configuration file (`--config`). For a `kind: mcp` target
without declared `contract_cases`, its `operation-contracts` check is
unsupported and the run exits 3.
A clean readiness result is not an admission. Meta reviews connectors by hand,
Poppy is draft 0.1 with no production Muse evidence, and the Gadget SDK's terms
say it is not a developer platform. [Readiness checks](docs/readiness-checks.md)
describes the rules, manifests, `--offline` behavior and the
`mcp-contract-2026-07-28` profile.
## Quickstart: Coverage Cat sandbox
From a source checkout or unpacked source distribution, complete
[Setup](#setup), then run:
```sh
uv run consumer-agent-testing-tool surface-run examples/coveragecat-sandbox.yaml --driver scripted
```
The example creates a fictional shopper's draft in Coverage Cat's hosted
**sandbox** at `https://www.coveragecat.com/api/consumer/mcp`. It needs no
Coverage Cat account, operator credentials or model API key.
The first `umbrella_consumer_prefill` call sets `sandbox: true`, as specified in
the [consumer MCP manifest](https://www.coveragecat.com/.well-known/mcp.json).
That argument activates the server's sandbox. The scenario's `test_only: true`
flag merely records the test author's intent.
The host fixes the tool arguments and keeps the returned access token private.
It permits two tools, `umbrella_consumer_prefill` and `consumer_intake_issues`,
and checks that:
1. Prefill returns `sandbox: true`.
2. Follow-up retains `sandbox: true`.
3. Follow-up returns the original application ID.
4. The server reports `application_submitted: false`.
Each run creates a new draft and stops before submission. A failed or missing
sandbox check stops the workflow. These checks cover draft creation and
follow-up, not payment widgets, browser behavior or independent backend checks.
To let a model choose the steps, create `agent.yaml` with the `init` command in
[Model providers](#model-providers), set `OPENROUTER_API_KEY` in your shell, and run:
```sh
uv run consumer-agent-testing-tool surface-run examples/coveragecat-sandbox.yaml \
--driver agent --config agent.yaml
```
The agent driver is a **scripted action menu**: Spark Open picks one ready
scenario step per turn, and the host runs it with the scenario's arguments. The
model never sees the server's tools, schemas or raw results, only the result
fields the scenario exposes, and the evidence is labeled `scripted action menu`.
The host supplies the test inputs and checks the results. This version uses
paid model inference through Together, with ZDR enabled and fallback disabled
by default.
**Verified 15 October 2026:** both scripted and Spark Open agent runs passed all
four sandbox checks. Both model responses identified Together; both requests
used the configured ZDR policy. See the
[recorded quickstart results](docs/release-evidence/coveragecat-sandbox-quickstart-2026-10-15.json).
Each command prints an outcome line, such as `Surface run completed (exit 0)`,
then `Surface report: .../report.html`. Open the report to inspect the workflow
and its checks. The same directory contains `result.json` for the structured
result and `events.jsonl` for the redacted event timeline. Agent runs also store
model and tool records under `agent/`.
Replace the placeholders below with the run directories printed by your commands:
```sh
uv run consumer-agent-testing-tool report runs/surface-YOUR_RUN_ID
uv run consumer-agent-testing-tool export runs/surface-YOUR_RUN_ID
uv run consumer-agent-testing-tool diff runs/surface-BASELINE_ID runs/surface-CANDIDATE_ID
```
Share the generated export. Keep the full run folder private. While a run can
resume, `checkpoint.private.json` holds the credentials, tool results and host
inputs it needs; a completed run, or one that failed with exit 1 and left no
write unconfirmed, keeps only a scrubbed record.
To test your own MCP service, copy the example and replace its endpoint, allowed
origins, tool policies, test data and response checks.
[Surface workflows](docs/surface-workflows.md) explains UI steps, the agent
driver and resuming runs.
### Extend through submission and sandbox checkout
The [checkout example](examples/coveragecat-sandbox-checkout.yaml) adds review,
submission, Monoline offer selection, consent, bind follow-up, document upload
and simulated payment-method setup. Its 14 steps contain 62 assertions. All
seven tools use the consumer umbrella MCP, and every service step must return
the same sandbox intake.
**Current integration status, 15 October 2026: blocked at submission.**
Coverage Cat returns quote fields that its MCP output schema excludes. It also
returns `minimum_auto_limits` as a string where the schema requires an object.
The host rejects the response. The draft quickstart passes, but the full checkout
test has **not passed end to end**. See the
[failure report and separate diagnostics](docs/coveragecat-sandbox-checkout.md).
Run this command to reproduce the failure or retest after the service contract
is corrected:
```sh
uv run consumer-agent-testing-tool surface-run examples/coveragecat-sandbox-checkout.yaml \
--driver scripted \
--replies examples/coveragecat-sandbox-checkout-replies.json
```
The reply files supply a **fictional shopper's answers**: approval to submit,
credit consent, the Monoline $2M offer at $389, sufficient auto limits, declined
excess UM/UIM, and “No” to both carrier questions. These fixtures do not authorize
transactions for a real shopper. A changed quote or missing or negative approval
stops the next write. The two generated declarations PDFs are labelled
`SYNTHETIC TEST ONLY`.
Once submission works, the example pauses with exit code 3 at
`sandbox_document_review_poll`. **Only at that checkpoint**, wait at least the
reported 60 seconds and resume the printed run:
```sh
sleep 60
uv run consumer-agent-testing-tool surface-run examples/coveragecat-sandbox-checkout.yaml \
--driver scripted --resume runs/surface-YOUR_RUN_ID \
--replies examples/coveragecat-sandbox-checkout-after-review.json
```
The host pauses for your reply but does not measure how long you wait. The
after-review fixture approves the simulated payment callback.
For an agent-driven run, replace `--driver scripted` with
`--driver agent --config agent.yaml` on **both** commands. A resumed run must
keep its original scenario, driver and model configuration.
The sandbox supplies mock payment credentials. The example sends the returned
`seti_sandbox_...` with the fixture `pm_sandbox_consumer_agent_testing_tool`, then requires
`payment_method_on_file`, `waiting_on_carrier`, and no charge confirmation.
Billing checks inspect setup metadata rather than rendered fields. No Stripe
form opens, no card details are collected, and no fallback URL is used. This
test establishes neither premium collection nor active insurance. Test the
embedded payment UI and Stripe test-mode setup separately.
## Test your own service
The quickstarts above are the pieces of one workflow. For an MCP server you
own:
1. **Write a scenario.** `scaffold URL --output my-connector.yaml` reads the
live catalog (`--auth-env VAR` for an API key, or `--auth-from FILE` with a
scenario that holds an OAuth block) and writes a skeleton. Replace every
`TODO(scaffold)`: the user's goal in their own words, the allowlist of tools a
reviewed connector would expose, tool classes your documentation settles,
approval rules for the scripted user, host-private values such as the
user's email, and checks that read the real outcome, from a private state URL
(an oracle) or one of your read tools.
2. **Check readiness.** `check URL --suite muse-guidelines --manifest FILE`
reviews the catalog and your declarations against the pinned connector
guidelines. A clean result is a documented constraint, not an admission.
3. **Pin the happy path.** Replay a scripted calls file with `--driver
scripted`; it needs no model and belongs in CI. After an agent trial you
trust, `scaffold --from-run RUN --output FILE` turns it into one.
4. **Run agent trials.** `--trials N` with a model profile gives pass^k. Run
the same scenario under each treatment a Muse-like host may use:
`--projection muse-cli`, `--exposure search-only`,
`--client-profile muse-template-unofficial-2026-10` and
`--output-schema strict`. The run lock records each one.
5. **Add realism.** [Lane extensions](docs/connector-extensions.md) add
competing connectors and connect-on-request, payment with single-use cards,
background jobs, untrusted content, channel limits and a simulated user.
6. **Compare builds.** `diff` compares two sealed runs and exits 1 on an
outcome regression. Share the output of `export`; the run directory's
`private/` folder holds host-private values.
For an MCP app with its own UI, start from the Coverage Cat scenario and
`surface-run`. For a website, use `browse` with a browser policy, or the
connector lane's `browser` extension. For an HTTP API, write a mapping for
`run --surface api-tools`, or test whether an agent can build its own client
from your docs with the `custom_connector` extension.
[Run a consumer test](#run-a-consumer-test) covers the other lanes.
## Model providers
Create a profile with `init`. This one uses **Muse Spark Open**
(`meta/muse-spark-open`) through OpenRouter, routed only to Together, with zero
data retention (ZDR) required and fallback disabled; set `OPENROUTER_API_KEY` in
your shell before an agent run:
```sh
uv run consumer-agent-testing-tool init --provider openrouter --model meta/muse-spark-open \
--routing-provider together --config agent.yaml
```
`init` also stages, beside the configuration, the API scenarios, the browser
scenarios and the mapped-API example. It stages only scenarios the supported
CLI can run, and refuses providers that have only a configuration template
(`openai`, `anthropic`, `gemini`, `azure-openai`, `vertex-gemini`,
`openai-compatible`) unless `--development-extensions` comes before `init`.
`init --provider none --config scripted.yaml` writes a configuration for
scripted commands that need one. `init` refuses to overwrite an existing file.
Profiles that `init` writes are labeled `specified/unverified`. `doctor --model-preflight`, `run`
and the agent drivers of `connector-run`, `surface-run` and `browse` print a
warning for any profile not labeled `supported`, and the run lock records the
label. Model IDs that contain `contributor` select a mode in which the provider
may train on your prompts; such a profile needs
`allow_training_on_prompts: true` (`init --allow-training-on-prompts`). See
[model providers](docs/model-providers.md) for every adapter, its checks and
its failure labels.
### OpenRouter model and privacy settings
Consumer Agent Testing Tool requests **zero data retention (ZDR) by default**, even when
`routing.zdr` is omitted. To change this, edit the selected profile in
`consumer-agent-testing-tool.yaml` or the file passed to `--config`:
```yaml
model_profiles:
openrouter-dev:
provider: openrouter
api: chat-completions
model_id: meta/muse-spark-open
auth:
mode: api_key
env: OPENROUTER_API_KEY
routing:
only: [together]
allow_fallbacks: false
require_parameters: true
data_collection: deny
zdr: true # Set to false to disable this tool's per-request ZDR requirement.
defaults:
model_profile: openrouter-dev
```
Use unquoted YAML booleans: `true` or `false`. Consumer Agent Testing Tool applies the setting to
every OpenRouter request, including requests that continue after a tool result,
and records it in the routing evidence.
Setting `zdr: false` removes this request-level requirement. It leaves
`data_collection: deny`, the pinned provider and the ban on fallbacks in place.
Your OpenRouter account or guardrails may still enforce ZDR. Request logs show
the policy sent; they cannot independently verify the provider's retention
practices. See [OpenRouter's ZDR documentation](https://openrouter.ai/docs/guides/features/zdr).
**Checked 15 October 2026:** OpenRouter listed Together for Spark Open in both
its model and ZDR endpoint catalogs. A live tool-call test passed with the
routing settings above. Both responses identified Together, and the model used
the tool result correctly. See the
[test record](docs/release-evidence/together-spark-zdr-live-2026-10-15.json).
To choose another provider, set `routing.only` or pass `--routing-provider` to
`init`. Requests never switch
providers or models silently.
Runs use Inspect's dedicated `openrouter` provider, which replays earlier
reasoning in structured form rather than as text. Each response must name the
pinned provider. Responses do not name endpoint variants, so a pin such as
`nebius/fp4` is recorded as `variant unverified`. Streaming is not supported.
See [model providers](docs/model-providers.md#openrouter).
With `zdr: true`, only ZDR-listed endpoints are eligible. On 15 October 2026,
Muse Spark 1.3 and Muse Wonder were served only by Meta on OpenRouter and were
not ZDR-listed, so no ZDR-compliant OpenRouter route existed for them. A
request for them is expected to fail, but no live run has recorded the error.
When OpenRouter's error cites the data policy, it is labeled
`no endpoint satisfies the routing/ZDR policy`. With `routing.only: [together]`,
the `init` default, it may instead be labeled
`routing policy: no allowed provider`.
Sources: [Meta announcement](https://research.meta.ai/blog/muse-spark-open),
[OpenRouter model](https://openrouter.ai/meta/muse-spark-open),
[provider endpoints](https://openrouter.ai/api/v1/models/meta/muse-spark-open/endpoints),
and [ZDR endpoints](https://openrouter.ai/api/v1/endpoints/zdr).
The standalone smoke test and older consumer helper read the same `--config`
and `--model-profile`, including privacy settings. Profiles that `init` writes
leave reasoning effort unset.
```sh
.venv/bin/python scripts/openrouter_smoke.py --config agent.yaml \
--model-profile openrouter-dev
```
This command tests the model connection with synthetic tool data. It needs an
API key and an available route to the pinned provider. It makes no requests to
the service you plan to test.
### Meta Model API
`init --provider meta` writes a profile for Meta's Model API:
```sh
uv run consumer-agent-testing-tool init --provider meta --model muse-spark-1.3 --config meta.yaml
```
The profile uses the Responses API at `https://api.meta.ai/v1` and reads the key
from `MODEL_API_KEY` or `META_API_KEY`; if both are set, they must agree. Add
`--api chat-completions` for Chat Completions, or `--key-env NAME` to use another
variable, which is then the only one read. The adapter uses the OpenAI SDK from
the `openrouter` extra.
The host refuses requests that Meta rejects, such as a forced tool choice,
before sending them (exit 3). Meta adds steering context to every prompt on its
servers, so captured requests are incomplete, and every Meta run records this
as an observation gap. Streaming is not supported, and the API is
pay-as-you-go. See [Meta Model API](docs/model-providers.md#meta-model-api).
No live Meta Model API run has been recorded; the adapter is tested against
local fixtures.
## Run a consumer test
This section covers the embedded MCP apps (`surface-run`), browser (`browse`)
and HTTP API lanes. To let an agent use your MCP server's own tools, use the
[connector lane](#quickstart-connector-lane).
A scenario defines the endpoint, allowed origins, tools, private arguments, UI
actions, consent requirements and result checks, and optionally an API key
for the MCP connection (`auth`). Use endpoints and synthetic transactions you
have permission to test.
```sh
consumer-agent-testing-tool surface-run path/to/scenario.yaml --driver scripted
consumer-agent-testing-tool surface-run path/to/scenario.yaml --driver agent --config agent.yaml
consumer-agent-testing-tool surface-run path/to/scenario.yaml --resume runs/existing-run
consumer-agent-testing-tool browse path/to/browser-policy.json --actions path/to/actions.json
```
The host declares MCP Apps support as a client, finds each tool's UI through its
`tools/list` definition and renders it in a native browser. With
`--driver agent`, the model picks among the scenario's steps (a scripted action
menu) and never sees the server's tools, raw results or private inputs. Service
checks determine the result; a rendered form or a model's claim of success
cannot prove that a transaction completed.
`browse` drives a restricted browser through accessibility snapshots and element
references, with no selectors, scripts or CDP. Its policy file sets the start
`url`, the permitted `origins` and request `rules`, `snapshot_max_chars`
(default 20,000), `history_snapshots` (2) and `dialogs` (`agent`, `accept` or
`dismiss`), as in [examples/browser-policy.json](examples/browser-policy.json).
Long snapshots are paged with `{"kind": "snapshot", "offset": N}`. Refused or
failed actions come back to the agent as observations with a `status`, and
dialogs and file choosers are answered with `kind: dialog` or `kind: upload`.
With `--driver agent`, the provider preflight runs before the browser opens.
The result's `observations` block counts truncated snapshots and refused
actions. When the agent requests a takeover, the run pauses (exit 3). The CLI
has no handback, so control does not return to the agent in that run. In
native mode, WebSockets opened by dedicated workers and WebRTC TCP relays are
not enforced. The policy field `webmcp: true`, off by default, lists
page-registered WebMCP tools in the snapshot and lets the agent call them with
`kind: webmcp`; that lane is labeled "generic WebMCP page tools, not Muse"
(basis: `assumed`), and its evidence notes that Meta documents WebMCP for its
AI glasses, a Meta product that is not Muse. See
[restricted browser behavior](docs/release-extensions.md#restricted-browser-behavior).
HTTP API tests use explicit mappings with the scenario runner. Mappings can
send declared request headers and put the target credential in a custom API-key
header. In API and browser trials, invalid tool proposals are returned to the
model as tool errors, and exhausted limits fail the trial; see
[API scenarios](docs/api-scenarios.md#agent-mistakes-and-limits). To test
whether an agent can build its own client from an API's documentation instead,
use the connector lane's `custom_connector` extension. Prism, Schemathesis and
generated-OpenAPI tools are available only as development extensions. Their
five packages with unresolved original license notices are excluded from the
supported release, not treated as cleared.
### Outcomes and exit codes
`surface-run` follows the exit codes in spec §18:
| Exit | Meaning |
| --- | --- |
| 0 | Completed |
| 1 | Failed: an assertion, a service rejection, a UI interaction failure or an agent error |
| 2 | Setup, infrastructure, integration or reporting failure |
| 3 | Blocked or unsupported: waiting for a reply or approval, an unconfirmed write, a limit, or missing coverage |
Before `Surface report: ...` it prints one line:
`Surface run (exit N) at []: `. `result.json`
records the same `failure_class` and `failure_stage`, and whether the run is
`resumable`. A completed run, or one that failed with exit 1, is final unless
the failure left a write unconfirmed: that write may have taken effect, so the
run stays resumable to reconcile it. Blocked runs and setup failures can resume.
`run` and `browse` use the same four codes, except that exhausting a turn,
tool-call or time limit is a failure (exit 1). Only Inspect operator and custom
limits, and in `run` a deadline that passed before an attempt's first
generation, block instead (exit 3). `run` prints each evidence path on stdout.
`browse` prints the final observation and its evidence path when the run
completes or pauses for takeover, and nothing on stdout otherwise. For any
outcome other than completed, both also print one line on stderr:
`