CATT logo: an orange cartoon cat at a desk, smiling over its shoulder, with CATT spelled in code on the monitor

Consumer Agent Testing Tool (CATT)

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: `