Share fixtures
Keep fixture data and schemas separate from each interface:fixtures/users.ts
MCP
mcp/users.ts
CLI
cli/users.ts
cli.pathis the executable name.command.pathis a non-empty list of argv segments.- Every input key needs an
optionsentry. Its flag is the key in kebab case. Setname(with the leading--) to override it. - String options accept
--id valueor--id=value. Boolean options aretruewhen present, so make them optional or give them a schema default. - Positional arguments are not supported.
sphynx-sh also exports a command for validate. If one file needs both, import one under another name.
Attach and validate
Attach MCP and CLI mocks on the suite, then check the call logs in a validator.users.eval.ts
unknown, so parse them with the shared schema before asserting on values.
HTTP APIs
Define endpoints withapi() and endpoint(). Standard Schema types the handler input and checks each response against the schema for its status code. Handlers are plain TypeScript.
mocks/catalog.ts
item and Item into a shared fixture file to reuse them with MCP and CLI mocks. API names use lowercase letters, digits and hyphens.
Attach an API
catalog.eval.ts
await api.url("catalog"). No environment variables or global fetch are replaced.
Requests and responses
- The input schema receives
{ params, query, headers, body }. Headers are lowercase. Repeated query values are arrays. An empty body isnull, and a nonempty body must be JSON. Use schema transforms for coercion. - Handlers return
{ status, body, headers? }. Usenullfor bodyless responses such as 204. The runtime sets content type and framing headers. - An optional endpoint
descriptionis shown to the agent. - Error responses you declare, such as 404 or 429, are normal mock behavior. Unmatched routes return 404 and are recorded with
matched: false. - A handler that throws, a response that fails its schema, or a body that cannot be serialized fails the mock. Nothing is forwarded upstream.
{ signal, log }. Call log(value) to attach evidence to the call, and use signal to cancel async work.
Evidence
HTTP calls appear in the trial’s Calls view and are available to judges.api.calls(name?) returns records with method, path, status, timing, whether a route matched, input, output, handler logs and errors. Calls to api.url and api.calls appear in the validator’s evidence.
Input, output and log values are bounded snapshots with text, format, state and truncated fields. Check those before parsing captured JSON.
Authorization headers, cookies and common secret fields are redacted before recording. Add more field names (case-insensitive) with api({ redact: ["accessToken"], ... }). Before a call is stored in the trial, anything that looks like a key and every secret the trial knows, such as its harness and sandbox credentials, are also replaced with [redacted], as described in secrets in evidence. A secret with no known shape inside a string is still recorded, so keep credentials out of fixtures.
Validator-owned servers
UsewithApi() when a validator needs its own server with hidden test data. The server closes when the callback finishes or fails.
calls(), not the trial’s call log. Log them inside the validator to include them in its logs.
Limits
- JSON HTTP endpoints only. No proxying, streaming, WebSockets or automatic recording.
- Request bodies up to 1 MiB, handlers up to 30 seconds, and 256 requests per mock runtime.
api.calls()inside a validator returns values up to 64,000 characters. The trial stores each value at up to 16,000 characters, cut after redaction.
Where mocks run
Mocks apply to every variant in the suite. Put variants in separate eval files when they need different mocks. CLI mocks are installed for every harness. MCP mocks are configured for every built-in harness, but not the command harness. Handlers need no credentials for the service they simulate. Fixture data is bundled into the sandbox, so keep secrets out of it. Running the eval still needsSPHYNX_API_KEY and a model connection.