TouchStone
More tools
Stack comparisonsCompare two container images

TouchStone documentation

Regression tests for RL environments. You review the tests, TouchStone runs them on your machine or CI runner, and only approved tests can fail a build.

How it fits together

Suite
A folder with suite.json and your adapter.py.
Adapter
Python you own that drives your environment and returns what it observed.
Case
One test: an input, the requirement it checks, and assertions on the observations.
Approval
A reviewer's sign-off on the exact content of a case. Any edit clears it.
Run
One execution, with every observation and assertion result kept as evidence.

Quickstart

Needs Linux (or WSL) and Python 3.12+. No GPU, container engine or API key.

  1. Install

    uv tool install touchstone-us
  2. Start the workspace

    touchstone serve

    Opens on loopback only, at port 4474. Change it with --port.

  3. Open the example

    Go to 127.0.0.1:4474/tests and choose Create the example suite.

Where files go, upgrading and source checkouts
  • Suites, reviews and history live under .local/ in the folder you run from. Add it to .gitignore.
  • To open your own suite, set TOUCHSTONE_SAFETY_SUITE=path/to/suite before touchstone serve.
  • touchstone doctor checks whether this host can run the container-based tools. The test workflow does not need them.
  • After uv tool upgrade touchstone-us, restart the server; a running process keeps the old version.
  • From a source checkout, run uv sync --frozen and npm start, and prefix commands with uv run.
  • The status page at /status shows whether the latest approved run passed, what failed, what changed, and run history by environment version, with the commands to reproduce each failure.

Starter walkthrough

The example checkout evaluator has a planted bug: it pays reward for a success marker even when nothing was bought. You catch it, fix it and keep the test.

  1. Create the suite

    Writes suite.json and adapter.py. Nothing runs yet.

  2. Add starter tests

    forged-success expects reward 0. valid-checkout expects reward 1.

  3. Review and approve both

    Your approval is tied to the exact content you read.

  4. Run approved tests

    forged-success fails with reward 1 and no purchase. The control passes, so the failure is real.

  5. Fix the evaluator

    In adapter.py, set REQUIRE_PURCHASE = True. No restart needed.

  6. Run again and compare

    Both pass. Compared with the failing run, the forged case shows Fixed.

Connect your environment

Pick how your grader runs. init writes the adapter for the first three.

touchstone init ./touchstone-suite \
  --requirement "Reward requires a paid backend order" \
  --http http://grader.internal:8080/evaluate

Each case input is POSTed as JSON. The JSON reply is the observation.

Timeouts, interpreters and other adapter rules
  • Each case times out after 30 seconds by default. Exceptions, timeouts, bad output and cleanup errors are inconclusive, never a pass.
  • The subprocess is not a sandbox for hostile code, and forced termination can't guarantee cleanup. Use disposable resources.
  • The adapter runs from a frozen copy. Install its dependencies in the runner and reference files by package or explicit path.
  • Set "python" in suite.json (for example "../.venv/bin/python") to run under your project's interpreter. A missing one exits 4.
  • Adapter changes are recorded by hash, so a fix is checked against the same approved test. Dependencies and services aren't frozen.
  • Without a target flag, init writes a skeleton whose cases stay inconclusive until you fill it in.

Write tests

Cases are JSON assertions on what your adapter returns.

candidates.json
{
  "cases": [{
    "id": "forged-checkout",
    "title": "A confirmation without an order earns no reward",
    "requirement_id": "verified-purchase",
    "input": {"action": "forge-marker"},
    "assertions": [
      {"path": "purchased", "operator": "eq", "expected": false},
      {"path": "reward", "operator": "eq", "expected": 0}
    ],
    "provenance": {"kind": "imported", "source": "Owner-authored regression"}
  }]
}
touchstone generate DIR --from-json candidates.json

Operators: eq ne lte gte contains not_contains. Paths use dots for nested fields. Missing fields and wrong types never pass, and there is no eval.

Reusing an ID edits that case and clears its approval.

Propose tests with a model

Nothing calls a hosted model by default. Use your own signed-in Codex CLI or any trusted command:

touchstone generate .local/my-suite --codex
touchstone generate .local/my-suite --command python my_author.py

Or run touchstone suggest DIR to rank twenty reward-hacking patterns against your requirements, with no model at all. Twelve come from published taxonomies and eight from game and robotics field reports.

What the model sees

The suite, requirements, existing cases and adapter source. Never run history or network captures. Codex runs read-only in a temporary directory for up to five minutes. A custom command gets JSON on stdin and must return a cases object within 120 seconds and 1 MiB.

Gate CI

Run touchstone check and branch on the exit code.

.github/workflows/touchstone.yml
jobs:
  regression:
    runs-on: [self-hosted, linux]
    steps:
      - uses: actions/checkout@v7
      - uses: astral-sh/setup-uv@v10
      - run: uv tool install touchstone-us
      - run: touchstone check touchstone-suite --evidence-dir /var/lib/touchstone/evidence
Exit codes
0Every approved assertion passed.
2An approved assertion failed.
3Inconclusive: no approved cases, a timeout, a crash or bad adapter output.
4Bad configuration, the suite is locked by another run, or storage failed.
130Interrupted. Partial evidence is kept as inconclusive.
What to commit

Commit suite.json, adapter.py, approvals.json and .touchstone-suite.lock. Keep .touchstone/ out of Git.

approvals.json holds each reviewer's decision and the hash it was made on, with no test content. A fresh checkout gates on the same approved cases; an approval whose hash no longer matches is ignored. When the file and local history disagree, the newer decision wins.

If you mount a suite read-only, create the lock file in the writable source first and keep it. --evidence-dir on persistent storage keeps history across jobs. An optional runner image is described in RUNNER.md.

Share with a team

Optional and always explicit. The control plane is a separate app your organization runs; no hosted instance is open to customers yet.

touchstone sync .local/my-suite <run-id> \
  --url http://127.0.0.1:4480 --suite checkout
DefaultIDs, timestamps, hashes and statuses only.
--include-evidenceAlso requirements, inputs, adapter source, observations and logs. An owner must enable it first.
--pull-reviewsApplies team decisions locally where hashes still match.
Tokens, metrics and retention

Sign in with GitHub, create an organization API token and set TOUCHSTONE_API_TOKEN. Tokens can upload but never approve; owners can revoke them. Uploaded evidence is customer-reported, not server attestation.

The shared workspace shows runs, approved and failing tests, verified fixes and how long each failure stayed open (elapsed time from run timestamps, not effort), with CSV export. touchstone train ./suite -- python train.py adds GPU-hours spent training on versions that fail an approved test. Owners can set a retention window.

Commands

Every browser action has a CLI equivalent, and output is JSON.

init DIRCreate a suite. Add --requirement with --http, --command or --from-harbor for your own environment.
generate DIRAdd starter candidates, or import with --from-json, --codex or --command.
suggest DIRDraft cases from twenty reward-hacking patterns. Imports nothing; generate --from-json accepts the output once each kept draft has an input.
review DIR CASEApprove or reject at the hash you read. In scripts, pass --sha256 from report.
check DIRRun approved cases and exit with a CI code. --timeout sets seconds per case, up to 300.
report DIR [RUN]Print the suite with hashes, or one run's evidence. --csv for spreadsheets.
sync DIR RUNUpload a run to your control plane.

All commands accept --evidence-dir PATH; use the same path for review, check and report. From Python, use SafetyStore in touchstone.safety.store and check in touchstone.safety.engine.

Other tools

Two earlier workflows ship in the same server. Both need rootless Podman.

What a pass means

Reviews are self-reported local decisions, not authenticated release approvals. Treat inconclusive runs as unfinished work.

What it does not detect

Tests check what the grader returns for a known input. These failures happen while the grader behaves exactly as approved.

Objective-level hacking

Spurious signals from token-level credit misalignment, not an exploitable verifier (Dong et al., arXiv 2602.01103). Watch: training-inference discrepancy and loss dynamics in your trainer's logs.

Training-inference numerical mismatch

Small numerical disagreements between rollout engine and trainer can cause collapse on their own (Zhong et al., arXiv 2605.14220). Watch: generation-time log probabilities against the trainer's recomputed values.

Capability regressions under a sound verifier

The policy can get worse while every test passes, because tests exercise the grader. Watch: held-out evaluations and reward curves per environment version.

Verifier-aware attacks

Cases assume the verifier is hidden. If the agent can read it, it can target it. Watch: that the verifier stays outside the agent's filesystem and network on every version.

Anything without a deterministic oracle

Rubric or model-graded quality and open-ended tasks have no expected value to approve. Watch: your judge calibration and human spot checks.

Usage reports

Each finished check, grader audit or stack comparison sends one small record to the TouchStone control plane. A failed report never affects the run.

Sent

Product, outcome, test counts, duration, package and Python version, OS, architecture, whether it ran in CI, a random install ID, and the run's IDs and hashes.

Never sent

Test content, inputs, observations, adapter source, paths or error text.

export TOUCHSTONE_TELEMETRY=off

DO_NOT_TRACK=1, TOUCHSTONE_LOCAL_ONLY=1 or check --local-only also send nothing. TOUCHSTONE_TELEMETRY=log prints each record instead.

How reports are attributed

Connected safety suites report to their saved control plane with the saved organization token, which takes precedence over telemetry settings. Other runs send TOUCHSTONE_API_TOKEN only to TOUCHSTONE_TELEMETRY_URL when you set it (the server that issued the token), attributing reports to your organization and the member who created the token; redirects are never followed. Otherwise the default host gets a one-way fingerprint of the token, which groups your runs but can't be used as the token. Without a token a report is anonymous, unless your organization uploads the same run's summary, which links the two.

Licensing

TouchStone is proprietary. You may run it on infrastructure you control for a thirty-day evaluation; continued use needs a written agreement such as a pilot or subscription. No redistribution, modification or use in a competing product. The full text ships as LICENSE. The control plane is licensed separately under the Functional Source License.