quickstarted 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.
Files changed (78) hide show
  1. quickstarted-0.2.0/.gitignore +14 -0
  2. quickstarted-0.2.0/CHANGELOG.md +72 -0
  3. quickstarted-0.2.0/CONTRIBUTING.md +56 -0
  4. quickstarted-0.2.0/DESIGN.md +106 -0
  5. quickstarted-0.2.0/LICENSE +21 -0
  6. quickstarted-0.2.0/PKG-INFO +256 -0
  7. quickstarted-0.2.0/README.md +211 -0
  8. quickstarted-0.2.0/RELEASING.md +67 -0
  9. quickstarted-0.2.0/SECURITY.md +48 -0
  10. quickstarted-0.2.0/docs/explanation/comparison.md +84 -0
  11. quickstarted-0.2.0/docs/explanation/proxy.md +76 -0
  12. quickstarted-0.2.0/docs/explanation/scoring.md +67 -0
  13. quickstarted-0.2.0/docs/getting-started/agent-mode.md +105 -0
  14. quickstarted-0.2.0/docs/getting-started/first-run.md +97 -0
  15. quickstarted-0.2.0/docs/getting-started/install.md +64 -0
  16. quickstarted-0.2.0/docs/guides/affordances.md +79 -0
  17. quickstarted-0.2.0/docs/guides/ci.md +125 -0
  18. quickstarted-0.2.0/docs/guides/cost.md +92 -0
  19. quickstarted-0.2.0/docs/guides/pass-rates.md +86 -0
  20. quickstarted-0.2.0/docs/guides/sandboxing.md +99 -0
  21. quickstarted-0.2.0/docs/guides/writing-journeys.md +132 -0
  22. quickstarted-0.2.0/docs/index.md +73 -0
  23. quickstarted-0.2.0/docs/reference/cli.md +102 -0
  24. quickstarted-0.2.0/docs/reference/environment.md +66 -0
  25. quickstarted-0.2.0/docs/reference/journey-schema.md +112 -0
  26. quickstarted-0.2.0/docs/reference/results.md +112 -0
  27. quickstarted-0.2.0/docs/reference/trace.md +86 -0
  28. quickstarted-0.2.0/docs_hooks.py +81 -0
  29. quickstarted-0.2.0/journeys/bottomtime-quickstart.yaml +22 -0
  30. quickstarted-0.2.0/journeys/duckdb-quickstart.yaml +45 -0
  31. quickstarted-0.2.0/journeys/fastapi-quickstart.yaml +52 -0
  32. quickstarted-0.2.0/journeys/httpx-quickstart.yaml +31 -0
  33. quickstarted-0.2.0/journeys/pnf-quickstart.yaml +26 -0
  34. quickstarted-0.2.0/journeys/polars-quickstart.yaml +40 -0
  35. quickstarted-0.2.0/journeys/quickstarted-quickstart.yaml +44 -0
  36. quickstarted-0.2.0/journeys/streamlit-quickstart.yaml +53 -0
  37. quickstarted-0.2.0/mkdocs.yml +82 -0
  38. quickstarted-0.2.0/pyproject.toml +121 -0
  39. quickstarted-0.2.0/src/quickstarted/__init__.py +24 -0
  40. quickstarted-0.2.0/src/quickstarted/_version.py +7 -0
  41. quickstarted-0.2.0/src/quickstarted/agents/__init__.py +4 -0
  42. quickstarted-0.2.0/src/quickstarted/agents/base.py +145 -0
  43. quickstarted-0.2.0/src/quickstarted/agents/claude.py +242 -0
  44. quickstarted-0.2.0/src/quickstarted/agents/gemini_agent.py +138 -0
  45. quickstarted-0.2.0/src/quickstarted/agents/openai_agent.py +167 -0
  46. quickstarted-0.2.0/src/quickstarted/agents/prompt.py +61 -0
  47. quickstarted-0.2.0/src/quickstarted/agents/registry.py +31 -0
  48. quickstarted-0.2.0/src/quickstarted/agents/replay.py +39 -0
  49. quickstarted-0.2.0/src/quickstarted/cli.py +250 -0
  50. quickstarted-0.2.0/src/quickstarted/docs.py +263 -0
  51. quickstarted-0.2.0/src/quickstarted/exec/__init__.py +89 -0
  52. quickstarted-0.2.0/src/quickstarted/exec/base.py +131 -0
  53. quickstarted-0.2.0/src/quickstarted/exec/docker.py +216 -0
  54. quickstarted-0.2.0/src/quickstarted/exec/local.py +16 -0
  55. quickstarted-0.2.0/src/quickstarted/exec/seatbelt.py +83 -0
  56. quickstarted-0.2.0/src/quickstarted/journey.py +182 -0
  57. quickstarted-0.2.0/src/quickstarted/net/__init__.py +5 -0
  58. quickstarted-0.2.0/src/quickstarted/net/proxy.py +274 -0
  59. quickstarted-0.2.0/src/quickstarted/net/proxy_main.py +53 -0
  60. quickstarted-0.2.0/src/quickstarted/pricing.py +93 -0
  61. quickstarted-0.2.0/src/quickstarted/report.py +206 -0
  62. quickstarted-0.2.0/src/quickstarted/results.py +166 -0
  63. quickstarted-0.2.0/src/quickstarted/run.py +240 -0
  64. quickstarted-0.2.0/src/quickstarted/sandbox.py +15 -0
  65. quickstarted-0.2.0/src/quickstarted/suite.py +203 -0
  66. quickstarted-0.2.0/src/quickstarted/trace.py +53 -0
  67. quickstarted-0.2.0/src/quickstarted/transport.py +88 -0
  68. quickstarted-0.2.0/tests/test_agents.py +133 -0
  69. quickstarted-0.2.0/tests/test_claude_credentials.py +95 -0
  70. quickstarted-0.2.0/tests/test_cli.py +85 -0
  71. quickstarted-0.2.0/tests/test_docker_backend.py +211 -0
  72. quickstarted-0.2.0/tests/test_docs.py +145 -0
  73. quickstarted-0.2.0/tests/test_exec.py +127 -0
  74. quickstarted-0.2.0/tests/test_journey.py +123 -0
  75. quickstarted-0.2.0/tests/test_proxy.py +117 -0
  76. quickstarted-0.2.0/tests/test_run.py +155 -0
  77. quickstarted-0.2.0/tests/test_sandbox.py +65 -0
  78. quickstarted-0.2.0/tests/test_suite.py +256 -0
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ quickstarted-results/
9
+ .coverage
10
+ coverage.xml
11
+ htmlcov/
12
+ .DS_Store
13
+ site/
14
+ .cache/
@@ -0,0 +1,72 @@
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.2.0] - 2026-07-26
7
+
8
+ The v0.1 harness could be talked out of its own guarantees: the docs allowlist
9
+ lived in the `read_docs` tool, so any agent that ran `curl` read pages the
10
+ trace never saw, and commands ran unconfined on the host. This release makes
11
+ the boundary real and makes the output publishable.
12
+
13
+ ### Added
14
+
15
+ - **Execution backends** (`quickstarted.exec`): `docker`, `seatbelt` (macOS), and
16
+ `local`. `auto` prefers an enforced backend. `quickstarted run` refuses the
17
+ unenforced one unless `--allow-unenforced` is given. Both enforced backends
18
+ are verified against live daemons, including tests asserting that a direct
19
+ connection bypassing the proxy fails.
20
+ - **Egress proxy** (`quickstarted.net`): all shell traffic leaves through a
21
+ harness-owned proxy. Documentation hosts are unreachable from the shell, so
22
+ the recorded set of pages read is complete; attempts to bypass are counted.
23
+ `network.allow` in a journey names hosts a shell may reach for installs.
24
+ - **Run classification**: `passed`, `docs_gap`, `budget_exhausted`,
25
+ `infra_error`, `harness_error`, `agent_refusal`. Only the first two are
26
+ evidence about documentation. The rest are excluded from pass rates, and
27
+ reported separately.
28
+ - **Pass rates**: `--repeat N` and `--workers N`. A single run is one sample,
29
+ and the rate over several is the number worth reading.
30
+ - **Affordance policy**: `--affordances all|none` withholds `llms.txt` and
31
+ `.md` variants so the same journey can be run both ways and the difference
32
+ measured. `--probe-affordances` records what exists. Never scored.
33
+ - **Docs cache and politeness**: `--cache-dir`, `--refresh` (flags content
34
+ that changed between runs), `--offline`, `--rate-limit`, `--ignore-robots`.
35
+ Truthful User-Agent, robots.txt honoured by default.
36
+ - **Machine-readable output**: versioned `results.json` (schema 1.0) and
37
+ `--junit` XML, where a docs gap is a failure and an infrastructure problem is
38
+ an error.
39
+ - **More agents**: `openai` and `gemini` adapters alongside `claude`, sharing
40
+ one prompt so cross-model numbers compare like with like. Neither assumes a
41
+ default model. Token counts are normalised across vendors: OpenAI and Google
42
+ report a prompt total that includes cached tokens while Anthropic excludes
43
+ them, so adapters subtract the overlap and the four counters never bill the
44
+ same token twice.
45
+ - **Retries** with exponential backoff on transient upstream faults. Every wait
46
+ lands in the trace instead of being absorbed silently by the SDK.
47
+ - **Token budgets** (`budgets.max_tokens`) and optional cost estimation from a
48
+ price book you supply (`--prices`). No prices ship with the tool.
49
+ - `quickstarted doctor`, reporting what this machine can enforce.
50
+ - Tests: proxy policy, Seatbelt confinement, classification, statistics,
51
+ caching, robots, and the affordance ablation.
52
+
53
+ ### Changed
54
+
55
+ - Cached prompt tokens are counted. Previously a run costing ~120k cache-read
56
+ tokens reported "22 in" and looked free.
57
+ - The Claude adapter defaults to `claude-opus-5` and records the exact model
58
+ the API served, since an alias can change under a benchmark.
59
+ - Agents are told what `setup` already did, so they stop rebuilding a
60
+ virtualenv that exists.
61
+ - Journeys no longer list package registries under `docs.allow`; `validate`
62
+ warns when they do, because the proxy will refuse `pip`.
63
+
64
+ ### Fixed
65
+
66
+ - `Trace` is thread-safe; the proxy appends from its own threads.
67
+
68
+ ## [0.1.0] - 2026-07-24
69
+
70
+ Initial release: journey spec, replay and Claude agent modes, sandboxed
71
+ execution, docs fetch with attribution, deterministic scoring, CLI and
72
+ GitHub Action.
@@ -0,0 +1,56 @@
1
+ # Contributing
2
+
3
+ ## Setup
4
+
5
+ ```
6
+ python3 -m venv .venv
7
+ .venv/bin/pip install -e ".[dev]"
8
+ .venv/bin/pytest
9
+ .venv/bin/ruff check .
10
+ .venv/bin/mypy
11
+ ```
12
+
13
+ Tests that touch the network are skipped unless `QUICKSTARTED_NETWORK_TESTS=1`
14
+ is set. No test needs an API key.
15
+
16
+ ## Writing a journey
17
+
18
+ A good journey asserts an outcome the documentation actually promises, and
19
+ nothing else.
20
+
21
+ - **Assert the data.** Check that `out.csv` holds the right rows. Do not check
22
+ which function the agent called; any correct route should pass.
23
+ - **Do not assert incidental paths.** An early journey checked for a `.venv`
24
+ the docs never mention; an agent that made `venv/` "failed" for no reason.
25
+ If `setup` creates something, the agent is told, and the check may rely on
26
+ it.
27
+ - **Let the harness verify.** If the goal is a running server, the success
28
+ script starts it, asks it a question, and stops it. Do not trust the agent's
29
+ report, and do not ask the agent to leave something running.
30
+ - **Separate docs hosts from registries.** `docs.allow` hosts are readable only
31
+ through `read_docs`; a shell cannot reach them. Putting `pypi.org` there
32
+ stops `pip` working. `quickstarted validate` warns about this.
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.
36
+
37
+ ## Adding an agent adapter
38
+
39
+ Implement `run(journey, toolbelt, deadline) -> AgentOutcome` and act only
40
+ through the toolbelt: never touch the filesystem or network directly, or the
41
+ run loses the attribution and enforcement the whole tool depends on.
42
+
43
+ Use `agents/prompt.py` unchanged. Adapters that phrase the task their own way
44
+ turn a cross-model comparison into a comparison of prompts.
45
+
46
+ Do not assume a default model for a new vendor. Model names change faster than
47
+ a pinned default survives, and a benchmark that silently picks one produces
48
+ numbers nobody can reproduce.
49
+
50
+ ## Things that will get a change rejected
51
+
52
+ - An LLM anywhere in the scoring path.
53
+ - Scoring the presence of `llms.txt`, MCP, or any other affordance. Measure it
54
+ with an ablation instead.
55
+ - Counting infrastructure failures as documentation failures.
56
+ - Making the unenforced backend the silent default.
@@ -0,0 +1,106 @@
1
+ # Design notes
2
+
3
+ ## The one decision everything follows from
4
+
5
+ Deterministic assertions are the only pass/fail truth. The success script runs
6
+ after the agent stops, in the same workspace, and its exit code is the
7
+ verdict. LLM judges may eventually classify WHY a run failed; they will never
8
+ decide WHETHER it failed. This keeps the tool falsifiable and keeps vendors
9
+ (including Anthropic) out of the scoring path.
10
+
11
+ ## Attribution has to be enforced, not requested
12
+
13
+ Agents act only through the Toolbelt (`bash` + `read_docs`), so every page read
14
+ is recorded and failures attribute to a page.
15
+
16
+ v0.1 stopped there, and that was not enough. The allowlist lived in the
17
+ `read_docs` tool while `bash` had unrestricted network access, so any agent
18
+ that ran `curl` read documentation the trace never saw. The guarantee held only
19
+ while the agent cooperated, which is not a guarantee.
20
+
21
+ Now all shell traffic leaves through a proxy the harness owns, and
22
+ documentation hosts are unreachable from the shell. An attempted `curl` to a
23
+ docs host is refused and counted. The set of pages in the report is the set of
24
+ pages the agent read.
25
+
26
+ This creates one deliberate asymmetry: `docs.allow` hosts are readable only via
27
+ `read_docs`, and `network.allow` hosts (package registries) are reachable only
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
30
+ is reported alongside the result.
31
+
32
+ ## Backends, and saying which one ran
33
+
34
+ Three: `docker` (container on an internal network whose only route out is the
35
+ proxy sidecar), `seatbelt` (macOS, kernel-enforced, all egress denied except
36
+ the proxy port, home directory unreadable), and `local` (nothing enforced).
37
+
38
+ `local` is refused unless explicitly allowed, every result records which
39
+ backend ran and whether it was enforced, and `quickstarted doctor` says what a
40
+ machine can do before anyone trusts its numbers. An unenforced result still
41
+ tells you something. It just supports a weaker claim, and the claim should
42
+ travel with the number.
43
+
44
+ ## A run is a sample; a rate is a measurement
45
+
46
+ The same docs and model can pass and then fail. `--repeat` produces a pass
47
+ rate, and every run is classified. Only `passed` and `docs_gap` are statements
48
+ about documentation. Rate limits, exhausted budgets, refusals, and our own bugs
49
+ are excluded from both the numerator and the denominator and reported
50
+ separately.
51
+
52
+ A sweep of fifty projects will hit 429s and flaky networks. Reporting those as
53
+ failed quickstarts would be a lie that is very easy to tell, and it is the
54
+ difference between a benchmark and a leaderboard-shaped rumour.
55
+
56
+ ## Affordances are measured, never scored
57
+
58
+ Whether a project ships `llms.txt` is a checklist item anyone can `curl`, and
59
+ scoring it would be the proxy metric this tool exists to replace. So presence
60
+ is recorded as context, with sizes, and the affordance can be *withheld*:
61
+ `--affordances none` blocks `llms.txt` and `.md` variants while keeping the
62
+ prompt byte-identical. Run both conditions and the difference in pass rate is a
63
+ measurement of the affordance itself.
64
+
65
+ That question is currently argued without data. This is the only shape of tool
66
+ that can answer it, because answering it requires running the task.
67
+
68
+ ## Replay mode is the floor, not a feature
69
+
70
+ Replay (documented commands verbatim, no LLM) is free, deterministic and
71
+ CI-friendly, and it defines a floor: if replay fails, agent mode is noise. It
72
+ is a weaker `runShell` than a docs-testing framework like Doc Detective
73
+ already offers. Sell it as a precondition, and let agent mode carry the
74
+ product.
75
+
76
+ ## Trace as the product surface
77
+
78
+ Everything interesting is an event in the JSONL trace: setup, tool calls, docs
79
+ fetches with content hashes, egress decisions, agent turns with token usage,
80
+ retries, the success check. `results.json` (schema 1.0) is the stable contract
81
+ over those traces. The hosted product is, to a first approximation, storage
82
+ plus diffing plus alerting across models and time.
83
+
84
+ ## Cost is reported in tokens
85
+
86
+ Cached prompt tokens are excluded from `input_tokens` by the API, so summing
87
+ that field alone made a run costing 120k cache-read tokens look free. All four
88
+ counters are tracked. Dollars appear only when the operator supplies a price
89
+ book: vendor rates change, and a stale table baked into a benchmarking tool
90
+ would quietly misreport what a sweep costs.
91
+
92
+ ## Known gaps
93
+
94
+ - Both enforced backends are verified end to end against live daemons. In the
95
+ container the isolation is stronger than Seatbelt's: a direct dial to an IP
96
+ address fails with `Network is unreachable`. The sandbox network has no route
97
+ out at all, so the isolation does not depend on name resolution.
98
+ - HTML-to-text is a crude tag stripper, so markdown-native docs read better.
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.
102
+ - No pip/npm cache; every run cold-installs.
103
+ - The Gemini adapter is written against the same Toolbelt contract but has not
104
+ been run live; Claude and OpenAI have.
105
+ - Nothing distinguishes "the docs are wrong" from "the product is broken". Both
106
+ surface as `docs_gap` and need a human.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Snehan Kekre
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,256 @@
1
+ Metadata-Version: 2.4
2
+ Name: quickstarted
3
+ Version: 0.2.0
4
+ Summary: Test whether an AI agent can actually complete your quickstart by following your docs
5
+ Project-URL: Homepage, https://github.com/snehankekre/quickstarted
6
+ Project-URL: Issues, https://github.com/snehankekre/quickstarted/issues
7
+ Project-URL: Changelog, https://github.com/snehankekre/quickstarted/blob/main/CHANGELOG.md
8
+ Author-email: snehan <snehan.minerva@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent-experience,agents,ci,devrel,docs,documentation,testing
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Documentation
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Requires-Python: >=3.9
23
+ Requires-Dist: pyyaml>=6.0
24
+ Provides-Extra: all-agents
25
+ Requires-Dist: anthropic>=0.40; extra == 'all-agents'
26
+ Requires-Dist: google-genai>=1.0; extra == 'all-agents'
27
+ Requires-Dist: openai>=1.40; extra == 'all-agents'
28
+ Provides-Extra: claude
29
+ Requires-Dist: anthropic>=0.40; extra == 'claude'
30
+ Provides-Extra: dev
31
+ Requires-Dist: anthropic>=0.40; extra == 'dev'
32
+ Requires-Dist: google-genai>=1.0; extra == 'dev'
33
+ Requires-Dist: mypy>=1.11; extra == 'dev'
34
+ Requires-Dist: openai>=1.40; extra == 'dev'
35
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
36
+ Requires-Dist: pytest>=8.0; extra == 'dev'
37
+ Requires-Dist: ruff>=0.6; extra == 'dev'
38
+ Provides-Extra: docs
39
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
40
+ Provides-Extra: gemini
41
+ Requires-Dist: google-genai>=1.0; extra == 'gemini'
42
+ Provides-Extra: openai
43
+ Requires-Dist: openai>=1.40; extra == 'openai'
44
+ Description-Content-Type: text/markdown
45
+
46
+ # quickstarted
47
+
48
+ Test whether an AI agent can actually complete your quickstart by following
49
+ your docs.
50
+
51
+ [![CI](https://github.com/snehankekre/quickstarted/actions/workflows/ci.yml/badge.svg)](https://github.com/snehankekre/quickstarted/actions/workflows/ci.yml)
52
+ [![PyPI](https://img.shields.io/pypi/v/quickstarted.svg)](https://pypi.org/project/quickstarted/)
53
+ [![Python](https://img.shields.io/pypi/pyversions/quickstarted.svg)](https://pypi.org/project/quickstarted/)
54
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
55
+
56
+ Roughly half the traffic to documentation sites is now AI agents and
57
+ AI-assisted workflows ([Mintlify's measurement](https://www.mintlify.com/blog/ai-traffic)).
58
+ When an agent reads your quickstart and fails, you lose the developer behind
59
+ it and you never find out. quickstarted turns that failure into a CI signal: a
60
+ sandboxed agent gets your goal, your docs, and nothing else, and a script you
61
+ wrote decides whether it succeeded.
62
+
63
+ Full documentation: **https://snehankekre.com/quickstarted/**
64
+
65
+ **Status: early. The interface will change.**
66
+
67
+ ## How it works
68
+
69
+ You write a journey, in YAML:
70
+
71
+ ```yaml
72
+ name: pnf-quickstart
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.
76
+ docs:
77
+ entrypoint: https://raw.githubusercontent.com/snehankekre/pnf/main/README.md
78
+ allow:
79
+ - raw.githubusercontent.com
80
+ setup:
81
+ - python3 -m venv .venv
82
+ success:
83
+ script: |
84
+ 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
91
+ ```
92
+
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.
112
+
113
+ ```
114
+ quickstarted run journeys/pnf-quickstart.yaml --agent replay
115
+ ```
116
+
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
+ ```
122
+ pip install "quickstarted[claude]"
123
+ export QUICKSTARTED_ANTHROPIC_API_KEY=...
124
+ quickstarted run journeys/pnf-quickstart.yaml --agent claude
125
+ ```
126
+
127
+ Keys are read from `QUICKSTARTED_*` names first so they can sit in your shell
128
+ without other tooling on the same machine picking them up and spending them.
129
+ The vendor-standard names still work as a fallback, which is what CI sets.
130
+
131
+ ```
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
138
+ ```
139
+
140
+ ## Pass rates
141
+
142
+ The same docs and the same model can pass at 10:00 and fail at 10:05. One run
143
+ is a sample. Use `--repeat` and read the rate:
144
+
145
+ ```
146
+ quickstarted run journeys/*.yaml --agent claude --repeat 5 --workers 3
147
+ ```
148
+
149
+ Every run is classified, and only two classifications say anything about your
150
+ documentation:
151
+
152
+ | Classification | Meaning | Counts toward pass rate |
153
+ | --- | --- | --- |
154
+ | `passed` | success script exited 0 | yes |
155
+ | `docs_gap` | the agent finished, the check failed | yes |
156
+ | `budget_exhausted` | out of turns, time, or tokens | no |
157
+ | `infra_error` | rate limit, upstream 5xx, network failure | no |
158
+ | `harness_error` | misconfigured journey, our bug | no |
159
+ | `agent_refusal` | model declined | no |
160
+
161
+ Runs that produced no evidence are excluded from the numerator *and* the
162
+ denominator, and reported separately. When nothing produced evidence the suite
163
+ says so. Reporting 0% there would be a different claim, and a false one.
164
+
165
+ ## Does llms.txt actually help?
166
+
167
+ Whether a project ships `llms.txt` is a checklist item anyone can `curl`, and
168
+ scoring it would be the proxy metric this tool exists to replace. So
169
+ quickstarted never scores affordances. It measures them:
170
+
171
+ ```
172
+ quickstarted run journeys/streamlit-quickstart.yaml --agent claude --repeat 10
173
+ quickstarted run journeys/streamlit-quickstart.yaml --agent claude --repeat 10 --affordances none
174
+ ```
175
+
176
+ Same prompt, same journey, same model. The only difference is whether the
177
+ agent could read `llms.txt` and `.md` variants. The difference in pass rate is
178
+ a measurement of the affordance itself.
179
+
180
+ `--probe-affordances` records what exists without changing anything, including
181
+ the size. Presence and usefulness come apart quickly: Streamlit's `llms.txt` is
182
+ 67 KB, while its `llms-full.txt` is 1.8 MB, which is more than most agents can
183
+ read in one go.
184
+
185
+ ## Sandboxing
186
+
187
+ Agent-authored commands from somebody else's quickstart are untrusted code.
188
+
189
+ | Backend | Filesystem | Network |
190
+ | --- | --- | --- |
191
+ | `docker` | container | internal network; the proxy sidecar is the only route out |
192
+ | `seatbelt` (macOS) | home directory unreadable, writes confined to the workspace | all egress denied except the harness proxy |
193
+ | `local` | none | none; proxy variables are advisory |
194
+
195
+ `auto` picks the best available. `quickstarted run` refuses `local` unless you
196
+ pass `--allow-unenforced`. Run `quickstarted doctor` to see what your machine
197
+ can enforce. Details in [SECURITY.md](SECURITY.md).
198
+
199
+ ## Install
200
+
201
+ ```
202
+ pip install quickstarted # replay mode only
203
+ pip install "quickstarted[claude]" # + Claude
204
+ pip install "quickstarted[all-agents]" # + OpenAI and Gemini
205
+ ```
206
+
207
+ Python 3.9+. One runtime dependency: PyYAML. Every adapter is a plain tool-use
208
+ loop against the same two harness-owned tools and the same shared prompt, so
209
+ cross-model numbers compare like with like.
210
+
211
+ ## CI
212
+
213
+ ```yaml
214
+ - uses: actions/checkout@v4
215
+ - uses: actions/setup-python@v5
216
+ with:
217
+ python-version: "3.12"
218
+ - 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/
225
+ ```
226
+
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.
232
+
233
+ ## Fetching other people's docs
234
+
235
+ Benchmarking means requesting pages from organisations who did not ask to be
236
+ measured. quickstarted sends a truthful User-Agent, honours `robots.txt`, and
237
+ waits a second between requests to the same host. `--cache-dir` makes reruns
238
+ read the same bytes. `--refresh` flags pages whose content changed since the
239
+ last run, which is a finding in its own right.
240
+
241
+ ## What this does and does not tell you
242
+
243
+ It tells you whether a given model, on a given day, could get from your docs to
244
+ a working result, and where it was reading when it could not. That is a lower
245
+ 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
247
+ UI testing at all: if your product's button moved, this will not notice.
248
+ [Doc Detective](https://github.com/doc-detective/doc-detective) is the tool for
249
+ that, and the two answer different questions.
250
+
251
+ Write success scripts that assert local, deterministic facts. A pass means the
252
+ floor holds, and says nothing about the ceiling.
253
+
254
+ ## License
255
+
256
+ MIT.