qe-harness
Black-box, five-stage validation of conversation-to-action AI assistants.
A chat-only test sees the final reply. qe-harness tests every turn at five stages, from the outside:
intent → orchestration → MCP tool calls → backend state → user response
It never imports the application under test. It signs in as test users and chats over the app's API, reads the app's per-turn traces, records every tool call independently through its own MCP proxy, and compares read-only snapshots of the business data before and after each turn. So it catches what a polite reply hides: a write the customer never approved, an amount no tool returned, a reply that says "done" when nothing was saved.
Install
pip install qe-harness # core
pip install "qe-harness[judge]" # plus the optional LLM grader (Azure OpenAI or Anthropic)
Python 3.11 or newer.
Quick start
qe init # writes qe.yaml (sandbox connection) and qe_catalog.yaml (test cases) in this folder
# edit both files, then provide the two secrets the config names:
# QE_SANDBOX_TOKEN sandbox control token
# QE_TEST_PASSWORD test users' password
# (environment variables, or a .env file next to qe.yaml)
qe doctor # connection checks: all green means testing can start
qe run --open # run every suite, evaluate each turn, open the HTML report
qe finds its configuration from --config PATH, then $QE_CONFIG, then qe.yaml in the working directory.
Relative paths in it start from the folder holding the file (or from base_dir: if set).
What the app's sandbox must provide
| Need | Endpoint or setting |
|---|---|
| Sign-in as test users | POST /api/login {"username", "password"}, session cookie |
| Conversations | POST /api/conversations → {"id"}; WebSocket /ws?conversation=<id> with messages {"type": "user", "text"} and {"type": "confirm", "key", "approved"}; events ready, awaiting_confirmation, assistant_message, error, turn_completed |
| Per-turn traces | GET /api/conversations/<id>/traces: intent (primary_intent, intents), plan steps, tool calls with arguments and outcomes |
| File uploads (optional) | POST /api/conversations/<id>/documents?filename=… |
| Control | GET /sandbox/status (versions, clock, toggles) and POST /sandbox/reset, with header x-sandbox-token |
| Independent tool record | Start the MCP tool server through python -m qe_harness.mcp_proxy --log-dir <dir> -- <server command> |
| Business data | A read-only SQLite replica the harness can snapshot |
Commands
| Command | What it does |
|---|---|
qe init [--dir D] [--force] |
Starter qe.yaml and qe_catalog.yaml |
qe doctor [--quick] [--workers] |
Network, control API, clock, versions, proxy toggle, reset, read-back, sign-in, round trip, trace link, tool capture; --workers checks every sandbox |
qe status |
Sandbox versions, clock and toggles |
qe cases [--suite S] |
List catalog cases |
qe run [--suite S…] [--case ID] [--repeats N] [--judge] [--parallel N] [--open] |
Reset the sandbox, run the suites, evaluate every turn, write evidence and the HTML report; --parallel N spreads suites over N sandboxes |
qe runs |
Recent runs |
qe show RUN CASE |
Evidence for one case: message, approvals, intent, plan, trace and proxy tool calls, data changes, reply, verdicts |
qe report RUN [--open] |
Rebuild the self-contained HTML report |
qe coverage [RUN] [--trend] [--open] |
Intent coverage for a run, or per intent across runs; --open opens the most recent finished run's report |
qe mcp serve |
Run the harness as an MCP server for AI assistants |
What is checked per turn
| Stage | Checks |
|---|---|
| Intent | The router's primary intent is one of the expected intents |
| Orchestration | Writes only after approval; no unexpected approval prompts (approval steps approve only the expected action with matching arguments; anything else is declined and fails here); no repeated identical calls |
| MCP tool call | Required calls succeeded with the expected arguments; forbidden calls absent; trace and proxy agree |
| Backend state | Only the expected tables changed; SQL checks; declined → nothing changed; data changed → a write succeeded |
| User response | Required facts present, forbidden phrases absent, optional LLM grader for consistency with what the tools did |
Each failure records its first failing stage, so developers know where to look. With --repeats N, every case gets
a pass rate with a 95% Wilson range and unstable cases are reported as flaky.
Parallel runs (one sandbox per worker)
Every run resets its sandbox, so one sandbox runs one test at a time (a lock enforces it). To go faster, give the
harness several identical sandboxes: list the extra ones under workers: in the config, each with its own URL,
read-only data path and proxy log folder.
workers:
- {base_url: http://127.0.0.1:8101, sor_path: data/sandbox-2.db, proxy_log_dir: qe_runs/proxy-2}
- {base_url: http://127.0.0.1:8102, sor_path: data/sandbox-3.db, proxy_log_dir: qe_runs/proxy-3}
qe doctor --workers # check every sandbox
qe run --judge --parallel 3 # the main sandbox plus two workers
Work is split into (repeat, suite) units; each runs on the next free worker from a fresh reset of that sandbox, and the results merge into one run and one report. Suites must be independent for this: each is one test user's conversation, and its data checks should be scoped to that user's records. The sandboxes must report the same versions and clock, or the run refuses to start.
Intent coverage
List the app's intent families under intents: in qe.yaml. Each report shows, per family, the cases that test it,
their pass rate and a status (covered, thin, failing, not tested), plus a families × runs grid of how coverage
changes over time. qe coverage --trend shows the same in the terminal.
In AI assistants (MCP)
claude mcp add qe-harness -- qe mcp serve
For Cursor or VS Code, add a stdio server with command qe and args ["mcp", "serve"].
Tools: sandbox_status, sandbox_doctor, list_cases, get_case, run_tests (changes state; capped per call),
get_run_status, list_runs, get_failure (masked evidence, marked as untrusted data), get_intent_coverage,
get_intent_trend. Prompt: triage_run.
LLM grader (optional)
qe run --judge asks a model whether each reply is consistent with what the tools actually did. It uses its own
settings, separate from the app's: QE_JUDGE_PROVIDER = AZURE (with QE_JUDGE_AZURE_API_KEY,
QE_JUDGE_AZURE_ENDPOINT, QE_JUDGE_AZURE_DEPLOYMENT) or ANTHROPIC (with ANTHROPIC_API_KEY, optional
QE_JUDGE_MODEL).
Status
Alpha. Not yet included: fault injection, load and soak testing, simulated customers, robustness variant generation and a release gate.
License
MIT
Metadata
Release files for qe-harness 0.2.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| qe_harness-0.2.3.tar.gz | 48.9 kB | With provenance | Details for qe_harness-0.2.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| qe_harness-0.2.3-py3-none-any.whl | Python 3 | none | any | Details for qe_harness-0.2.3-py3-none-any.whl |
Total release size: 102.7 kB
Release files / qe_harness-0.2.3.tar.gz
| Download URL | qe_harness-0.2.3.tar.gz |
|---|---|
| Size | 48.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
46621c82e9bd701f07c1f40552ce687f7a6766d16c3fb07c17e6b4196ba7e605
|
|
BLAKE2b-256 checksum How to use checksums |
8f2ee62dc91013735aa5c75befcbad34350ac81fbe5e3cd853a6c6b135c9c10c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logRelease files / qe_harness-0.2.3-py3-none-any.whl
| Download URL | qe_harness-0.2.3-py3-none-any.whl |
|---|---|
| Size | 53.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
14cb367bdea15be38f7758018cf666d120dd5ce57c88478d101fbc9c453103b6
|
|
BLAKE2b-256 checksum How to use checksums |
5dd95c58b3597ad792be0e50cc2a2c31fc8d92aeadf1c71efeb98a88f5b24c6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logSecurity for qe-harness 0.2.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for qe_harness-0.2.3.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.