ghostlab 0.1.0__tar.gz → 0.2.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.
- {ghostlab-0.1.0 → ghostlab-0.2.0}/.gitignore +9 -0
- ghostlab-0.2.0/PKG-INFO +506 -0
- ghostlab-0.2.0/README.md +466 -0
- ghostlab-0.2.0/docs/cli.md +354 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/docs/concepts.md +19 -0
- ghostlab-0.2.0/docs/getting-started.md +126 -0
- ghostlab-0.2.0/docs/ghostlab-vision-gap-and-roadmap.md +963 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/mkdocs.yml +1 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/__init__.py +1 -1
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/apps_host/executor.py +18 -5
- ghostlab-0.2.0/rehearsal/apps_host/live.py +356 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/apps_host/protocol.py +81 -4
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/apps_host/renderer.py +81 -16
- ghostlab-0.2.0/rehearsal/cli.py +2238 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/config.py +27 -12
- ghostlab-0.2.0/rehearsal/contract.py +501 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/critique.py +18 -6
- ghostlab-0.2.0/rehearsal/dashboard.py +456 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/dataset.py +2 -1
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/evaluate.py +33 -17
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/generate.py +22 -9
- ghostlab-0.2.0/rehearsal/hosts/__init__.py +63 -0
- ghostlab-0.2.0/rehearsal/hosts/base.py +80 -0
- ghostlab-0.2.0/rehearsal/hosts/direct.py +236 -0
- ghostlab-0.2.0/rehearsal/hosts/runner.py +295 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/inspect.py +7 -1
- ghostlab-0.2.0/rehearsal/jobs.py +245 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/mcp_apps.py +70 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/mcp_client.py +96 -20
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/mcp_config.py +4 -2
- ghostlab-0.2.0/rehearsal/mcp_targets.py +128 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/orchestrator.py +54 -1
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/personas.py +20 -7
- ghostlab-0.2.0/rehearsal/plan.py +611 -0
- ghostlab-0.2.0/rehearsal/plan_generate.py +131 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/profile.py +14 -3
- ghostlab-0.2.0/rehearsal/prompts.py +218 -0
- ghostlab-0.2.0/rehearsal/readiness.py +386 -0
- ghostlab-0.2.0/rehearsal/sampling.py +300 -0
- ghostlab-0.2.0/rehearsal/setup_runtime.py +331 -0
- ghostlab-0.2.0/rehearsal/spec.py +529 -0
- ghostlab-0.2.0/rehearsal/termcolor.py +96 -0
- ghostlab-0.2.0/rehearsal/testrun.py +237 -0
- ghostlab-0.2.0/rehearsal/ui/app.py +487 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_apps_host.py +6 -1
- ghostlab-0.2.0/tests/test_apps_live.py +126 -0
- ghostlab-0.2.0/tests/test_cli_spec_flow.py +471 -0
- ghostlab-0.2.0/tests/test_contract.py +216 -0
- ghostlab-0.2.0/tests/test_hosts_runner.py +304 -0
- ghostlab-0.2.0/tests/test_jobs.py +192 -0
- ghostlab-0.2.0/tests/test_mcp_client.py +72 -0
- ghostlab-0.2.0/tests/test_mcp_targets.py +110 -0
- ghostlab-0.2.0/tests/test_plan.py +201 -0
- ghostlab-0.2.0/tests/test_plan_generate.py +105 -0
- ghostlab-0.2.0/tests/test_readiness.py +169 -0
- ghostlab-0.2.0/tests/test_sampling.py +187 -0
- ghostlab-0.2.0/tests/test_setup_runtime.py +160 -0
- ghostlab-0.2.0/tests/test_spec.py +160 -0
- ghostlab-0.2.0/tests/test_testrun.py +240 -0
- ghostlab-0.2.0/tests/test_widgets_and_dashboard.py +138 -0
- ghostlab-0.1.0/PKG-INFO +0 -669
- ghostlab-0.1.0/README.md +0 -629
- ghostlab-0.1.0/docs/cli.md +0 -146
- ghostlab-0.1.0/docs/getting-started.md +0 -49
- ghostlab-0.1.0/rehearsal/cli.py +0 -1027
- ghostlab-0.1.0/rehearsal/prompts.py +0 -90
- ghostlab-0.1.0/rehearsal/ui/app.py +0 -1095
- {ghostlab-0.1.0 → ghostlab-0.2.0}/CONTRIBUTING.md +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/LICENSE +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/docs/datasets-evaluation.md +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/docs/index.md +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/docs/publishing.md +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/ghostlab/__init__.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/ghostlab/__main__.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/ghostlab/cli.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/llms.txt +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/personas/careful-celpip-beginner.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/personas/general-learning-edge-case.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/personas/ielts-power-user.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/personas/skeptical-toefl-retaker.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/pyproject.toml +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/__main__.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/apps_host/__init__.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/apps_host/assertions.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/apps_host/report.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/codex_backend.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/compare.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/logging.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/report.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/review.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/runner_presets.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/runners.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/scorecard.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/__init__.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/db.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/hashing.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/ids.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/migrations/0001_initial.sql +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/redact.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/storage/repository.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/tool_capture.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/types.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/rehearsal/ui/__init__.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/claude-process.example.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/codex-cortex-aut.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/codex-cortex-local-aut.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/codex-cortex-local-session.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/codex-process.example.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/codex-user-emulator.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/mock-aut.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/runners/mock-user.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/scenarios/basic-discovery.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/scenarios/cortex-onboarding-status.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/scenarios/cortex-practice-generation.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/scenarios/error-recovery.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/targets/cortex-local.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/targets/cortex-ngrok.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/targets/example-http.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/targets/example-stdio.json +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_compare.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_config.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_critique.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_dataset.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_evaluate.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_generate.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_inspect_lint.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_mcp_apps.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_orchestrator.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_personas.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_profile.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_review.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_runner_presets.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_scorecard.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_session_runner.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_storage.py +0 -0
- {ghostlab-0.1.0 → ghostlab-0.2.0}/tests/test_tool_capture.py +0 -0
|
@@ -222,3 +222,12 @@ __marimo__/
|
|
|
222
222
|
|
|
223
223
|
# Streamlit UI workspace (generated)
|
|
224
224
|
ghostlab_workspace/
|
|
225
|
+
|
|
226
|
+
# ghostlab spec-flow local artifacts
|
|
227
|
+
.ghostlab/
|
|
228
|
+
ghostlab.sqlite3
|
|
229
|
+
|
|
230
|
+
# ghostlab jobs — track job.yaml + test-plan.*, ignore per-job run artifacts + db
|
|
231
|
+
jobs/*/workspace/
|
|
232
|
+
jobs/*/runs/
|
|
233
|
+
jobs/*/ghostlab.sqlite3*
|
ghostlab-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ghostlab
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Local end-to-end testing lab for any MCP server: coding agents role-play real users, drive your tools over multiple turns, score runs, and render/interact with MCP Apps ui:// widgets.
|
|
5
|
+
Project-URL: Documentation, https://sajjadgg.github.io/Rehearsal/
|
|
6
|
+
Project-URL: Homepage, https://github.com/sajjadGG/Rehearsal
|
|
7
|
+
Project-URL: Repository, https://github.com/sajjadGG/Rehearsal
|
|
8
|
+
Project-URL: Issues, https://github.com/sajjadGG/Rehearsal/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/sajjadGG/Rehearsal/releases
|
|
10
|
+
Author: Sajjad Gholamzadeh
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: agents,ai-agents,claude,cli,codex,end-to-end-testing,evaluation,llm,llm-evaluation,mcp,mcp-apps,model-context-protocol,testing
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
25
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
26
|
+
Classifier: Topic :: Software Development :: Testing
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Provides-Extra: apps
|
|
29
|
+
Requires-Dist: playwright>=1.40; extra == 'apps'
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
32
|
+
Requires-Dist: mkdocs>=1.6; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
35
|
+
Provides-Extra: docs
|
|
36
|
+
Requires-Dist: mkdocs>=1.6; extra == 'docs'
|
|
37
|
+
Provides-Extra: ui
|
|
38
|
+
Requires-Dist: streamlit>=1.30; extra == 'ui'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# MCP Rehearsal / Ghostlab
|
|
42
|
+
|
|
43
|
+
> A local, end-to-end **testing lab for any MCP server** — coding agents role-play
|
|
44
|
+
> real users, drive your tools over multiple turns, and the harness captures
|
|
45
|
+
> traces, scores outcomes, and even **renders and clicks through MCP Apps UI
|
|
46
|
+
> widgets**.
|
|
47
|
+
|
|
48
|
+
[](https://github.com/sajjadGG/Rehearsal/actions)
|
|
49
|
+
[](https://sajjadgg.github.io/Rehearsal/)
|
|
50
|
+
[](pyproject.toml)
|
|
51
|
+
[](LICENSE)
|
|
52
|
+
[](llms.txt)
|
|
53
|
+
|
|
54
|
+
**Test your MCP server the way it's actually used** — not with unit tests against
|
|
55
|
+
the protocol, but with a real coding agent (Codex / Claude) that picks tools,
|
|
56
|
+
makes mistakes, and tries to accomplish goals, while a second agent plays the
|
|
57
|
+
user. Protocol-level checks (schema errors, a tool call that 500s) are useful
|
|
58
|
+
sanity checks, but they aren't the real test — the real test is whether an
|
|
59
|
+
agent can actually get a task done through your MCP, end to end.
|
|
60
|
+
|
|
61
|
+
📖 **Docs wiki:** https://sajjadgg.github.io/Rehearsal/ · 🤖 **For agents:** [`llms.txt`](llms.txt) · 🛠 **Contributing:** [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
|
62
|
+
|
|
63
|
+
## Quickstart
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python3.13 -m venv .venv
|
|
67
|
+
.venv/bin/pip install -e . # add '.[ui]' for the web UI, '.[apps]' for widget rendering
|
|
68
|
+
|
|
69
|
+
ghostlab create
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
That's the whole flow. `ghostlab create` walks you through everything, end to end:
|
|
73
|
+
|
|
74
|
+
1. **Name + target** — the only two prompts. A target is an MCP URL, or a path
|
|
75
|
+
to a target JSON / standard `mcpServers` config.
|
|
76
|
+
2. **Discover** — connects to the target, lints its contract (schema errors,
|
|
77
|
+
risk labels), and probes any MCP Apps `ui://` widgets.
|
|
78
|
+
3. **Configure semantic testing** — if `codex` is on your `$PATH`, offers to
|
|
79
|
+
wire it up as the **agent-under-test**: a real coding-agent session with
|
|
80
|
+
your MCP's tools available, so the dual-agent conversations below have
|
|
81
|
+
something to actually drive. (No codex? The wizard still finishes —
|
|
82
|
+
protocol-level suites still run, semantic/security just skip until a host
|
|
83
|
+
is configured; see [Runner Configs](#runner-configs).)
|
|
84
|
+
4. **Generate a test plan** — personas × scenarios for the semantic/security
|
|
85
|
+
suites, plus deterministic coverage for every discovered tool
|
|
86
|
+
(`test-plan.yaml`), all editable afterward.
|
|
87
|
+
5. **Pick which suites to run** — defaults to everything; narrow it to just
|
|
88
|
+
`semantic` while you're iterating, or the full set for a release check.
|
|
89
|
+
6. **Run + review** — executes the plan against your configured host(s),
|
|
90
|
+
writes a colored pass/fail summary plus a dashboard, and prints the
|
|
91
|
+
readiness/gate verdict.
|
|
92
|
+
|
|
93
|
+
Everything the wizard does is one of `discover` / `plan` / `test` / `review`
|
|
94
|
+
under the hood — run any of them standalone afterward to iterate without
|
|
95
|
+
repeating the whole wizard:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
ghostlab discover --job <name> # re-inspect after the target changes
|
|
99
|
+
ghostlab plan --job <name> # regenerate/curate the test plan
|
|
100
|
+
ghostlab test --job <name> # rerun (add --suite semantic to narrow it)
|
|
101
|
+
ghostlab review --job <name> # the readiness/gate report on its own
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
A job is a self-contained folder: `jobs/<name>/job.yaml` (target, hosts,
|
|
105
|
+
generation/test defaults, gates — all editable), `test-plan.yaml`, `workspace/`
|
|
106
|
+
(discover/generated/test artifacts + a local sqlite db), and `runs/`.
|
|
107
|
+
|
|
108
|
+
## What you get
|
|
109
|
+
|
|
110
|
+
| Stage | What it produces |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| **Discover** | A deterministic `contract.json` (schema lint, risk labels, MCP Apps metadata checks) and a refreshed `capabilities:` section in `job.yaml` |
|
|
113
|
+
| **Plan** | A coverage-driven `test-plan.yaml`: deterministic protocol cases for every tool, plus generated persona/scenario cases for the semantic/security suites |
|
|
114
|
+
| **Test** | Multi-host execution results (`results.json`/`results.md`), a standalone HTML dashboard, and — for conversational cases — full dual-agent transcripts with structured tool-call capture |
|
|
115
|
+
| **Review** | A readiness report: pass/fail gate verdict, failure clusters, and prioritized repairs |
|
|
116
|
+
|
|
117
|
+
## Goal
|
|
118
|
+
|
|
119
|
+
Build a repeatable, sandboxed tester that can:
|
|
120
|
+
|
|
121
|
+
- Run any target MCP app in an isolated environment.
|
|
122
|
+
- Launch one coding-agent session as the **agent-under-test** (with target MCP injected).
|
|
123
|
+
- Launch another coding-agent session as the **user emulator** (persona + goal driven).
|
|
124
|
+
- Drive multi-turn interactions between them.
|
|
125
|
+
- Capture full traces, tool activity, failures, and outcomes.
|
|
126
|
+
|
|
127
|
+
This lets you test with your existing Codex/Claude usage path, instead of wiring a separate LLM provider deployment just for E2E testing.
|
|
128
|
+
|
|
129
|
+
## Scope
|
|
130
|
+
|
|
131
|
+
Rehearsal is intentionally **app-agnostic**:
|
|
132
|
+
|
|
133
|
+
- Works with any MCP server reachable by stdio/SSE/streamable HTTP.
|
|
134
|
+
- Supports local or remote MCP endpoints.
|
|
135
|
+
- Supports multiple coding-agent runners (Codex, Claude Code, and future adapters).
|
|
136
|
+
|
|
137
|
+
No Cortex-specific assumptions are required in the core harness.
|
|
138
|
+
|
|
139
|
+
## Reference
|
|
140
|
+
|
|
141
|
+
Everything below is the individual-command reference and advanced usage —
|
|
142
|
+
useful once you're past the first `ghostlab create` run, or scripting CI.
|
|
143
|
+
|
|
144
|
+
### Job folder layout
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
jobs/<name>/
|
|
148
|
+
job.yaml # target, setup, hosts, generation, test, prompts, gates
|
|
149
|
+
test-plan.yaml # produced by `ghostlab plan`
|
|
150
|
+
workspace/ # discover/, generated/, test/ artifacts + ghostlab.sqlite3
|
|
151
|
+
runs/ # dual-agent run output
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Core dual-harness architecture
|
|
155
|
+
|
|
156
|
+
1. **AUT Harness (Agent Under Test)** — starts a coding-agent session (Codex or
|
|
157
|
+
Claude Code), injects the target MCP server config into it, and exposes a
|
|
158
|
+
controlled I/O bridge so it can receive user messages and return
|
|
159
|
+
replies/tool results.
|
|
160
|
+
2. **User Emulator Harness** — starts a second coding-agent session, gives it a
|
|
161
|
+
scenario file (persona, goals, constraints, success criteria), and asks it
|
|
162
|
+
to act like a realistic user, sending messages turn-by-turn to the AUT.
|
|
163
|
+
3. **Orchestrator** — coordinates turn-taking, timeouts, retries, and stop
|
|
164
|
+
conditions; logs every message/event in structured format; produces a run
|
|
165
|
+
report with bug candidates and reproduction context.
|
|
166
|
+
|
|
167
|
+
### Target configuration model
|
|
168
|
+
|
|
169
|
+
Each test run points to a target definition:
|
|
170
|
+
|
|
171
|
+
- `target.id`: unique name (`filesystem-mcp-local`, `my-app-staging`)
|
|
172
|
+
- `transport`: `stdio` | `sse` | `streamable-http`
|
|
173
|
+
- `connection`: command+args+env (stdio) or URL+headers (network transports)
|
|
174
|
+
- `capabilities`: optional expected tools/resources/prompts
|
|
175
|
+
- `startup`: optional health checks and boot timeout
|
|
176
|
+
|
|
177
|
+
### Commands
|
|
178
|
+
|
|
179
|
+
The package installs two equivalent console scripts: `ghostlab` and `rehearsal`.
|
|
180
|
+
|
|
181
|
+
- `ghostlab create` — the end-to-end wizard described above.
|
|
182
|
+
- `ghostlab init` — create a `job.yaml`/`ghostlab.yaml` from an existing target JSON, without the wizard prompts.
|
|
183
|
+
- `ghostlab discover` — inspect the job's target, lint its contract, refresh capabilities.
|
|
184
|
+
- `ghostlab plan` — generate (or curate) the coverage-driven test plan.
|
|
185
|
+
- `ghostlab test` — execute the test plan across the job's host adapters.
|
|
186
|
+
- `ghostlab review` — readiness report over discover + plan + test artifacts (release gate).
|
|
187
|
+
- `ghostlab inspect` — connect to a target MCP and capture what it exposes (no job needed).
|
|
188
|
+
- `ghostlab profile` — turn an `inspect.json` into a capability profile (codex).
|
|
189
|
+
- `ghostlab generate-scenarios` / `generate-personas` / `generate-dataset` — build reusable persona×scenario datasets outside the job model.
|
|
190
|
+
- `ghostlab review-dataset` / `run-dataset` — curate and run a standalone dataset.
|
|
191
|
+
- `ghostlab run` — run one dual-agent E2E scenario directly.
|
|
192
|
+
- `ghostlab evaluate` — score a run into a pass/fail verdict (codex judge).
|
|
193
|
+
- `ghostlab compare` — diff two dataset runs for regressions.
|
|
194
|
+
- `ghostlab apps-probe` / `apps-render` — probe/render MCP Apps `ui://` widgets.
|
|
195
|
+
- `ghostlab doctor` — check codex and validate runner presets.
|
|
196
|
+
- `ghostlab dashboard` — build a standalone HTML dashboard for a `ghostlab test` run.
|
|
197
|
+
- `ghostlab ui` — launch the Streamlit pipeline UI.
|
|
198
|
+
- `ghostlab db` — manage the SQLite persistence database.
|
|
199
|
+
|
|
200
|
+
### The UI: `ghostlab ui`
|
|
201
|
+
|
|
202
|
+
Run the whole pipeline from a browser instead of the CLI:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
pip install 'ghostlab[ui]' # installs streamlit
|
|
206
|
+
ghostlab ui # opens http://localhost:8501
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The app mirrors the same job-based flow as `ghostlab create`: pick or create a
|
|
210
|
+
job, discover its target, configure the agent-under-test host, generate and
|
|
211
|
+
curate the test plan, run selected suites, and review colored results and full
|
|
212
|
+
conversation traces — the same `job.yaml`/`test-plan.yaml`/`results.json` a CLI
|
|
213
|
+
run of the same job would produce.
|
|
214
|
+
|
|
215
|
+
### Understand a new MCP: `inspect`
|
|
216
|
+
|
|
217
|
+
Point it at a target and it introspects the server without any coding-agent
|
|
218
|
+
credits or manual `curl`:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
ghostlab inspect --target targets/cortex-local.json
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
This connects over the configured transport (stdio / streamable-HTTP / SSE),
|
|
225
|
+
runs the `initialize` handshake, and pages through `tools/list`,
|
|
226
|
+
`resources/list`, `resources/templates/list`, and `prompts/list`. It writes
|
|
227
|
+
`runs/<id>-inspect/inspect.json` (raw) and `inspect.md` (readable), and **lints**
|
|
228
|
+
tool/resource descriptions for references to tools the server does not actually
|
|
229
|
+
expose. This capability dump is the input to capability profiling and scenario
|
|
230
|
+
generation.
|
|
231
|
+
|
|
232
|
+
### Profile a new MCP: `profile`
|
|
233
|
+
|
|
234
|
+
Turn the raw `inspect.json` into a structured **capability profile** — the
|
|
235
|
+
bridge between Understand and Generate. Deterministic structure (tool taxonomy
|
|
236
|
+
by name family, read/write state surfaces, gaps) is computed locally; a domain
|
|
237
|
+
summary and inferred multi-step workflows are generated by codex:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
ghostlab profile \
|
|
241
|
+
--inspect runs/<id>-inspect/inspect.json
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
It writes `capabilities.json` + `capabilities.md` next to the `inspect.json`.
|
|
245
|
+
Generated workflow steps are filtered to real tool names, so the profile never
|
|
246
|
+
references hallucinated or non-exposed tools.
|
|
247
|
+
|
|
248
|
+
### Generate scenarios: `generate-scenarios`
|
|
249
|
+
|
|
250
|
+
Generate grounded use-case scenarios the MCP supports, derived from the
|
|
251
|
+
capability profile:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
ghostlab generate-scenarios \
|
|
255
|
+
--profile runs/<id>-inspect/capabilities.json \
|
|
256
|
+
--n 3 \
|
|
257
|
+
--output-dir scenarios
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Scenarios are spread across intents (`happy_path` / `edge_case` / `adversarial`)
|
|
261
|
+
and each declares an `exercises` list of the tools it should drive the assistant
|
|
262
|
+
to use. Tool references are filtered to real tool names.
|
|
263
|
+
|
|
264
|
+
### Build a persona library: `generate-personas`
|
|
265
|
+
|
|
266
|
+
Personas are reusable **user profiles** decoupled from scenarios, so the same
|
|
267
|
+
persona can be paired with many scenarios (the basis for the dataset matrix).
|
|
268
|
+
Generate a domain-relevant library from a capability profile:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
ghostlab generate-personas \
|
|
272
|
+
--profile runs/<id>-inspect/capabilities.json \
|
|
273
|
+
--n 4 \
|
|
274
|
+
--output-dir personas
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Each persona has a `summary`, behavioral `traits` (terse, impatient, easily
|
|
278
|
+
confused, non-native, ...), and a domain `context` map (native_language,
|
|
279
|
+
target_exam, level, ...). Pass one to a run with `--persona`:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
ghostlab run ... --persona personas/ielts-power-user.json
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Build a dataset: `generate-dataset`
|
|
286
|
+
|
|
287
|
+
A dataset is a **persona x scenario matrix** — different users, and different
|
|
288
|
+
scenarios tailored to each of them. For every persona, codex generates
|
|
289
|
+
persona-specific scenarios, and the pairs become runnable cases:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
ghostlab generate-dataset \
|
|
293
|
+
--profile runs/<id>-inspect/capabilities.json \
|
|
294
|
+
--personas 3 --scenarios-per-persona 3 --seed 7 \
|
|
295
|
+
--name cortex
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
This writes a self-contained dataset directory:
|
|
299
|
+
|
|
300
|
+
```text
|
|
301
|
+
datasets/cortex/
|
|
302
|
+
dataset.json manifest: mcp, seed, cases[]
|
|
303
|
+
personas/<id>.json
|
|
304
|
+
scenarios/<id>.json persona-namespaced; inline `persona` is a situational note
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Review & curate a dataset: `review-dataset`
|
|
308
|
+
|
|
309
|
+
Before spending agent credits, check that the dataset makes sense:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
ghostlab review-dataset \
|
|
313
|
+
--dataset datasets/cortex \
|
|
314
|
+
--profile runs/<id>-inspect/capabilities.json
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
This writes `review.md` + `review.json` with a tool-coverage matrix, per-case
|
|
318
|
+
previews, and flags (near-duplicate cases, scenarios exercising non-exposed
|
|
319
|
+
tools, personas with no scenarios). Curation is file-first — each case gets a
|
|
320
|
+
`status` in `dataset.json` (`pending` / `approved` / `rejected` / `needs-edit`):
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
ghostlab review-dataset --dataset datasets/cortex \
|
|
324
|
+
--approve case-a case-b --reject case-c
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Run a dataset: `run-dataset`
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
ghostlab run-dataset \
|
|
331
|
+
--dataset datasets/cortex \
|
|
332
|
+
--target targets/cortex-local.json \
|
|
333
|
+
--aut-runner runners/codex-cortex-aut.json \
|
|
334
|
+
--user-runner runners/codex-user-emulator.json \
|
|
335
|
+
--limit 2
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Each case runs through the orchestrator (with its persona) into its own run
|
|
339
|
+
directory, and a dataset-level `summary.md` + `results.json` capture per-case
|
|
340
|
+
status and turn counts.
|
|
341
|
+
|
|
342
|
+
### Tool-call capture & output hygiene
|
|
343
|
+
|
|
344
|
+
Every run captures structured MCP tool calls from the agent host. The codex AUT
|
|
345
|
+
runners set `"parser": "codex-json"` and run `codex exec --json`, so the
|
|
346
|
+
orchestrator parses the JSONL stream and records each `mcp_tool_call` with its
|
|
347
|
+
arguments, result, error, and status into `events.jsonl`, with a per-turn table
|
|
348
|
+
in `report.md`. stdout and stderr are kept separate: only stdout (with known
|
|
349
|
+
host noise redacted) becomes the conversational message handed to the other
|
|
350
|
+
agent, while raw stderr is logged for debugging.
|
|
351
|
+
|
|
352
|
+
### Evaluate a run: `evaluate`
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
ghostlab evaluate --run runs/<id> --capabilities runs/<id>-inspect/capabilities.json
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Combines deterministic checks over captured tool calls with a codex LLM-judge
|
|
359
|
+
that scores each `success_criterion`/`failure_signal` from the transcript.
|
|
360
|
+
Writes `verdict.json` + `verdict.md`; exits non-zero unless the verdict is
|
|
361
|
+
`pass`.
|
|
362
|
+
|
|
363
|
+
### Compare two runs: `compare`
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
ghostlab compare --base runs/<base>-summary --candidate runs/<cand>-summary \
|
|
367
|
+
--output comparison.md
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Diffs case-by-case on verdict, listing regressions first, then fixes, then
|
|
371
|
+
other changes. Exits non-zero when there are regressions, so it can gate CI.
|
|
372
|
+
|
|
373
|
+
### MCP Apps: `apps-probe` / `apps-render`
|
|
374
|
+
|
|
375
|
+
Some MCPs ship **MCP Apps UI** resources — a tool's `_meta.ui.resourceUri`
|
|
376
|
+
points to a `ui://…` HTML widget a compatible host is expected to render.
|
|
377
|
+
|
|
378
|
+
`apps-probe` connects to a target, finds every UI-producing tool, fetches each
|
|
379
|
+
`ui://` resource, and reports render-readiness and CSP diagnostics:
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
ghostlab apps-probe --target targets/cortex-local.json
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`apps-render` actually renders a `ui://` widget in headless Chrome, proving a
|
|
386
|
+
user can see and use it — it implements the MCP Apps host bridge, mounts the
|
|
387
|
+
widget in a sandboxed iframe, completes the `ui/initialize` handshake, feeds it
|
|
388
|
+
real tool input/result, and can drive a sequence of UI intents:
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
pip install 'ghostlab[apps]' && playwright install chrome # one-time
|
|
392
|
+
ghostlab apps-render --target targets/cortex-local.json \
|
|
393
|
+
--tool views_generate_sentence_scramble \
|
|
394
|
+
--arguments '{"target_sentence":"The cat sat on the mat","shuffled_elements":["mat","The","on","sat","cat","the"]}' \
|
|
395
|
+
--intent '{"type":"reorder","value":["The","cat","sat","on","the","mat"]}' \
|
|
396
|
+
--intent '{"type":"reveal"}'
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
It writes `apps-render.json` + `apps-render.md`, a `widget.png` of the initial
|
|
400
|
+
render, and `widget-final.png` after the intents run. Exit status is non-zero
|
|
401
|
+
if the render errored or any assertion failed.
|
|
402
|
+
|
|
403
|
+
### Session runner (one live agent across turns)
|
|
404
|
+
|
|
405
|
+
By default each turn spawns a fresh agent process and the orchestrator replays
|
|
406
|
+
the transcript. The session runner (`"kind": "codex-session"`) instead keeps
|
|
407
|
+
one codex session alive: turn 1 records the `thread_id`, and later turns run
|
|
408
|
+
`codex exec resume <thread_id>` so codex retains context — fewer tokens, no
|
|
409
|
+
repeated cold-start noise.
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
ghostlab run --target targets/cortex-local.json --scenario <scenario.json> \
|
|
413
|
+
--aut-runner runners/codex-cortex-local-session.json --user-runner <user.json>
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Validate your setup: `doctor`
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
ghostlab doctor # validates runners/*.json
|
|
420
|
+
ghostlab doctor --runners runners/codex-cortex-local-session.json
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Reports the codex binary + version and validates each runner's kind, command,
|
|
424
|
+
and parser.
|
|
425
|
+
|
|
426
|
+
### Default agent backend
|
|
427
|
+
|
|
428
|
+
`codex` is the default coding-agent backend for generation, the
|
|
429
|
+
agent-under-test, and judging. `inspect` needs no agent — it is a direct MCP
|
|
430
|
+
client. The codex binary is auto-detected from `$PATH`, then the macOS app
|
|
431
|
+
bundle (`/Applications/Codex.app/Contents/Resources/codex`); override with
|
|
432
|
+
`$REHEARSAL_CODEX_BIN` or `--codex-bin`.
|
|
433
|
+
|
|
434
|
+
### Colored output
|
|
435
|
+
|
|
436
|
+
CLI output is colored automatically on a TTY (dual-agent transcripts,
|
|
437
|
+
pass/fail/skip verdicts, gate failures). Set `NO_COLOR=1` (or
|
|
438
|
+
`GHOSTLAB_COLOR=0`) to disable it, `GHOSTLAB_COLOR=1` to force it on (e.g.
|
|
439
|
+
piping into a pager that groks ANSI).
|
|
440
|
+
|
|
441
|
+
## Runner Configs
|
|
442
|
+
|
|
443
|
+
Mock runner (no agent, free):
|
|
444
|
+
|
|
445
|
+
```json
|
|
446
|
+
{ "kind": "mock" }
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Process runner:
|
|
450
|
+
|
|
451
|
+
```json
|
|
452
|
+
{
|
|
453
|
+
"kind": "process",
|
|
454
|
+
"command": ["codex", "exec", "-"],
|
|
455
|
+
"env": {},
|
|
456
|
+
"timeout_seconds": 300,
|
|
457
|
+
"prompt_mode": "stdin"
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
The process runner starts one fresh process per turn. `prompt_mode` can be
|
|
462
|
+
`stdin`, `append-arg`, or `replace-placeholder`. `ghostlab create` synthesizes
|
|
463
|
+
one of these automatically for the agent-under-test host (wiring the target
|
|
464
|
+
MCP in via codex's `-c mcp_servers.<id>...` overrides, including
|
|
465
|
+
`bearer_token_env_var` for `Bearer ${VAR}`-style auth headers) — see
|
|
466
|
+
`jobs/<name>/runners/aut.json` after running it. To use Claude Code or another
|
|
467
|
+
agent as the AUT instead, hand-write a runner JSON (see
|
|
468
|
+
`runners/claude-process.example.json`) and pass it via `--aut-runner` to
|
|
469
|
+
`ghostlab create`/`ghostlab plan`, or add it directly under `hosts:` in
|
|
470
|
+
`job.yaml`.
|
|
471
|
+
|
|
472
|
+
## Install from PyPI
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
pip install ghostlab # add [ui] and/or [apps] for those extras
|
|
476
|
+
ghostlab --help
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## Packaging & Release
|
|
480
|
+
|
|
481
|
+
Build and validate distributions locally:
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
.venv/bin/python -m pytest
|
|
485
|
+
.venv/bin/python -m build
|
|
486
|
+
.venv/bin/twine check dist/*
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
CI runs tests on Python 3.10 through 3.13 and verifies that the package builds.
|
|
490
|
+
Releases are automated: the **`publish.yml`** workflow builds the sdist + wheel,
|
|
491
|
+
publishes them to PyPI via **Trusted Publishing**, and attaches them to the
|
|
492
|
+
GitHub Release — triggered when you **publish a GitHub Release** (or run the
|
|
493
|
+
workflow manually). Cut a release like:
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
# bump rehearsal/__init__.py __version__ first, then:
|
|
497
|
+
gh release create v0.1.0 --generate-notes
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
To enable publishing, create the PyPI project **`ghostlab`** and add a Trusted
|
|
501
|
+
Publisher for this repository, workflow `.github/workflows/publish.yml`,
|
|
502
|
+
environment `pypi`. No PyPI username or token is committed.
|
|
503
|
+
|
|
504
|
+
The Pages workflow builds the docs wiki with MkDocs and deploys it to GitHub
|
|
505
|
+
Pages on pushes to `main`, `v*.*.*` release tags, and manual workflow runs. In
|
|
506
|
+
the GitHub repository settings, set Pages to use GitHub Actions as the source.
|