roadtest 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. roadtest-0.1.0/.gitignore +15 -0
  2. roadtest-0.1.0/LICENSE +21 -0
  3. roadtest-0.1.0/PKG-INFO +331 -0
  4. roadtest-0.1.0/README.md +302 -0
  5. roadtest-0.1.0/examples/command/README.md +30 -0
  6. roadtest-0.1.0/examples/http-json/README.md +32 -0
  7. roadtest-0.1.0/examples/http-streaming/README.md +33 -0
  8. roadtest-0.1.0/examples/openai-compatible/README.md +34 -0
  9. roadtest-0.1.0/examples/python-function/README.md +29 -0
  10. roadtest-0.1.0/pyproject.toml +56 -0
  11. roadtest-0.1.0/skills/roadtest/SKILL.md +65 -0
  12. roadtest-0.1.0/skills/roadtest/references/ci.md +114 -0
  13. roadtest-0.1.0/skills/roadtest/references/scenarios.md +188 -0
  14. roadtest-0.1.0/skills/roadtest/references/setup.md +237 -0
  15. roadtest-0.1.0/skills/roadtest/references/triage.md +65 -0
  16. roadtest-0.1.0/src/roadtest/__init__.py +8 -0
  17. roadtest-0.1.0/src/roadtest/__main__.py +3 -0
  18. roadtest-0.1.0/src/roadtest/agents.py +535 -0
  19. roadtest-0.1.0/src/roadtest/checks.py +130 -0
  20. roadtest-0.1.0/src/roadtest/cli.py +523 -0
  21. roadtest-0.1.0/src/roadtest/config.py +246 -0
  22. roadtest-0.1.0/src/roadtest/demo/__init__.py +1 -0
  23. roadtest-0.1.0/src/roadtest/demo/agent.py +147 -0
  24. roadtest-0.1.0/src/roadtest/demo/scenarios/support.yaml +126 -0
  25. roadtest-0.1.0/src/roadtest/errors.py +27 -0
  26. roadtest-0.1.0/src/roadtest/judge.py +113 -0
  27. roadtest-0.1.0/src/roadtest/llm.py +167 -0
  28. roadtest-0.1.0/src/roadtest/report.py +204 -0
  29. roadtest-0.1.0/src/roadtest/results.py +151 -0
  30. roadtest-0.1.0/src/roadtest/runner.py +248 -0
  31. roadtest-0.1.0/src/roadtest/scaffold.py +149 -0
  32. roadtest-0.1.0/src/roadtest/scenarios.py +277 -0
  33. roadtest-0.1.0/src/roadtest/simulate.py +52 -0
  34. roadtest-0.1.0/src/roadtest/template.py +122 -0
  35. roadtest-0.1.0/src/roadtest/templates/report.html +620 -0
  36. roadtest-0.1.0/tests/conftest.py +137 -0
  37. roadtest-0.1.0/tests/test_adapters.py +134 -0
  38. roadtest-0.1.0/tests/test_cli.py +173 -0
  39. roadtest-0.1.0/tests/test_config.py +130 -0
  40. roadtest-0.1.0/tests/test_demo_end_to_end.py +91 -0
  41. roadtest-0.1.0/tests/test_live_models.py +100 -0
  42. roadtest-0.1.0/tests/test_prompts.py +67 -0
  43. roadtest-0.1.0/tests/test_results.py +145 -0
  44. roadtest-0.1.0/tests/test_runner.py +143 -0
  45. roadtest-0.1.0/tests/test_scenarios.py +152 -0
@@ -0,0 +1,15 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ .DS_Store
9
+ .env
10
+ results/
11
+ node_modules/
12
+ media/out/
13
+ .env.*
14
+ *.pem
15
+ *.key
roadtest-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fenil Ramoliya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,331 @@
1
+ Metadata-Version: 2.5
2
+ Name: roadtest
3
+ Version: 0.1.0
4
+ Summary: Road-test your AI agent before it ships: scenario-based evals for any agent, written by your coding agent.
5
+ Project-URL: Homepage, https://github.com/fenil210/roadtest
6
+ Project-URL: Documentation, https://github.com/fenil210/roadtest#readme
7
+ Project-URL: Issues, https://github.com/fenil210/roadtest/issues
8
+ Author: Fenil Ramoliya
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent-skills,agents,ai,evals,evaluation,llm,llm-judge,testing
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: pydantic>=2.6
24
+ Requires-Dist: pyyaml>=6.0
25
+ Requires-Dist: rich>=13.7
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ <p align="center">
31
+ <img src="docs/assets/logo.svg" width="84" alt="">
32
+ </p>
33
+
34
+ <h1 align="center">roadtest</h1>
35
+
36
+ <p align="center">
37
+ <b>Road-test your AI agent before it ships.</b><br>
38
+ Open-source evals for any agent. Your coding agent writes the scenarios.<br>
39
+ roadtest runs them, grades them, and stops a bad release.
40
+ </p>
41
+
42
+ <p align="center">
43
+ <a href="https://pypi.org/project/roadtest/"><img src="https://img.shields.io/pypi/v/roadtest?color=34D399" alt="PyPI"></a>
44
+ <a href="https://pypi.org/project/roadtest/"><img src="https://img.shields.io/pypi/pyversions/roadtest" alt="Python versions"></a>
45
+ <a href="https://github.com/fenil210/roadtest/actions/workflows/ci.yml"><img src="https://github.com/fenil210/roadtest/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
46
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT licence"></a>
47
+ </p>
48
+
49
+ <p align="center">
50
+ <img src="docs/assets/demo.gif" width="820" alt="roadtest demo: twelve scenarios run against an agent, two planted bugs are caught, and the gate fails">
51
+ </p>
52
+
53
+ <p align="center">
54
+ <a href="docs/assets/launch.mp4"><img src="docs/assets/launch-poster.png" width="420" alt="Watch the launch video"></a><br>
55
+ <sub><a href="docs/assets/launch.mp4">Watch the 55-second tour</a> (sound on)</sub>
56
+ </p>
57
+
58
+ ## Why roadtest
59
+
60
+ - **It works with the agent you already have.** Point it at an HTTP endpoint, a Python function or
61
+ a command. There is no SDK to adopt, no code to wrap and no tracing to install. LangGraph, CrewAI,
62
+ the OpenAI Agents SDK, your own loop: roadtest tests it from the outside, the way your users do.
63
+ - **Your coding agent does the setup.** `roadtest init` installs a skill that teaches Claude Code,
64
+ Codex, Cursor and Copilot how to connect your agent and write scenarios for it. You say
65
+ "write 20 scenarios" and review a YAML file.
66
+ - **It answers one question: can this ship?** Scenarios are either `gate` (must pass) or `watch`
67
+ (tracked). The command exits non-zero when a gate scenario fails, so it drops into CI unchanged.
68
+
69
+ ## Try it in 30 seconds
70
+
71
+ ```bash
72
+ pip install roadtest
73
+ roadtest demo
74
+ ```
75
+
76
+ The demo runs twelve scenarios against a small bundled agent that has two bugs planted in it, and
77
+ shows you roadtest catching them. It needs no setup and no API key.
78
+
79
+ ## Use it on your agent
80
+
81
+ ### 1. Add roadtest to your project
82
+
83
+ ```bash
84
+ pip install roadtest
85
+ roadtest init
86
+ ```
87
+
88
+ This creates `evals/roadtest.yaml`, a starter scenario file, and the roadtest skill for your coding
89
+ agent.
90
+
91
+ ### 2. Let your coding agent set it up
92
+
93
+ Open Claude Code, Codex, Cursor or Copilot in the project and say:
94
+
95
+ > Set up roadtest for my agent, then write 20 scenarios and run them.
96
+
97
+ The agent reads your code, fills in `evals/roadtest.yaml`, checks the connection with
98
+ `roadtest doctor`, writes scenarios that cover your agent's real behaviour, runs them, and tells
99
+ you which failures look like real bugs.
100
+
101
+ Prefer to do it by hand? It is one small file. See [connecting an agent](#connecting-an-agent).
102
+
103
+ ### 3. Run it
104
+
105
+ ```bash
106
+ roadtest run
107
+ ```
108
+
109
+ ```text
110
+ ✓ order-status-in-transit 0.6s
111
+ ✓ refund-within-window 0.8s
112
+ ✗ refund-outside-window 0.7s
113
+ ✓ prompt-injection 0.5s
114
+ ...
115
+
116
+ gate 9/10 passed 90% 1 failed
117
+ watch 0/1 passed 0% 1 failed
118
+
119
+ ✗ refund-outside-window A refund outside the 30-day window is declined
120
+ does not call start_refund: called: lookup_order, start_refund
121
+ judge: declines-the-refund: The reply says "I've started a refund of €35.00".
122
+ It approves the refund and never mentions the 30-day window.
123
+
124
+ ✗ Gate failed 1 of 10 gate scenarios did not pass
125
+ ```
126
+
127
+ Every run also writes an HTML report with each conversation, each tool call and the judge's
128
+ reasoning for every verdict:
129
+
130
+ <p align="center">
131
+ <img src="docs/assets/report.png" width="820" alt="The roadtest HTML report: a failed gate, the scenario list, and one expanded scenario with its conversation and checks">
132
+ </p>
133
+
134
+ ## What a scenario looks like
135
+
136
+ ```yaml
137
+ # evals/scenarios/refunds.yaml
138
+ scenarios:
139
+ - id: refund-outside-window
140
+ title: A refund outside the 30-day window is declined
141
+ why: refunding outside the policy window costs money on every late request
142
+ user: Please refund order A-1002.
143
+ expect:
144
+ tools_called: [lookup_order] # code checks: free, instant, exact
145
+ tools_not_called: [start_refund]
146
+ contains_any: ["30 days", "30-day"]
147
+ rubric: # graded by a judge model, pass or fail
148
+ - Declines the refund and explains that the order is outside the 30-day window.
149
+ ```
150
+
151
+ Conversations with several turns, and users simulated by a model, work the same way:
152
+
153
+ ```yaml
154
+ - id: refund-remembers-order
155
+ turns:
156
+ - user: Hi, I have a question about order A-1001.
157
+ - user: Can I get a refund for it?
158
+ expect:
159
+ tools_called: [start_refund]
160
+
161
+ - id: impatient-customer-refund
162
+ simulate:
163
+ persona: An impatient customer who writes very short messages.
164
+ goal: Get a refund for order A-1001 because the helmet does not fit.
165
+ expect:
166
+ rubric:
167
+ - The agent asked for the order number before it started the refund.
168
+ ```
169
+
170
+ | `expect` key | Passes when |
171
+ | --- | --- |
172
+ | `contains`, `contains_any`, `not_contains` | the reply has all, any or none of the strings |
173
+ | `matches` | every regular expression matches the reply |
174
+ | `tools_called`, `tools_any`, `tools_not_called` | the agent called all, any or none of the tools |
175
+ | `max_seconds` | the reply arrived in time |
176
+ | `rubric` | a judge model agrees with every line |
177
+ | `checks` | your own Python function returns true |
178
+
179
+ The full format is in the [scenarios guide](skills/roadtest/references/scenarios.md).
180
+
181
+ ## Connecting an agent
182
+
183
+ `evals/roadtest.yaml` tells roadtest how to reach your agent. For an HTTP endpoint:
184
+
185
+ ```yaml
186
+ agent:
187
+ url: http://localhost:8000/chat
188
+ headers:
189
+ Authorization: "Bearer ${AGENT_API_KEY}" # from your shell or a .env file
190
+ body:
191
+ message: "{{message}}"
192
+ session_id: "{{session_id}}" # one conversation per scenario
193
+ response:
194
+ text: reply # where the answer is in the JSON
195
+ tool_calls: "tool_calls[*].name" # optional: lets scenarios check tools
196
+
197
+ targets: # the same agent elsewhere
198
+ staging:
199
+ url: https://staging.example.com/chat
200
+ ```
201
+
202
+ Then check it:
203
+
204
+ ```bash
205
+ roadtest probe "Where is my order?" # shows the request, the raw reply and what roadtest read
206
+ roadtest doctor # config, scenarios, agent and models in one check
207
+ ```
208
+
209
+ | Your agent is | Use | |
210
+ | --- | --- | --- |
211
+ | an HTTP endpoint returning JSON or text | `type: http` | [example](examples/http-json) |
212
+ | a streaming endpoint (SSE or JSON lines) | `type: http` with `format: sse` | [example](examples/http-streaming) |
213
+ | an OpenAI-compatible endpoint | `type: http` with `messages: "{{messages}}"` | [example](examples/openai-compatible) |
214
+ | a Python function or object, or any other protocol | `type: python` | [example](examples/python-function) |
215
+ | a program in another language | `type: command` | [example](examples/command) |
216
+
217
+ Sessions, streaming, server-issued conversation ids and every option are in the
218
+ [setup guide](skills/roadtest/references/setup.md).
219
+
220
+ ## Commands
221
+
222
+ | Command | What it does |
223
+ | --- | --- |
224
+ | `roadtest init` | Add roadtest to a project: config, starter scenarios, coding-agent skill |
225
+ | `roadtest demo` | A full run against the bundled demo agent |
226
+ | `roadtest doctor` | Check that the config, the agent and the models all work |
227
+ | `roadtest probe "hi"` | Send one message and show exactly what came back |
228
+ | `roadtest validate` | Check the scenario files without running them |
229
+ | `roadtest run` | Run scenarios. Narrow with ids, `--tag`, `--tier`; add `--target`, `--repeat` |
230
+ | `roadtest report` | Show a past run again, or open its HTML report |
231
+ | `roadtest compare` | What changed between two runs: regressions, fixes, new scenarios |
232
+ | `roadtest baseline` | Pin a run as the baseline that `compare` uses |
233
+ | `roadtest guide` | Print a guide, for you or for your coding agent |
234
+
235
+ ## Before and after every deploy
236
+
237
+ ```yaml
238
+ # .github/workflows/evals.yml
239
+ - run: pip install roadtest
240
+ - run: roadtest run # on pull requests: exits 1 if a gate scenario fails
241
+ env:
242
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
243
+ ```
244
+
245
+ ```bash
246
+ roadtest run --tag core --target prod # after a deploy: a short check that production behaves
247
+ roadtest run --repeat 3 # nightly: a scenario passes only if all three runs pass
248
+ roadtest compare # what regressed since the baseline
249
+ ```
250
+
251
+ The pass and fail summary lands on the GitHub job summary page automatically. Complete workflows are
252
+ in the [CI guide](skills/roadtest/references/ci.md).
253
+
254
+ ## Coding agent support
255
+
256
+ `roadtest init` installs the skill into the project. You can also install it on its own.
257
+
258
+ | Agent | How the skill gets there |
259
+ | --- | --- |
260
+ | Claude Code | `roadtest init` writes `.claude/skills/roadtest/`. Or as a plugin: `/plugin marketplace add fenil210/roadtest`, then `/plugin install roadtest@roadtest` |
261
+ | Codex, Cursor, GitHub Copilot, OpenCode, Amp | `roadtest init` writes `.agents/skills/roadtest/`, which they all read |
262
+ | Any of them | `npx skills add fenil210/roadtest` |
263
+ | Anything else that can run a shell command | `roadtest guide setup`, `roadtest guide scenarios`, `roadtest guide triage`, `roadtest guide ci` print the same instructions |
264
+
265
+ The skill covers four jobs: connecting your agent, writing scenarios, explaining failures and
266
+ adding roadtest to CI.
267
+
268
+ ## Models
269
+
270
+ roadtest uses two models, both through your OpenAI API key:
271
+
272
+ ```yaml
273
+ models:
274
+ judge: gpt-6-luna # decides pass or fail for each rubric line
275
+ generator: gpt-6.1-terra # plays the user in simulated conversations
276
+ ```
277
+
278
+ Set `OPENAI_API_KEY` in your shell or in a `.env` file. Change either model to any id your key can
279
+ use. `base_url` accepts any server that speaks the OpenAI Chat Completions API. Code checks need no
280
+ model at all: `roadtest run --no-judge` runs with no key.
281
+
282
+ ## How it works
283
+
284
+ ```text
285
+ scenario.yaml ──▶ adapter ──▶ your agent (HTTP, Python function or command)
286
+ │ reply + tool calls
287
+ ▼
288
+ code checks judge model
289
+ (text, tools, time) (one rubric line → pass or fail, with reasoning)
290
+ │
291
+ ▼
292
+ gate: every must-pass scenario passed? ──▶ exit code, report, run.json
293
+ ```
294
+
295
+ A few choices worth knowing:
296
+
297
+ - **Pass or fail, never a score.** Each rubric line gets a yes or a no with a written reason. There
298
+ are no 1-to-5 scales to argue about.
299
+ - **A broken judge is not a failed agent.** If the agent is unreachable or the judge call fails, the
300
+ scenario is an `error`, reported apart from real failures.
301
+ - **Repeats are strict.** With `--repeat 3` a scenario passes only if all three runs pass, because
302
+ an agent that fails one time in three has a bug.
303
+ - **Everything is a file.** Scenarios, config and the baseline live in your repo and go through
304
+ code review like the rest of your code.
305
+
306
+ ## FAQ
307
+
308
+ **Do I need to change my agent's code?**
309
+ No. If it has an endpoint, roadtest calls it. To check which tools were called, the reply has to
310
+ include them; the [setup guide](skills/roadtest/references/setup.md#tool-calls) shows the options.
311
+
312
+ **How many scenarios do I need?**
313
+ Start with about 20 that cover what the agent is for, then add one each time you find a bug.
314
+
315
+ **What does a run cost?**
316
+ Your agent's own cost per scenario, plus one small judge call per rubric line. Code checks are free.
317
+
318
+ **My agent is not deterministic. Will results flip?**
319
+ Sometimes, and that is information. Use `--repeat` to measure it, and move anything exact into code
320
+ checks.
321
+
322
+ **Can I use it without a coding agent?**
323
+ Yes. Scenarios are plain YAML and the [guides](skills/roadtest/references) are written for people too.
324
+
325
+ ## Contributing
326
+
327
+ Bug reports, ideas and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
328
+
329
+ ## Licence
330
+
331
+ [MIT](LICENSE)
@@ -0,0 +1,302 @@
1
+ <p align="center">
2
+ <img src="docs/assets/logo.svg" width="84" alt="">
3
+ </p>
4
+
5
+ <h1 align="center">roadtest</h1>
6
+
7
+ <p align="center">
8
+ <b>Road-test your AI agent before it ships.</b><br>
9
+ Open-source evals for any agent. Your coding agent writes the scenarios.<br>
10
+ roadtest runs them, grades them, and stops a bad release.
11
+ </p>
12
+
13
+ <p align="center">
14
+ <a href="https://pypi.org/project/roadtest/"><img src="https://img.shields.io/pypi/v/roadtest?color=34D399" alt="PyPI"></a>
15
+ <a href="https://pypi.org/project/roadtest/"><img src="https://img.shields.io/pypi/pyversions/roadtest" alt="Python versions"></a>
16
+ <a href="https://github.com/fenil210/roadtest/actions/workflows/ci.yml"><img src="https://github.com/fenil210/roadtest/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
17
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT licence"></a>
18
+ </p>
19
+
20
+ <p align="center">
21
+ <img src="docs/assets/demo.gif" width="820" alt="roadtest demo: twelve scenarios run against an agent, two planted bugs are caught, and the gate fails">
22
+ </p>
23
+
24
+ <p align="center">
25
+ <a href="docs/assets/launch.mp4"><img src="docs/assets/launch-poster.png" width="420" alt="Watch the launch video"></a><br>
26
+ <sub><a href="docs/assets/launch.mp4">Watch the 55-second tour</a> (sound on)</sub>
27
+ </p>
28
+
29
+ ## Why roadtest
30
+
31
+ - **It works with the agent you already have.** Point it at an HTTP endpoint, a Python function or
32
+ a command. There is no SDK to adopt, no code to wrap and no tracing to install. LangGraph, CrewAI,
33
+ the OpenAI Agents SDK, your own loop: roadtest tests it from the outside, the way your users do.
34
+ - **Your coding agent does the setup.** `roadtest init` installs a skill that teaches Claude Code,
35
+ Codex, Cursor and Copilot how to connect your agent and write scenarios for it. You say
36
+ "write 20 scenarios" and review a YAML file.
37
+ - **It answers one question: can this ship?** Scenarios are either `gate` (must pass) or `watch`
38
+ (tracked). The command exits non-zero when a gate scenario fails, so it drops into CI unchanged.
39
+
40
+ ## Try it in 30 seconds
41
+
42
+ ```bash
43
+ pip install roadtest
44
+ roadtest demo
45
+ ```
46
+
47
+ The demo runs twelve scenarios against a small bundled agent that has two bugs planted in it, and
48
+ shows you roadtest catching them. It needs no setup and no API key.
49
+
50
+ ## Use it on your agent
51
+
52
+ ### 1. Add roadtest to your project
53
+
54
+ ```bash
55
+ pip install roadtest
56
+ roadtest init
57
+ ```
58
+
59
+ This creates `evals/roadtest.yaml`, a starter scenario file, and the roadtest skill for your coding
60
+ agent.
61
+
62
+ ### 2. Let your coding agent set it up
63
+
64
+ Open Claude Code, Codex, Cursor or Copilot in the project and say:
65
+
66
+ > Set up roadtest for my agent, then write 20 scenarios and run them.
67
+
68
+ The agent reads your code, fills in `evals/roadtest.yaml`, checks the connection with
69
+ `roadtest doctor`, writes scenarios that cover your agent's real behaviour, runs them, and tells
70
+ you which failures look like real bugs.
71
+
72
+ Prefer to do it by hand? It is one small file. See [connecting an agent](#connecting-an-agent).
73
+
74
+ ### 3. Run it
75
+
76
+ ```bash
77
+ roadtest run
78
+ ```
79
+
80
+ ```text
81
+ ✓ order-status-in-transit 0.6s
82
+ ✓ refund-within-window 0.8s
83
+ ✗ refund-outside-window 0.7s
84
+ ✓ prompt-injection 0.5s
85
+ ...
86
+
87
+ gate 9/10 passed 90% 1 failed
88
+ watch 0/1 passed 0% 1 failed
89
+
90
+ ✗ refund-outside-window A refund outside the 30-day window is declined
91
+ does not call start_refund: called: lookup_order, start_refund
92
+ judge: declines-the-refund: The reply says "I've started a refund of €35.00".
93
+ It approves the refund and never mentions the 30-day window.
94
+
95
+ ✗ Gate failed 1 of 10 gate scenarios did not pass
96
+ ```
97
+
98
+ Every run also writes an HTML report with each conversation, each tool call and the judge's
99
+ reasoning for every verdict:
100
+
101
+ <p align="center">
102
+ <img src="docs/assets/report.png" width="820" alt="The roadtest HTML report: a failed gate, the scenario list, and one expanded scenario with its conversation and checks">
103
+ </p>
104
+
105
+ ## What a scenario looks like
106
+
107
+ ```yaml
108
+ # evals/scenarios/refunds.yaml
109
+ scenarios:
110
+ - id: refund-outside-window
111
+ title: A refund outside the 30-day window is declined
112
+ why: refunding outside the policy window costs money on every late request
113
+ user: Please refund order A-1002.
114
+ expect:
115
+ tools_called: [lookup_order] # code checks: free, instant, exact
116
+ tools_not_called: [start_refund]
117
+ contains_any: ["30 days", "30-day"]
118
+ rubric: # graded by a judge model, pass or fail
119
+ - Declines the refund and explains that the order is outside the 30-day window.
120
+ ```
121
+
122
+ Conversations with several turns, and users simulated by a model, work the same way:
123
+
124
+ ```yaml
125
+ - id: refund-remembers-order
126
+ turns:
127
+ - user: Hi, I have a question about order A-1001.
128
+ - user: Can I get a refund for it?
129
+ expect:
130
+ tools_called: [start_refund]
131
+
132
+ - id: impatient-customer-refund
133
+ simulate:
134
+ persona: An impatient customer who writes very short messages.
135
+ goal: Get a refund for order A-1001 because the helmet does not fit.
136
+ expect:
137
+ rubric:
138
+ - The agent asked for the order number before it started the refund.
139
+ ```
140
+
141
+ | `expect` key | Passes when |
142
+ | --- | --- |
143
+ | `contains`, `contains_any`, `not_contains` | the reply has all, any or none of the strings |
144
+ | `matches` | every regular expression matches the reply |
145
+ | `tools_called`, `tools_any`, `tools_not_called` | the agent called all, any or none of the tools |
146
+ | `max_seconds` | the reply arrived in time |
147
+ | `rubric` | a judge model agrees with every line |
148
+ | `checks` | your own Python function returns true |
149
+
150
+ The full format is in the [scenarios guide](skills/roadtest/references/scenarios.md).
151
+
152
+ ## Connecting an agent
153
+
154
+ `evals/roadtest.yaml` tells roadtest how to reach your agent. For an HTTP endpoint:
155
+
156
+ ```yaml
157
+ agent:
158
+ url: http://localhost:8000/chat
159
+ headers:
160
+ Authorization: "Bearer ${AGENT_API_KEY}" # from your shell or a .env file
161
+ body:
162
+ message: "{{message}}"
163
+ session_id: "{{session_id}}" # one conversation per scenario
164
+ response:
165
+ text: reply # where the answer is in the JSON
166
+ tool_calls: "tool_calls[*].name" # optional: lets scenarios check tools
167
+
168
+ targets: # the same agent elsewhere
169
+ staging:
170
+ url: https://staging.example.com/chat
171
+ ```
172
+
173
+ Then check it:
174
+
175
+ ```bash
176
+ roadtest probe "Where is my order?" # shows the request, the raw reply and what roadtest read
177
+ roadtest doctor # config, scenarios, agent and models in one check
178
+ ```
179
+
180
+ | Your agent is | Use | |
181
+ | --- | --- | --- |
182
+ | an HTTP endpoint returning JSON or text | `type: http` | [example](examples/http-json) |
183
+ | a streaming endpoint (SSE or JSON lines) | `type: http` with `format: sse` | [example](examples/http-streaming) |
184
+ | an OpenAI-compatible endpoint | `type: http` with `messages: "{{messages}}"` | [example](examples/openai-compatible) |
185
+ | a Python function or object, or any other protocol | `type: python` | [example](examples/python-function) |
186
+ | a program in another language | `type: command` | [example](examples/command) |
187
+
188
+ Sessions, streaming, server-issued conversation ids and every option are in the
189
+ [setup guide](skills/roadtest/references/setup.md).
190
+
191
+ ## Commands
192
+
193
+ | Command | What it does |
194
+ | --- | --- |
195
+ | `roadtest init` | Add roadtest to a project: config, starter scenarios, coding-agent skill |
196
+ | `roadtest demo` | A full run against the bundled demo agent |
197
+ | `roadtest doctor` | Check that the config, the agent and the models all work |
198
+ | `roadtest probe "hi"` | Send one message and show exactly what came back |
199
+ | `roadtest validate` | Check the scenario files without running them |
200
+ | `roadtest run` | Run scenarios. Narrow with ids, `--tag`, `--tier`; add `--target`, `--repeat` |
201
+ | `roadtest report` | Show a past run again, or open its HTML report |
202
+ | `roadtest compare` | What changed between two runs: regressions, fixes, new scenarios |
203
+ | `roadtest baseline` | Pin a run as the baseline that `compare` uses |
204
+ | `roadtest guide` | Print a guide, for you or for your coding agent |
205
+
206
+ ## Before and after every deploy
207
+
208
+ ```yaml
209
+ # .github/workflows/evals.yml
210
+ - run: pip install roadtest
211
+ - run: roadtest run # on pull requests: exits 1 if a gate scenario fails
212
+ env:
213
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
214
+ ```
215
+
216
+ ```bash
217
+ roadtest run --tag core --target prod # after a deploy: a short check that production behaves
218
+ roadtest run --repeat 3 # nightly: a scenario passes only if all three runs pass
219
+ roadtest compare # what regressed since the baseline
220
+ ```
221
+
222
+ The pass and fail summary lands on the GitHub job summary page automatically. Complete workflows are
223
+ in the [CI guide](skills/roadtest/references/ci.md).
224
+
225
+ ## Coding agent support
226
+
227
+ `roadtest init` installs the skill into the project. You can also install it on its own.
228
+
229
+ | Agent | How the skill gets there |
230
+ | --- | --- |
231
+ | Claude Code | `roadtest init` writes `.claude/skills/roadtest/`. Or as a plugin: `/plugin marketplace add fenil210/roadtest`, then `/plugin install roadtest@roadtest` |
232
+ | Codex, Cursor, GitHub Copilot, OpenCode, Amp | `roadtest init` writes `.agents/skills/roadtest/`, which they all read |
233
+ | Any of them | `npx skills add fenil210/roadtest` |
234
+ | Anything else that can run a shell command | `roadtest guide setup`, `roadtest guide scenarios`, `roadtest guide triage`, `roadtest guide ci` print the same instructions |
235
+
236
+ The skill covers four jobs: connecting your agent, writing scenarios, explaining failures and
237
+ adding roadtest to CI.
238
+
239
+ ## Models
240
+
241
+ roadtest uses two models, both through your OpenAI API key:
242
+
243
+ ```yaml
244
+ models:
245
+ judge: gpt-6-luna # decides pass or fail for each rubric line
246
+ generator: gpt-6.1-terra # plays the user in simulated conversations
247
+ ```
248
+
249
+ Set `OPENAI_API_KEY` in your shell or in a `.env` file. Change either model to any id your key can
250
+ use. `base_url` accepts any server that speaks the OpenAI Chat Completions API. Code checks need no
251
+ model at all: `roadtest run --no-judge` runs with no key.
252
+
253
+ ## How it works
254
+
255
+ ```text
256
+ scenario.yaml ──▶ adapter ──▶ your agent (HTTP, Python function or command)
257
+ │ reply + tool calls
258
+ ▼
259
+ code checks judge model
260
+ (text, tools, time) (one rubric line → pass or fail, with reasoning)
261
+ │
262
+ ▼
263
+ gate: every must-pass scenario passed? ──▶ exit code, report, run.json
264
+ ```
265
+
266
+ A few choices worth knowing:
267
+
268
+ - **Pass or fail, never a score.** Each rubric line gets a yes or a no with a written reason. There
269
+ are no 1-to-5 scales to argue about.
270
+ - **A broken judge is not a failed agent.** If the agent is unreachable or the judge call fails, the
271
+ scenario is an `error`, reported apart from real failures.
272
+ - **Repeats are strict.** With `--repeat 3` a scenario passes only if all three runs pass, because
273
+ an agent that fails one time in three has a bug.
274
+ - **Everything is a file.** Scenarios, config and the baseline live in your repo and go through
275
+ code review like the rest of your code.
276
+
277
+ ## FAQ
278
+
279
+ **Do I need to change my agent's code?**
280
+ No. If it has an endpoint, roadtest calls it. To check which tools were called, the reply has to
281
+ include them; the [setup guide](skills/roadtest/references/setup.md#tool-calls) shows the options.
282
+
283
+ **How many scenarios do I need?**
284
+ Start with about 20 that cover what the agent is for, then add one each time you find a bug.
285
+
286
+ **What does a run cost?**
287
+ Your agent's own cost per scenario, plus one small judge call per rubric line. Code checks are free.
288
+
289
+ **My agent is not deterministic. Will results flip?**
290
+ Sometimes, and that is information. Use `--repeat` to measure it, and move anything exact into code
291
+ checks.
292
+
293
+ **Can I use it without a coding agent?**
294
+ Yes. Scenarios are plain YAML and the [guides](skills/roadtest/references) are written for people too.
295
+
296
+ ## Contributing
297
+
298
+ Bug reports, ideas and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
299
+
300
+ ## Licence
301
+
302
+ [MIT](LICENSE)