quickstarted 0.2.0__tar.gz → 0.3.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 (91) hide show
  1. {quickstarted-0.2.0 → quickstarted-0.3.0}/.gitignore +3 -0
  2. quickstarted-0.3.0/CHANGELOG.md +145 -0
  3. {quickstarted-0.2.0 → quickstarted-0.3.0}/CONTRIBUTING.md +6 -6
  4. {quickstarted-0.2.0 → quickstarted-0.3.0}/DESIGN.md +5 -5
  5. {quickstarted-0.2.0 → quickstarted-0.3.0}/PKG-INFO +109 -69
  6. {quickstarted-0.2.0 → quickstarted-0.3.0}/README.md +108 -68
  7. {quickstarted-0.2.0 → quickstarted-0.3.0}/RELEASING.md +4 -17
  8. {quickstarted-0.2.0 → quickstarted-0.3.0}/SECURITY.md +5 -5
  9. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/explanation/proxy.md +3 -2
  10. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/explanation/scoring.md +1 -1
  11. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/getting-started/agent-mode.md +14 -44
  12. quickstarted-0.3.0/docs/getting-started/first-run.md +138 -0
  13. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/getting-started/install.md +13 -9
  14. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/guides/affordances.md +4 -4
  15. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/guides/ci.md +8 -8
  16. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/guides/cost.md +3 -3
  17. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/guides/pass-rates.md +28 -20
  18. quickstarted-0.3.0/docs/guides/replay-mode.md +83 -0
  19. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/guides/sandboxing.md +14 -9
  20. quickstarted-0.3.0/docs/guides/writing-tasks.md +237 -0
  21. quickstarted-0.3.0/docs/index.md +110 -0
  22. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/reference/cli.md +13 -13
  23. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/reference/environment.md +15 -0
  24. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/reference/results.md +46 -24
  25. quickstarted-0.2.0/docs/reference/journey-schema.md → quickstarted-0.3.0/docs/reference/task-schema.md +40 -6
  26. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/reference/trace.md +16 -3
  27. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs_hooks.py +1 -1
  28. {quickstarted-0.2.0 → quickstarted-0.3.0}/mkdocs.yml +6 -3
  29. {quickstarted-0.2.0 → quickstarted-0.3.0}/pyproject.toml +2 -2
  30. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/__init__.py +7 -7
  31. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/_version.py +1 -1
  32. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/agents/base.py +18 -9
  33. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/agents/claude.py +56 -9
  34. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/agents/gemini_agent.py +5 -5
  35. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/agents/openai_agent.py +6 -6
  36. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/agents/prompt.py +8 -8
  37. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/agents/replay.py +8 -8
  38. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/cli.py +59 -26
  39. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/docs.py +61 -6
  40. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/exec/base.py +18 -9
  41. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/exec/docker.py +150 -22
  42. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/exec/local.py +1 -1
  43. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/exec/seatbelt.py +16 -11
  44. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/net/proxy.py +20 -6
  45. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/report.py +15 -14
  46. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/results.py +11 -8
  47. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/run.py +34 -27
  48. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/suite.py +22 -22
  49. quickstarted-0.2.0/src/quickstarted/journey.py → quickstarted-0.3.0/src/quickstarted/task.py +43 -20
  50. quickstarted-0.3.0/tasks/README.md +65 -0
  51. quickstarted-0.3.0/tasks/django-quickstart.yaml +77 -0
  52. quickstarted-0.3.0/tasks/fastapi-quickstart.yaml +83 -0
  53. {quickstarted-0.2.0/journeys → quickstarted-0.3.0/tasks}/httpx-quickstart.yaml +2 -2
  54. quickstarted-0.3.0/tasks/prefect-quickstart.yaml +55 -0
  55. quickstarted-0.3.0/tasks/prisma-quickstart.yaml +77 -0
  56. {quickstarted-0.2.0/journeys → quickstarted-0.3.0/tasks}/quickstarted-quickstart.yaml +2 -2
  57. {quickstarted-0.2.0/journeys → quickstarted-0.3.0/tasks}/streamlit-quickstart.yaml +20 -7
  58. quickstarted-0.3.0/tasks/tailwind-quickstart.yaml +45 -0
  59. quickstarted-0.3.0/tasks/uv-quickstart.yaml +42 -0
  60. quickstarted-0.3.0/tasks/vite-quickstart.yaml +29 -0
  61. {quickstarted-0.2.0 → quickstarted-0.3.0}/tests/test_agents.py +37 -17
  62. {quickstarted-0.2.0 → quickstarted-0.3.0}/tests/test_claude_credentials.py +3 -3
  63. {quickstarted-0.2.0 → quickstarted-0.3.0}/tests/test_cli.py +30 -5
  64. {quickstarted-0.2.0 → quickstarted-0.3.0}/tests/test_docker_backend.py +38 -3
  65. {quickstarted-0.2.0 → quickstarted-0.3.0}/tests/test_docs.py +84 -0
  66. {quickstarted-0.2.0 → quickstarted-0.3.0}/tests/test_exec.py +29 -2
  67. {quickstarted-0.2.0 → quickstarted-0.3.0}/tests/test_run.py +20 -20
  68. {quickstarted-0.2.0 → quickstarted-0.3.0}/tests/test_sandbox.py +20 -1
  69. {quickstarted-0.2.0 → quickstarted-0.3.0}/tests/test_suite.py +43 -43
  70. quickstarted-0.2.0/tests/test_journey.py → quickstarted-0.3.0/tests/test_task.py +52 -20
  71. quickstarted-0.2.0/CHANGELOG.md +0 -72
  72. quickstarted-0.2.0/docs/getting-started/first-run.md +0 -97
  73. quickstarted-0.2.0/docs/guides/writing-journeys.md +0 -132
  74. quickstarted-0.2.0/docs/index.md +0 -73
  75. quickstarted-0.2.0/journeys/fastapi-quickstart.yaml +0 -52
  76. {quickstarted-0.2.0 → quickstarted-0.3.0}/LICENSE +0 -0
  77. {quickstarted-0.2.0 → quickstarted-0.3.0}/docs/explanation/comparison.md +0 -0
  78. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/agents/__init__.py +0 -0
  79. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/agents/registry.py +0 -0
  80. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/exec/__init__.py +0 -0
  81. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/net/__init__.py +0 -0
  82. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/net/proxy_main.py +0 -0
  83. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/pricing.py +0 -0
  84. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/sandbox.py +0 -0
  85. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/trace.py +0 -0
  86. {quickstarted-0.2.0 → quickstarted-0.3.0}/src/quickstarted/transport.py +0 -0
  87. {quickstarted-0.2.0/journeys → quickstarted-0.3.0/tasks}/bottomtime-quickstart.yaml +0 -0
  88. {quickstarted-0.2.0/journeys → quickstarted-0.3.0/tasks}/duckdb-quickstart.yaml +0 -0
  89. {quickstarted-0.2.0/journeys → quickstarted-0.3.0/tasks}/pnf-quickstart.yaml +0 -0
  90. {quickstarted-0.2.0/journeys → quickstarted-0.3.0/tasks}/polars-quickstart.yaml +0 -0
  91. {quickstarted-0.2.0 → quickstarted-0.3.0}/tests/test_proxy.py +0 -0
@@ -12,3 +12,6 @@ htmlcov/
12
12
  .DS_Store
13
13
  site/
14
14
  .cache/
15
+ bench/
16
+ bench.old/
17
+ bench*/
@@ -0,0 +1,145 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. This project follows
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [0.3.0] - 2026-07-26
7
+
8
+ Breaking, and deliberately early. The unit of testing is now a **task**, not a
9
+ journey.
10
+
11
+ ### Changed
12
+
13
+ - **`journey` is now `task`**, everywhere: the `journeys/` directory is
14
+ `tasks/`, `load_journey` is `load_task`, `run_journey` is `run_task`, the
15
+ `Journey` dataclass is `Task`, and `JourneyError` is `TaskError`.
16
+ "Journey" came from analytics funnels and describes the wrong shape. A task
17
+ is a goal plus one exit code, and "can an agent complete this task from your
18
+ docs" is the claim the tool actually tests.
19
+ - **`results.json` is schema 2.0**: the `journey` and `journeys` keys are now
20
+ `task` and `tasks`. Nothing else about the document changed, so a 1.0
21
+ consumer needs only that substitution.
22
+ - `quickstarted validate` and `quickstarted run` still accept paths under
23
+ `journeys/` and read the matching file in `tasks/`, printing a warning. That
24
+ fallback is removed in 0.4.0.
25
+
26
+ ### Added
27
+
28
+ - **Per-task container image** (`image:` in a task file). The default
29
+ `python:3.12-slim` has no Node, so a suite could not mix a Python quickstart
30
+ with a JavaScript one; `--image` is one flag for a whole invocation.
31
+ Precedence is task, then `--image`, then the default. The resolved image is
32
+ recorded in `results.json` and printed beside the backend, because a pass
33
+ rate is not comparable across base images.
34
+ - `validate` warns when a success script calls `npm`, `npx`, `node`, `pnpm`,
35
+ `yarn`, or `bun` while no image is set, since that check would fail for a
36
+ reason unrelated to the documentation.
37
+ - **Six more tasks**, doubling the set to 14 and covering both runtimes: `uv`,
38
+ `django`, `prefect` (Python) and `vite`, `prisma`, `tailwind` (Node).
39
+ - Four hosts added to the default network allowlist:
40
+ `release-assets.githubusercontent.com`, where npm packages with prebuilt
41
+ native binaries fetch during install, and the Debian and Ubuntu mirrors, so a
42
+ quickstart that opens with `apt-get install` works. `raw.githubusercontent.com`
43
+ stays out on purpose: projects serve READMEs from it, and a documentation host
44
+ the shell can reach is an attribution hole.
45
+
46
+ ### Fixed
47
+
48
+ - **Client-side redirects are followed.** DuckDB answers its own versioned doc
49
+ URLs with 938 bytes of HTML that render as 73 characters of "Redirecting...",
50
+ naming the real page in a `<meta http-equiv="refresh">`. The fetcher handed
51
+ those 73 characters to the agent as though they were documentation, so the
52
+ duckdb task was measuring recovery from an empty page. A browser follows the
53
+ refresh, so an agent with one reads the docs and an agent without reads
54
+ nothing; following it keeps the two comparable. The hop is taken only within
55
+ the same host, since a stub pointing elsewhere would read a page the task
56
+ never allowlisted, and both URLs land in the trace as
57
+ `docs_redirect_followed`. The same entrypoint now yields 21,659 characters.
58
+ - **Docker workspaces no longer leak into the VM (macOS).** The sandbox root
59
+ came from `tempfile.mkdtemp()`, which on macOS returns a path under
60
+ `/var/folders`. That is not shared with the Docker VM, so `-v` created a
61
+ directory of the same name *inside* the VM and mounted that instead. Runs
62
+ passed, but the host saw an empty workspace, `--keep-sandbox` handed back
63
+ nothing, and cleanup freed no bytes. Found by filling a 19 GB Colima disk with
64
+ 132 orphaned workspaces, after which every run failed with `no space left on
65
+ device` and was correctly classified `harness_error`. The root now defaults to
66
+ `~/.quickstarted/sandboxes` on macOS, overridable with
67
+ `QUICKSTARTED_SANDBOX_DIR`, and the mount is probed in both directions at
68
+ startup so a path the daemon cannot see is refused immediately.
69
+ - **The workspace starts empty.** `HOME` and `TMPDIR` used to point inside the
70
+ workspace, so `.npm`, `.cache` and `tmp/` were there before the agent ran a
71
+ single command, and every scaffolding tool that requires an empty directory
72
+ refused: `npm create vite@latest .`, `django-admin startproject .`,
73
+ `cargo new`. An agent had to notice and work around a mess the harness made,
74
+ which is measurement noise dressed up as a documentation problem. Under
75
+ `docker` both now live on the container's own filesystem; under `seatbelt` and
76
+ `local` they are siblings of the workspace inside the same sandbox, so the
77
+ kernel policy is unchanged.
78
+
79
+ ## [0.2.0] - 2026-07-26
80
+
81
+ The v0.1 harness could be talked out of its own guarantees: the docs allowlist
82
+ lived in the `read_docs` tool, so any agent that ran `curl` read pages the
83
+ trace never saw, and commands ran unconfined on the host. This release makes
84
+ the boundary real and makes the output publishable.
85
+
86
+ ### Added
87
+
88
+ - **Execution backends** (`quickstarted.exec`): `docker`, `seatbelt` (macOS), and
89
+ `local`. `auto` prefers an enforced backend. `quickstarted run` refuses the
90
+ unenforced one unless `--allow-unenforced` is given. Both enforced backends
91
+ are verified against live daemons, including tests asserting that a direct
92
+ connection bypassing the proxy fails.
93
+ - **Egress proxy** (`quickstarted.net`): all shell traffic leaves through a
94
+ harness-owned proxy. Documentation hosts are unreachable from the shell, so
95
+ the recorded set of pages read is complete; attempts to bypass are counted.
96
+ `network.allow` in a journey names hosts a shell may reach for installs.
97
+ - **Run classification**: `passed`, `docs_gap`, `budget_exhausted`,
98
+ `infra_error`, `harness_error`, `agent_refusal`. Only the first two are
99
+ evidence about documentation. The rest are excluded from pass rates, and
100
+ reported separately.
101
+ - **Pass rates**: `--repeat N` and `--workers N`. A single run is one sample,
102
+ and the rate over several is the number worth reading.
103
+ - **Affordance policy**: `--affordances all|none` withholds `llms.txt` and
104
+ `.md` variants so the same journey can be run both ways and the difference
105
+ measured. `--probe-affordances` records what exists. Never scored.
106
+ - **Docs cache and politeness**: `--cache-dir`, `--refresh` (flags content
107
+ that changed between runs), `--offline`, `--rate-limit`, `--ignore-robots`.
108
+ Truthful User-Agent, robots.txt honoured by default.
109
+ - **Machine-readable output**: versioned `results.json` (schema 1.0) and
110
+ `--junit` XML, where a docs gap is a failure and an infrastructure problem is
111
+ an error.
112
+ - **More agents**: `openai` and `gemini` adapters alongside `claude`, sharing
113
+ one prompt so cross-model numbers compare like with like. Neither assumes a
114
+ default model. Token counts are normalised across vendors: OpenAI and Google
115
+ report a prompt total that includes cached tokens while Anthropic excludes
116
+ them, so adapters subtract the overlap and the four counters never bill the
117
+ same token twice.
118
+ - **Retries** with exponential backoff on transient upstream faults. Every wait
119
+ lands in the trace instead of being absorbed silently by the SDK.
120
+ - **Token budgets** (`budgets.max_tokens`) and optional cost estimation from a
121
+ price book you supply (`--prices`). No prices ship with the tool.
122
+ - `quickstarted doctor`, reporting what this machine can enforce.
123
+ - Tests: proxy policy, Seatbelt confinement, classification, statistics,
124
+ caching, robots, and the affordance ablation.
125
+
126
+ ### Changed
127
+
128
+ - Cached prompt tokens are counted. Previously a run costing ~120k cache-read
129
+ tokens reported "22 in" and looked free.
130
+ - The Claude adapter defaults to `claude-opus-5` and records the exact model
131
+ the API served, since an alias can change under a benchmark.
132
+ - Agents are told what `setup` already did, so they stop rebuilding a
133
+ virtualenv that exists.
134
+ - Journeys no longer list package registries under `docs.allow`; `validate`
135
+ warns when they do, because the proxy will refuse `pip`.
136
+
137
+ ### Fixed
138
+
139
+ - `Trace` is thread-safe; the proxy appends from its own threads.
140
+
141
+ ## [0.1.0] - 2026-07-24
142
+
143
+ Initial release: journey spec, replay and Claude agent modes, sandboxed
144
+ execution, docs fetch with attribution, deterministic scoring, CLI and
145
+ GitHub Action.
@@ -13,14 +13,14 @@ python3 -m venv .venv
13
13
  Tests that touch the network are skipped unless `QUICKSTARTED_NETWORK_TESTS=1`
14
14
  is set. No test needs an API key.
15
15
 
16
- ## Writing a journey
16
+ ## Writing a task
17
17
 
18
- A good journey asserts an outcome the documentation actually promises, and
18
+ A good task asserts an outcome the documentation actually promises, and
19
19
  nothing else.
20
20
 
21
21
  - **Assert the data.** Check that `out.csv` holds the right rows. Do not check
22
22
  which function the agent called; any correct route should pass.
23
- - **Do not assert incidental paths.** An early journey checked for a `.venv`
23
+ - **Do not assert incidental paths.** An early task checked for a `.venv`
24
24
  the docs never mention; an agent that made `venv/` "failed" for no reason.
25
25
  If `setup` creates something, the agent is told, and the check may rely on
26
26
  it.
@@ -31,12 +31,12 @@ nothing else.
31
31
  through `read_docs`; a shell cannot reach them. Putting `pypi.org` there
32
32
  stops `pip` working. `quickstarted validate` warns about this.
33
33
 
34
- Run `quickstarted run <journey> --agent replay` first. If the documented
35
- commands do not pass, the journey is not ready for agent mode.
34
+ Run `quickstarted run <task> --agent replay` first. If the documented
35
+ commands do not pass, the task is not ready for agent mode.
36
36
 
37
37
  ## Adding an agent adapter
38
38
 
39
- Implement `run(journey, toolbelt, deadline) -> AgentOutcome` and act only
39
+ Implement `run(task, toolbelt, deadline) -> AgentOutcome` and act only
40
40
  through the toolbelt: never touch the filesystem or network directly, or the
41
41
  run loses the attribution and enforcement the whole tool depends on.
42
42
 
@@ -8,7 +8,7 @@ verdict. LLM judges may eventually classify WHY a run failed; they will never
8
8
  decide WHETHER it failed. This keeps the tool falsifiable and keeps vendors
9
9
  (including Anthropic) out of the scoring path.
10
10
 
11
- ## Attribution has to be enforced, not requested
11
+ ## Every page read goes through a tool the agent cannot bypass
12
12
 
13
13
  Agents act only through the Toolbelt (`bash` + `read_docs`), so every page read
14
14
  is recorded and failures attribute to a page.
@@ -26,7 +26,7 @@ pages the agent read.
26
26
  This creates one deliberate asymmetry: `docs.allow` hosts are readable only via
27
27
  `read_docs`, and `network.allow` hosts (package registries) are reachable only
28
28
  from the shell. When a host is genuinely both, PyPI being the obvious case, the
29
- journey says so explicitly and installs win. The resulting gap in attribution
29
+ task says so explicitly and installs win. The resulting gap in attribution
30
30
  is reported alongside the result.
31
31
 
32
32
  ## Backends, and saying which one ran
@@ -65,7 +65,7 @@ measurement of the affordance itself.
65
65
  That question is currently argued without data. This is the only shape of tool
66
66
  that can answer it, because answering it requires running the task.
67
67
 
68
- ## Replay mode is the floor, not a feature
68
+ ## Replay mode is a precondition
69
69
 
70
70
  Replay (documented commands verbatim, no LLM) is free, deterministic and
71
71
  CI-friendly, and it defines a floor: if replay fails, agent mode is noise. It
@@ -97,8 +97,8 @@ would quietly misreport what a sweep costs.
97
97
  out at all, so the isolation does not depend on name resolution.
98
98
  - HTML-to-text is a crude tag stripper, so markdown-native docs read better.
99
99
  That is itself a finding worth publishing.
100
- - Concurrency is per-attempt threads, not a scheduler; hosts are rate-limited
101
- individually but there is no global budget governor.
100
+ - Concurrency is per-attempt threads. Hosts are rate-limited individually, and
101
+ there is no global budget governor.
102
102
  - No pip/npm cache; every run cold-installs.
103
103
  - The Gemini adapter is written against the same Toolbelt contract but has not
104
104
  been run live; Claude and OpenAI have.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quickstarted
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Test whether an AI agent can actually complete your quickstart by following your docs
5
5
  Project-URL: Homepage, https://github.com/snehankekre/quickstarted
6
6
  Project-URL: Issues, https://github.com/snehankekre/quickstarted/issues
@@ -66,84 +66,106 @@ Full documentation: **https://snehankekre.com/quickstarted/**
66
66
 
67
67
  ## How it works
68
68
 
69
- You write a journey, in YAML:
69
+ You write a task, in YAML. This one points at Streamlit's documentation:
70
70
 
71
71
  ```yaml
72
- name: pnf-quickstart
72
+ name: streamlit-quickstart
73
73
  goal: >
74
- Install the pnf package from PyPI into a virtualenv and write a runnable
75
- snippet that imports pnf and identifies the decode function.
74
+ Install Streamlit into the virtualenv in this workspace and build a small app
75
+ in app.py that shows a title and a slider whose value is displayed on the
76
+ page. Do not start a long-running server yourself; just leave app.py ready to
77
+ run and confirm streamlit is installed.
76
78
  docs:
77
- entrypoint: https://raw.githubusercontent.com/snehankekre/pnf/main/README.md
79
+ entrypoint: https://docs.streamlit.io/get-started
78
80
  allow:
79
- - raw.githubusercontent.com
81
+ - docs.streamlit.io
82
+ - streamlit.io
80
83
  setup:
81
84
  - python3 -m venv .venv
82
85
  success:
83
86
  script: |
84
87
  set -e
85
- .venv/bin/python -c "import pnf; assert callable(pnf.decode)"
86
- .venv/bin/python demo.py
87
- replay:
88
- - .venv/bin/pip install pnf
89
- - printf 'import pnf\nprint(pnf.decode.__name__)\n' > demo.py
90
- - .venv/bin/python demo.py
88
+ test -f app.py
89
+ .venv/bin/python -c "import streamlit"
91
90
  ```
92
91
 
93
- The harness runs an agent in a throwaway workspace with two tools: a sandboxed
94
- `bash`, and `read_docs`, which is the only way it can read documentation.
95
-
96
- The harness enforces that. All shell traffic leaves through a proxy it owns,
97
- and documentation hosts are unreachable from the shell, so an agent that tries
98
- `curl https://docs.example.com/...` is refused and the attempt is recorded.
99
- The pages listed in a report are therefore the pages the agent really read.
100
- When a run fails, the last page it read is a fact you can act on.
101
-
102
- When the agent stops, the harness runs your `success.script` in the workspace.
103
- Exit code 0 is a pass. Nothing else is. The agent's own claim of success is
104
- recorded and never trusted; there is no LLM judge anywhere in scoring.
105
-
106
- ## Two modes
107
-
108
- **Replay mode** (free, no LLM, no API key) runs your `replay` commands, the
109
- literal commands your docs tell users to type, and stops at the first failure.
110
- Treat it as a precondition. If the documented commands break, no reader stands
111
- a chance, and agent mode only adds noise.
92
+ Then you run an agent against it:
112
93
 
113
94
  ```
114
- quickstarted run journeys/pnf-quickstart.yaml --agent replay
95
+ pip install "quickstarted[claude]"
96
+ export QUICKSTARTED_ANTHROPIC_API_KEY=...
97
+ quickstarted run tasks/streamlit-quickstart.yaml --agent claude
115
98
  ```
116
99
 
117
- **Agent mode** has an LLM follow your docs to the goal. This catches what
118
- replay cannot: ambiguity, missing steps, wrong ordering, docs that assume
119
- context a newcomer does not have.
120
-
121
100
  ```
122
- pip install "quickstarted[claude]"
123
- export QUICKSTARTED_ANTHROPIC_API_KEY=...
124
- quickstarted run journeys/pnf-quickstart.yaml --agent claude
101
+ [PASS] streamlit-quickstart (claude:claude-opus-5)
102
+ classification: passed
103
+ turns: 6, duration: 63.5s
104
+ backend: docker
105
+ tokens: 12 in / 1150 out, cache 11204 written / 33181 read
106
+ docs pages read: 4
125
107
  ```
126
108
 
109
+ The agent works in a throwaway workspace with two tools: a sandboxed `bash`,
110
+ and `read_docs`, which is the only way it can read documentation. It gets no
111
+ browser, no search engine, and no network of its own. The system prompt also
112
+ forbids leaning on prior knowledge of the project under test, because a model
113
+ that already knows Streamlit would sail through docs that teach a newcomer
114
+ nothing.
115
+
116
+ The harness enforces the boundary rather than requesting it. All shell traffic
117
+ leaves through a proxy the harness owns, and documentation hosts are
118
+ unreachable from the shell, so an agent that tries
119
+ `curl https://docs.streamlit.io/...` is refused and the attempt is recorded.
120
+ The pages in a report are therefore the pages the agent really read, and when a
121
+ run fails, the last page it read is a fact rather than a guess.
122
+
127
123
  Keys are read from `QUICKSTARTED_*` names first so they can sit in your shell
128
124
  without other tooling on the same machine picking them up and spending them.
129
125
  The vendor-standard names still work as a fallback, which is what CI sets.
130
126
 
127
+ ## The success script is the whole verdict
128
+
129
+ When the agent stops, the harness runs your `success.script` in the same
130
+ workspace. Exit code 0 is a pass. Nothing else is. The agent's own claim of
131
+ success is recorded and never trusted, and there is no LLM judge anywhere in
132
+ scoring, because a model asked to grade its own work will tell you the app is
133
+ ready when `app.py` does not parse.
134
+
135
+ Most checks are the size of the one above. You are asserting whatever your
136
+ tutorial already promises the reader: the file exists, the import works, the
137
+ command runs, the output contains the number on the page. If your quickstart
138
+ ends with "you should see `200`", the check is `grep -q 200`.
139
+
140
+ You can go stricter when it is worth it.
141
+ [tasks/streamlit-quickstart.yaml](tasks/streamlit-quickstart.yaml) boots
142
+ the app headless and polls Streamlit's own health endpoint, which is about
143
+ fifteen more lines and proves the app actually serves. That is optional. Start
144
+ with the two-line version.
145
+
146
+ ## Replay mode, the free precondition
147
+
148
+ `--agent replay` runs your `replay` commands, the literal commands your docs
149
+ tell a reader to type, and stops at the first failure. No model, no API key, no
150
+ cost.
151
+
131
152
  ```
132
- [PASS] pnf-quickstart (claude:claude-opus-5)
133
- classification: passed
134
- turns: 12, duration: 70.7s
135
- backend: seatbelt
136
- tokens: 24 in / 4616 out, cache 16812 written / 104148 read
137
- docs pages read: 2
153
+ quickstarted run tasks/streamlit-quickstart.yaml --agent replay
138
154
  ```
139
155
 
156
+ Treat it as a floor to check on every push. If the documented commands are
157
+ broken, no reader stands a chance and an agent run only adds noise on top of a
158
+ failure you already know about. It cannot tell you whether a reader could have
159
+ *found* those commands, understood their order, or guessed the prerequisite you
160
+ left out. That is what agent mode is for.
161
+
140
162
  ## Pass rates
141
163
 
142
164
  The same docs and the same model can pass at 10:00 and fail at 10:05. One run
143
165
  is a sample. Use `--repeat` and read the rate:
144
166
 
145
167
  ```
146
- quickstarted run journeys/*.yaml --agent claude --repeat 5 --workers 3
168
+ quickstarted run tasks/*.yaml --agent claude --repeat 5 --workers 3
147
169
  ```
148
170
 
149
171
  Every run is classified, and only two classifications say anything about your
@@ -155,7 +177,7 @@ documentation:
155
177
  | `docs_gap` | the agent finished, the check failed | yes |
156
178
  | `budget_exhausted` | out of turns, time, or tokens | no |
157
179
  | `infra_error` | rate limit, upstream 5xx, network failure | no |
158
- | `harness_error` | misconfigured journey, our bug | no |
180
+ | `harness_error` | misconfigured task, our bug | no |
159
181
  | `agent_refusal` | model declined | no |
160
182
 
161
183
  Runs that produced no evidence are excluded from the numerator *and* the
@@ -169,11 +191,11 @@ scoring it would be the proxy metric this tool exists to replace. So
169
191
  quickstarted never scores affordances. It measures them:
170
192
 
171
193
  ```
172
- quickstarted run journeys/streamlit-quickstart.yaml --agent claude --repeat 10
173
- quickstarted run journeys/streamlit-quickstart.yaml --agent claude --repeat 10 --affordances none
194
+ quickstarted run tasks/streamlit-quickstart.yaml --agent claude --repeat 10
195
+ quickstarted run tasks/streamlit-quickstart.yaml --agent claude --repeat 10 --affordances none
174
196
  ```
175
197
 
176
- Same prompt, same journey, same model. The only difference is whether the
198
+ Same prompt, same task, same model. The only difference is whether the
177
199
  agent could read `llms.txt` and `.md` variants. The difference in pass rate is
178
200
  a measurement of the affordance itself.
179
201
 
@@ -199,9 +221,9 @@ can enforce. Details in [SECURITY.md](SECURITY.md).
199
221
  ## Install
200
222
 
201
223
  ```
202
- pip install quickstarted # replay mode only
203
- pip install "quickstarted[claude]" # + Claude
224
+ pip install "quickstarted[claude]" # agent mode with Claude
204
225
  pip install "quickstarted[all-agents]" # + OpenAI and Gemini
226
+ pip install quickstarted # replay mode only, no SDK
205
227
  ```
206
228
 
207
229
  Python 3.9+. One runtime dependency: PyYAML. Every adapter is a plain tool-use
@@ -210,25 +232,43 @@ cross-model numbers compare like with like.
210
232
 
211
233
  ## CI
212
234
 
235
+ Two jobs, on two schedules. Replay on every push, because it is free:
236
+
213
237
  ```yaml
214
- - uses: actions/checkout@v4
215
- - uses: actions/setup-python@v5
216
- with:
217
- python-version: "3.12"
218
238
  - run: pip install quickstarted
219
- - run: quickstarted run journeys/*.yaml --agent replay --out results --junit junit.xml
220
- - uses: actions/upload-artifact@v4
221
- if: always()
222
- with:
223
- name: quickstarted-results
224
- path: results/
239
+ - run: quickstarted run tasks/*.yaml --agent replay --junit junit.xml
240
+ ```
241
+
242
+ Agent mode on a schedule, or when a model ships, because it costs real tokens:
243
+
244
+ ```yaml
245
+ on:
246
+ schedule:
247
+ - cron: "0 6 * * 1"
248
+ workflow_dispatch:
249
+ jobs:
250
+ agent:
251
+ steps:
252
+ - uses: actions/checkout@v4
253
+ - uses: actions/setup-python@v5
254
+ with:
255
+ python-version: "3.12"
256
+ - run: pip install "quickstarted[claude]"
257
+ - run: quickstarted run tasks/*.yaml --agent claude --repeat 3
258
+ --out results --junit junit.xml
259
+ env:
260
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
261
+ - uses: actions/upload-artifact@v4
262
+ if: always()
263
+ with:
264
+ name: quickstarted-results
265
+ path: results/
225
266
  ```
226
267
 
227
- `quickstarted run` exits 1 when any journey fails, so the job gates merges.
228
- `results.json` is versioned (schema 1.0). JUnit XML reports a docs gap as a
229
- failure and infrastructure trouble as an error, so a rate limit does not read
230
- as a broken quickstart. Run agent mode on a schedule, or when a model ships.
231
- Every push is too often, because agent runs cost real tokens.
268
+ `quickstarted run` exits 1 when any task fails, so either job can gate
269
+ merges. `results.json` is versioned (schema 1.0). JUnit XML reports a docs gap
270
+ as a failure and infrastructure trouble as an error, so a rate limit does not
271
+ read as a broken quickstart.
232
272
 
233
273
  ## Fetching other people's docs
234
274
 
@@ -243,7 +283,7 @@ last run, which is a finding in its own right.
243
283
  It tells you whether a given model, on a given day, could get from your docs to
244
284
  a working result, and where it was reading when it could not. That is a lower
245
285
  bound on documentation quality. Nothing here measures whether your docs are
246
- pleasant, complete, or accurate beyond the journey you wrote, and there is no
286
+ pleasant, complete, or accurate beyond the task you wrote, and there is no
247
287
  UI testing at all: if your product's button moved, this will not notice.
248
288
  [Doc Detective](https://github.com/doc-detective/doc-detective) is the tool for
249
289
  that, and the two answer different questions.