agentpace 0.9.2__tar.gz → 0.11.2__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 (102) hide show
  1. {agentpace-0.9.2 → agentpace-0.11.2}/.github/workflows/publish.yml +5 -3
  2. {agentpace-0.9.2 → agentpace-0.11.2}/CHANGELOG.md +65 -0
  3. {agentpace-0.9.2 → agentpace-0.11.2}/CITATION.cff +2 -2
  4. {agentpace-0.9.2 → agentpace-0.11.2}/PKG-INFO +10 -7
  5. {agentpace-0.9.2 → agentpace-0.11.2}/README.md +9 -6
  6. {agentpace-0.9.2 → agentpace-0.11.2}/docs/DESIGN.md +13 -1
  7. {agentpace-0.9.2 → agentpace-0.11.2}/docs/cli.md +6 -4
  8. {agentpace-0.9.2 → agentpace-0.11.2}/docs/index.html +2 -2
  9. {agentpace-0.9.2 → agentpace-0.11.2}/docs/releasing.md +8 -8
  10. {agentpace-0.9.2 → agentpace-0.11.2}/skills/agentpace/SKILL.md +3 -3
  11. {agentpace-0.9.2 → agentpace-0.11.2}/skills/pace-estimate/SKILL.md +3 -3
  12. {agentpace-0.9.2 → agentpace-0.11.2}/skills/pace-log/SKILL.md +1 -1
  13. {agentpace-0.9.2 → agentpace-0.11.2}/skills/pace-share/SKILL.md +1 -1
  14. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/__init__.py +1 -1
  15. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/claims.py +12 -0
  16. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/cli.py +19 -0
  17. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/estimate.py +140 -9
  18. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/forecasts.py +4 -0
  19. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/render.py +43 -0
  20. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_claims.py +36 -0
  21. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_estimate.py +122 -0
  22. agentpace-0.11.2/tests/test_frozen_method.py +225 -0
  23. agentpace-0.11.2/tests/test_outcomes.py +334 -0
  24. agentpace-0.11.2/tests/test_review_capacity.py +273 -0
  25. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_skills.py +2 -2
  26. {agentpace-0.9.2 → agentpace-0.11.2}/.gitattributes +0 -0
  27. {agentpace-0.9.2 → agentpace-0.11.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  28. {agentpace-0.9.2 → agentpace-0.11.2}/.github/ISSUE_TEMPLATE/ingest-found-nothing.yml +0 -0
  29. {agentpace-0.9.2 → agentpace-0.11.2}/.github/ISSUE_TEMPLATE/remove-my-rows.yml +0 -0
  30. {agentpace-0.9.2 → agentpace-0.11.2}/.github/dependabot.yml +0 -0
  31. {agentpace-0.9.2 → agentpace-0.11.2}/.github/pull_request_template.md +0 -0
  32. {agentpace-0.9.2 → agentpace-0.11.2}/.github/workflows/ci.yml +0 -0
  33. {agentpace-0.9.2 → agentpace-0.11.2}/.github/workflows/validate-data.yml +0 -0
  34. {agentpace-0.9.2 → agentpace-0.11.2}/.gitignore +0 -0
  35. {agentpace-0.9.2 → agentpace-0.11.2}/CODE_OF_CONDUCT.md +0 -0
  36. {agentpace-0.9.2 → agentpace-0.11.2}/CONTRIBUTING.md +0 -0
  37. {agentpace-0.9.2 → agentpace-0.11.2}/LICENSE +0 -0
  38. {agentpace-0.9.2 → agentpace-0.11.2}/SECURITY.md +0 -0
  39. {agentpace-0.9.2 → agentpace-0.11.2}/data/LICENSE +0 -0
  40. {agentpace-0.9.2 → agentpace-0.11.2}/data/README.md +0 -0
  41. {agentpace-0.9.2 → agentpace-0.11.2}/data/runs/4546788e7930eccd-2026-W40-2026-W45.jsonl +0 -0
  42. {agentpace-0.9.2 → agentpace-0.11.2}/data/runs/4546788e7930eccd-2026-W44-2026-W45.jsonl +0 -0
  43. {agentpace-0.9.2 → agentpace-0.11.2}/docs/.nojekyll +0 -0
  44. {agentpace-0.9.2 → agentpace-0.11.2}/docs/assets/backtest-coverage.svg +0 -0
  45. {agentpace-0.9.2 → agentpace-0.11.2}/docs/assets/hero.webp +0 -0
  46. {agentpace-0.9.2 → agentpace-0.11.2}/docs/assets/run-ranges.svg +0 -0
  47. {agentpace-0.9.2 → agentpace-0.11.2}/docs/assets/social-preview.png +0 -0
  48. {agentpace-0.9.2 → agentpace-0.11.2}/docs/backtest.json +0 -0
  49. {agentpace-0.9.2 → agentpace-0.11.2}/docs/backtest.md +0 -0
  50. {agentpace-0.9.2 → agentpace-0.11.2}/docs/robots.txt +0 -0
  51. {agentpace-0.9.2 → agentpace-0.11.2}/docs/sitemap.xml +0 -0
  52. {agentpace-0.9.2 → agentpace-0.11.2}/docs/verify-adapter.md +0 -0
  53. {agentpace-0.9.2 → agentpace-0.11.2}/examples/plan.json +0 -0
  54. {agentpace-0.9.2 → agentpace-0.11.2}/examples/quote-plan.json +0 -0
  55. {agentpace-0.9.2 → agentpace-0.11.2}/pyproject.toml +0 -0
  56. {agentpace-0.9.2 → agentpace-0.11.2}/schema/run.schema.json +0 -0
  57. {agentpace-0.9.2 → agentpace-0.11.2}/schema/turn.schema.json +0 -0
  58. {agentpace-0.9.2 → agentpace-0.11.2}/skills/agentpace/assets/portable-prompt.md +0 -0
  59. {agentpace-0.9.2 → agentpace-0.11.2}/skills/agentpace/references/method.md +0 -0
  60. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/__main__.py +0 -0
  61. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/adapters/__init__.py +0 -0
  62. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/adapters/base.py +0 -0
  63. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/adapters/claude_code.py +0 -0
  64. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/adapters/cline.py +0 -0
  65. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/adapters/codex.py +0 -0
  66. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/adapters/gemini_cli.py +0 -0
  67. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/adapters/opencode.py +0 -0
  68. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/backtest.py +0 -0
  69. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/cache.py +0 -0
  70. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/charts.py +0 -0
  71. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/classify.py +0 -0
  72. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/ingest.py +0 -0
  73. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/paths.py +0 -0
  74. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/pseudonym.py +0 -0
  75. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/py.typed +0 -0
  76. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/report.py +0 -0
  77. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/schema.py +0 -0
  78. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/share.py +0 -0
  79. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/site.py +0 -0
  80. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/turns.py +0 -0
  81. {agentpace-0.9.2 → agentpace-0.11.2}/src/agentpace/validate.py +0 -0
  82. {agentpace-0.9.2 → agentpace-0.11.2}/tests/__init__.py +0 -0
  83. {agentpace-0.9.2 → agentpace-0.11.2}/tests/fixtures/__init__.py +0 -0
  84. {agentpace-0.9.2 → agentpace-0.11.2}/tests/fixtures/cline_tasks.py +0 -0
  85. {agentpace-0.9.2 → agentpace-0.11.2}/tests/fixtures/gemini_cli.py +0 -0
  86. {agentpace-0.9.2 → agentpace-0.11.2}/tests/fixtures/opencode.py +0 -0
  87. {agentpace-0.9.2 → agentpace-0.11.2}/tests/synthetic.py +0 -0
  88. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_backtest.py +0 -0
  89. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_classify.py +0 -0
  90. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_cli.py +0 -0
  91. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_cline.py +0 -0
  92. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_codex.py +0 -0
  93. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_docs.py +0 -0
  94. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_forecasts.py +0 -0
  95. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_gemini_cli.py +0 -0
  96. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_ingest.py +0 -0
  97. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_labels.py +0 -0
  98. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_opencode.py +0 -0
  99. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_schema.py +0 -0
  100. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_share_validate.py +0 -0
  101. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_site.py +0 -0
  102. {agentpace-0.9.2 → agentpace-0.11.2}/tests/test_turns.py +0 -0
@@ -1,9 +1,11 @@
1
1
  name: Publish
2
2
 
3
- # Manual until the PyPI pending publisher exists, so a release cannot produce a failed run.
4
- # Once it does: dispatch this on the latest tested tag, `gh workflow run publish.yml --ref vX.Y.Z`,
5
- # confirm it passes, then restore `release: types: [published]`. docs/releasing.md has the steps.
3
+ # Publishing is live: agentpace is on PyPI since 0.9.2. Publishing a GitHub release page uploads
4
+ # its version. Dispatch remains for re-running a version that did not upload:
5
+ # `gh workflow run publish.yml --ref vX.Y.Z`. docs/releasing.md has the steps.
6
6
  on:
7
+ release:
8
+ types: [published]
7
9
  workflow_dispatch:
8
10
 
9
11
  permissions:
@@ -4,6 +4,71 @@ This project follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) an
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.11.2 - 2026-11-09
8
+
9
+ ### Changed
10
+
11
+ - A cell warning now says which level had no recorded run (kind, risk or model), which of the task's levels were therefore not used, where the mean and the residuals came from, and which values are recorded under the parent. For example, a `feature` task marked `high` on `claude-opus-5-5` now reads: `cell feature x high: n=0. No recorded run has kind feature at risk high, so model claude-opus-5-5 was not used. Mean and residuals from feature (n=1353); interval widened by the spread between risk levels. Recorded risk levels under feature: normal (1353).`
12
+ - Thin cells say the mean is shrunk toward broader cells and name the residual pool.
13
+
14
+ ### Added
15
+
16
+ - A regression fixture, `tests/test_frozen_method.py`, pins the shipped method's numbers on synthetic rows, not only its name. Its values were generated with 0.11.1's code, and it passes on 0.11.1 unchanged.
17
+
18
+ No published number moved; the warning line in the two README examples changed.
19
+
20
+ ## 0.11.1 - 2026-11-08
21
+
22
+ ### Changed
23
+
24
+ - agentpace installs from PyPI, where it has been since 0.9.2.
25
+ - The README and the site now show `uvx agentpace estimate --example` and `uv tool install agentpace` (the README also `pipx install agentpace`), and all four skills show `uv tool install agentpace`, instead of the git install lines.
26
+ - The README no longer says agentpace is not on PyPI, and DESIGN section 8 says the install is from PyPI since 0.9.2.
27
+ - `publish.yml` runs when a GitHub release page is published, so each release uploads its version to PyPI. Manual dispatch remains for re-running a version that did not upload.
28
+ - `docs/releasing.md` describes releasing after the switch: publishing the release page uploads the version, so check the publish run. The one-time PyPI setup and the switch stay as the record.
29
+ - The quickstart tests expect `uvx agentpace estimate --example`.
30
+
31
+ No published number moved.
32
+
33
+ ## 0.11.0 - 2026-11-08
34
+
35
+ ### Added
36
+
37
+ - `agentpace estimate --review-capacity TEXT` takes your own review capacity, such as "4 PRs a day", verbatim. It prints on its own line under the human part line: `Review capacity: "4 PRs a day" (stated by you; not measured, not applied to the total: a plan task is an agent run, not a pull request)`.
38
+ - Nothing is parsed or computed from it: no number is extracted, no unit converted and no human timeline derived. The range never changes.
39
+ - The text must be one line of at most 80 characters once stripped. Otherwise the command exits 2 with a one-line message, before anything is read or written.
40
+ - `--json` gains `human.review_capacity_stated`, present only when the flag is given.
41
+ - With `--record`, the ledger keeps it inside `human`; without `--record`, nothing is written.
42
+ - `score` and `share` never see it.
43
+ - A forecast recorded with a stated capacity gets a different id from the same plan without one, because the id hashes `human`.
44
+ - The `agentpace` and `pace-estimate` skills pass the figure only when you state one, and relay its line as printed.
45
+
46
+ ### Changed
47
+
48
+ - `/pace-estimate` now passes a timeline someone already quoted to the CLI with `--quoted`, and relays the `Quoted:` line as printed.
49
+ - Both skills now say never to state what a quoted timeline counts, because nothing has checked it. A smoke prompt had speculated about it.
50
+
51
+ No published number moved.
52
+
53
+ ## 0.10.0 - 2026-11-08
54
+
55
+ ### Added
56
+
57
+ - Every estimate now prints the recorded runs that did not complete, by harness, on one line after the Method line. The Method line is unchanged. For example: `Not completed, by harness: claude-code 13 of 3219 (0.4%; 3 failed, 10 unknown), codex 34 of 211 (16.1%; 33 failed, 1 unknown). These are recorded harness outcomes, not task acceptance, and they are left out of the fit.`
58
+ - The counts cover every row the estimate loaded, after duplicates are dropped, and never a single cell.
59
+ - `unknown` is never counted as a failure.
60
+ - The counts never widen or change the range, and counting draws no random numbers.
61
+ - `--json` gains `outcomes`: `rows`, `completed`, `failed`, `unknown`, `completed_without_duration` and `by_harness`.
62
+ - `estimate --record` stores `outcomes` with the forecast. Records from earlier versions still read and score.
63
+ - A record's id hashes its content, so the ids of new forecasts differ from what the same forecast would have received before.
64
+ - The README's Limitations bullet on failed runs carries the per-harness counts as a generated claim (`not_completed_by_harness`). A test keeps those shares out of unmarked text.
65
+
66
+ ### Changed
67
+
68
+ - `docs/cli.md` documents `rows_excluded` as failed plus unknown plus completed rows with no duration. Its value and meaning (rows not used) are unchanged.
69
+
70
+ No published number moved. New generated numbers: the per-harness not-completed counts in the two README examples and in Limitations (claude-code 13 of 3,219, codex 34 of 211).
71
+
7
72
  ## 0.9.2 - 2026-11-08
8
73
 
9
74
  ### Fixed
@@ -8,8 +8,8 @@ authors:
8
8
  repository-code: https://github.com/mohamedzhioua/agentpace
9
9
  url: https://mohamedzhioua.github.io/agentpace/
10
10
  license: MIT
11
- version: 0.9.2
12
- date-released: 2026-11-08
11
+ version: 0.11.2
12
+ date-released: 2026-11-09
13
13
  keywords:
14
14
  - coding agents
15
15
  - estimation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentpace
3
- Version: 0.9.2
3
+ Version: 0.11.2
4
4
  Summary: Stop estimating agent work in human weeks. agentpace measures how long coding agents actually take: P50/P80/P90 ranges from real Claude Code and Codex runs, the human part stated apart, never derived. Open CC0 dataset, zero-dependency Python CLI, agent skill.
5
5
  Project-URL: Homepage, https://mohamedzhioua.github.io/agentpace/
6
6
  Project-URL: Repository, https://github.com/mohamedzhioua/agentpace
@@ -41,7 +41,7 @@ Description-Content-Type: text/markdown
41
41
  With [uv](https://docs.astral.sh/uv/), this needs no install, no clone and no history of your own:
42
42
 
43
43
  ```bash
44
- uvx --from git+https://github.com/mohamedzhioua/agentpace agentpace estimate --example
44
+ uvx agentpace estimate --example
45
45
  ```
46
46
 
47
47
  ```text
@@ -60,9 +60,10 @@ review 3 min 8 min 13 min 82% review x low x claude-opus-5-5
60
60
  Critical path: schema -> ui -> review (longest in 47% of simulations)
61
61
  Effective parallelism at cap 3: 0.90 (from 29 sessions). Throughput set the total in 76% of simulations.
62
62
  Method: shrunk. 3383 runs, 47 not completed so excluded. 20000 simulations, seed 20261107.
63
+ Not completed, by harness: claude-code 13 of 3219 (0.4%; 3 failed, 10 unknown), codex 34 of 211 (16.1%; 33 failed, 1 unknown). These are recorded harness outcomes, not task acceptance, and they are left out of the fit.
63
64
 
64
65
  Warnings:
65
- - cell docs x normal x claude-opus-5-5: n=0, effective n=0.0, below 30. Pooled with all and interval widened.
66
+ - cell docs x normal x claude-opus-5-5: n=0. No recorded run has kind docs at risk normal with model claude-opus-5-5. Mean from docs x normal (n=21); residuals pooled from all; interval widened by the spread between models. Recorded models under docs x normal: claude-sonnet-5-5 (21).
66
67
  <!-- /claim -->
67
68
  ```
68
69
 
@@ -117,10 +118,10 @@ These are <!-- claim:completed -->3,383<!-- /claim --> completed agent runs: Cla
117
118
  ## Install
118
119
 
119
120
  ```bash
120
- uv tool install git+https://github.com/mohamedzhioua/agentpace
121
+ uv tool install agentpace
121
122
  ```
122
123
 
123
- `pipx install git+https://github.com/mohamedzhioua/agentpace` works too. It needs Python 3.10 or later and has no dependencies. agentpace is not on PyPI yet, which is why both commands install from git. After installing, drop the `uvx --from ...` prefix from the command above.
124
+ `pipx install agentpace` works too. It needs Python 3.10 or later and has no dependencies. After installing, drop `uvx` from the command above.
124
125
 
125
126
  Then add the agent skills:
126
127
 
@@ -167,9 +168,10 @@ review 3 min 8 min 13 min 100% review x low x claude-opu
167
168
  Critical path: schema -> importer -> tests -> review (longest in 40% of simulations)
168
169
  Effective parallelism at cap 3: 0.90 (from 29 sessions). Throughput set the total in 78% of simulations.
169
170
  Method: shrunk. 3383 runs, 47 not completed so excluded. 20000 simulations, seed 20261107.
171
+ Not completed, by harness: claude-code 13 of 3219 (0.4%; 3 failed, 10 unknown), codex 34 of 211 (16.1%; 33 failed, 1 unknown). These are recorded harness outcomes, not task acceptance, and they are left out of the fit.
170
172
 
171
173
  Warnings:
172
- - cell tests x normal x claude-opus-5-5: n=2, effective n=2.0, below 30. Pooled with all and interval widened.
174
+ - cell tests x normal x claude-opus-5-5: n=2, effective n=2.0, below 30. Mean shrunk toward broader cells; residuals pooled from all; interval widened.
173
175
  <!-- /claim -->
174
176
  ```
175
177
 
@@ -180,6 +182,7 @@ The quote is calendar time, as quoted, and nothing has checked it. The range is
180
182
  | Use | What it does |
181
183
  |---|---|
182
184
  | `agentpace estimate <plan>` | P50, P80 and P90 for a plan, and the human part on its own line |
185
+ | `agentpace estimate <plan> --review-capacity "4 PRs a day"` | The same, with your stated review capacity shown on its own line as yours: not measured, not applied to the range |
183
186
  | `agentpace estimate <plan> --data ~/.agentpace/runs.jsonl` | The same, from your own ingested runs only |
184
187
  | `agentpace estimate <plan> --record --quoted "3 weeks"` | Keep the forecast on your machine to score later, with any timeline someone already quoted |
185
188
  | `agentpace ingest [path]` | Read local agent history into rows |
@@ -306,7 +309,7 @@ Each of these does something better than agentpace:
306
309
  - **Pooled by kind, risk and model only.** A cell with fewer than 30 runs leans on broader cells, with a warning. Repository size is not recorded, and `size` is an edit count measured after the run, so neither can segment the published runs. Long integration work has no cell of its own.
307
310
  - **Two harnesses, still one person.** The published rows hold Claude Code subagent runs and, since 0.6.0, Codex turns, all from the same contributor. Codex is scored on its own, never in the headline. Each Codex exec session is a single turn, so the session effect and the parallelism factor learn nothing from Codex, and a Codex plan borrows Claude Code's session structure. Gemini CLI, OpenCode and Cline have experimental adapters: built from each tool's source, not yet verified on real sessions, with no published rows (`validate` rejects them until each adapter is verified; [how to verify one](docs/verify-adapter.md)), and problems go in an [issue](https://github.com/mohamedzhioua/agentpace/issues). Other harnesses have no adapter.
308
311
  - **No human allowance, and short waits sit inside the clock.** The plan total is a forecast on the session clock, which counts agent runs and the gaps between them, each gap up to the idle cap (900 s in the published rows). So a wait on you shorter than that sits inside the total, a longer one is clipped at the cap, and no allowance for review, testing or decisions is added. Every estimate says so on its own lines and never derives the human part from agent time. `agentpace turns` measures your own wait per human turn from your main-session timestamps and keeps it on your machine. That is elapsed time, not effort, shown beside the range and never added to it.
309
- - **Failed runs are under-counted.** Only runs with a clear completion signal train the estimator, so P90 may run low.
312
+ - **Failed runs are under-counted.** Only runs with a clear completion signal train the estimator, so P90 may run low. Recorded runs that did not complete (failed or unknown), by harness: <!-- claim:not_completed_by_harness -->claude-code 13 of 3,219 (0.4%), codex 34 of 211 (16.1%)<!-- /claim -->. Those are harness outcomes, not task acceptance, and every estimate prints the counts for the rows it loaded.
310
313
  - **Size is not used by default.** Stored size is measured after a run, so it would leak the answer into the forecast.
311
314
  - **The transcript format is undocumented.** It may change, and Claude Code removes old transcripts, so run `agentpace ingest` regularly.
312
315
  - **Dependencies are untested.** The whole-session test cannot see real dependencies.
@@ -14,7 +14,7 @@
14
14
  With [uv](https://docs.astral.sh/uv/), this needs no install, no clone and no history of your own:
15
15
 
16
16
  ```bash
17
- uvx --from git+https://github.com/mohamedzhioua/agentpace agentpace estimate --example
17
+ uvx agentpace estimate --example
18
18
  ```
19
19
 
20
20
  ```text
@@ -33,9 +33,10 @@ review 3 min 8 min 13 min 82% review x low x claude-opus-5-5
33
33
  Critical path: schema -> ui -> review (longest in 47% of simulations)
34
34
  Effective parallelism at cap 3: 0.90 (from 29 sessions). Throughput set the total in 76% of simulations.
35
35
  Method: shrunk. 3383 runs, 47 not completed so excluded. 20000 simulations, seed 20261107.
36
+ Not completed, by harness: claude-code 13 of 3219 (0.4%; 3 failed, 10 unknown), codex 34 of 211 (16.1%; 33 failed, 1 unknown). These are recorded harness outcomes, not task acceptance, and they are left out of the fit.
36
37
 
37
38
  Warnings:
38
- - cell docs x normal x claude-opus-5-5: n=0, effective n=0.0, below 30. Pooled with all and interval widened.
39
+ - cell docs x normal x claude-opus-5-5: n=0. No recorded run has kind docs at risk normal with model claude-opus-5-5. Mean from docs x normal (n=21); residuals pooled from all; interval widened by the spread between models. Recorded models under docs x normal: claude-sonnet-5-5 (21).
39
40
  <!-- /claim -->
40
41
  ```
41
42
 
@@ -90,10 +91,10 @@ These are <!-- claim:completed -->3,383<!-- /claim --> completed agent runs: Cla
90
91
  ## Install
91
92
 
92
93
  ```bash
93
- uv tool install git+https://github.com/mohamedzhioua/agentpace
94
+ uv tool install agentpace
94
95
  ```
95
96
 
96
- `pipx install git+https://github.com/mohamedzhioua/agentpace` works too. It needs Python 3.10 or later and has no dependencies. agentpace is not on PyPI yet, which is why both commands install from git. After installing, drop the `uvx --from ...` prefix from the command above.
97
+ `pipx install agentpace` works too. It needs Python 3.10 or later and has no dependencies. After installing, drop `uvx` from the command above.
97
98
 
98
99
  Then add the agent skills:
99
100
 
@@ -140,9 +141,10 @@ review 3 min 8 min 13 min 100% review x low x claude-opu
140
141
  Critical path: schema -> importer -> tests -> review (longest in 40% of simulations)
141
142
  Effective parallelism at cap 3: 0.90 (from 29 sessions). Throughput set the total in 78% of simulations.
142
143
  Method: shrunk. 3383 runs, 47 not completed so excluded. 20000 simulations, seed 20261107.
144
+ Not completed, by harness: claude-code 13 of 3219 (0.4%; 3 failed, 10 unknown), codex 34 of 211 (16.1%; 33 failed, 1 unknown). These are recorded harness outcomes, not task acceptance, and they are left out of the fit.
143
145
 
144
146
  Warnings:
145
- - cell tests x normal x claude-opus-5-5: n=2, effective n=2.0, below 30. Pooled with all and interval widened.
147
+ - cell tests x normal x claude-opus-5-5: n=2, effective n=2.0, below 30. Mean shrunk toward broader cells; residuals pooled from all; interval widened.
146
148
  <!-- /claim -->
147
149
  ```
148
150
 
@@ -153,6 +155,7 @@ The quote is calendar time, as quoted, and nothing has checked it. The range is
153
155
  | Use | What it does |
154
156
  |---|---|
155
157
  | `agentpace estimate <plan>` | P50, P80 and P90 for a plan, and the human part on its own line |
158
+ | `agentpace estimate <plan> --review-capacity "4 PRs a day"` | The same, with your stated review capacity shown on its own line as yours: not measured, not applied to the range |
156
159
  | `agentpace estimate <plan> --data ~/.agentpace/runs.jsonl` | The same, from your own ingested runs only |
157
160
  | `agentpace estimate <plan> --record --quoted "3 weeks"` | Keep the forecast on your machine to score later, with any timeline someone already quoted |
158
161
  | `agentpace ingest [path]` | Read local agent history into rows |
@@ -279,7 +282,7 @@ Each of these does something better than agentpace:
279
282
  - **Pooled by kind, risk and model only.** A cell with fewer than 30 runs leans on broader cells, with a warning. Repository size is not recorded, and `size` is an edit count measured after the run, so neither can segment the published runs. Long integration work has no cell of its own.
280
283
  - **Two harnesses, still one person.** The published rows hold Claude Code subagent runs and, since 0.6.0, Codex turns, all from the same contributor. Codex is scored on its own, never in the headline. Each Codex exec session is a single turn, so the session effect and the parallelism factor learn nothing from Codex, and a Codex plan borrows Claude Code's session structure. Gemini CLI, OpenCode and Cline have experimental adapters: built from each tool's source, not yet verified on real sessions, with no published rows (`validate` rejects them until each adapter is verified; [how to verify one](docs/verify-adapter.md)), and problems go in an [issue](https://github.com/mohamedzhioua/agentpace/issues). Other harnesses have no adapter.
281
284
  - **No human allowance, and short waits sit inside the clock.** The plan total is a forecast on the session clock, which counts agent runs and the gaps between them, each gap up to the idle cap (900 s in the published rows). So a wait on you shorter than that sits inside the total, a longer one is clipped at the cap, and no allowance for review, testing or decisions is added. Every estimate says so on its own lines and never derives the human part from agent time. `agentpace turns` measures your own wait per human turn from your main-session timestamps and keeps it on your machine. That is elapsed time, not effort, shown beside the range and never added to it.
282
- - **Failed runs are under-counted.** Only runs with a clear completion signal train the estimator, so P90 may run low.
285
+ - **Failed runs are under-counted.** Only runs with a clear completion signal train the estimator, so P90 may run low. Recorded runs that did not complete (failed or unknown), by harness: <!-- claim:not_completed_by_harness -->claude-code 13 of 3,219 (0.4%), codex 34 of 211 (16.1%)<!-- /claim -->. Those are harness outcomes, not task acceptance, and every estimate prints the counts for the rows it loaded.
283
286
  - **Size is not used by default.** Stored size is measured after a run, so it would leak the answer into the forecast.
284
287
  - **The transcript format is undocumented.** It may change, and Claude Code removes old transcripts, so run `agentpace ingest` regularly.
285
288
  - **Dependencies are untested.** The whole-session test cannot see real dependencies.
@@ -264,7 +264,7 @@ for each draw:
264
264
 
265
265
  **Layout (D6).** Four sibling folders. `skills/agentpace/` holds the guidance, the portable paste-in prompt and the method summary, and auto-triggers on estimate questions. `skills/pace-estimate/`, `skills/pace-log/` and `skills/pace-share/` are thin commands. Frontmatter stays within the six fields of the open standard for portability. Reports that extra keys fail claude.ai uploads are unverified, and that check is a Phase 7 step.
266
266
 
267
- **Install.** `uv tool install git+https://github.com/mohamedzhioua/agentpace` or the `pipx` equivalent, with no PyPI publish needed. The wheel carries `schema/` and `data/runs/` through hatchling `force-include`, so an install can estimate before any local history exists, with no duplicate copy in git and no download at run time. `npx skills add` has no post-install hook and we will not add an implicit download, so a skill checks for `agentpace` and shows the install command if it is missing. Phase 7 proves this with a real `npx skills add` run, which needs Node 22.20 or later.
267
+ **Install.** `uv tool install agentpace` or the `pipx` equivalent, from PyPI since 0.9.2. The wheel carries `schema/` and `data/runs/` through hatchling `force-include`, so an install can estimate before any local history exists, with no duplicate copy in git and no download at run time. `npx skills add` has no post-install hook and we will not add an implicit download, so a skill checks for `agentpace` and shows the install command if it is missing. Phase 7 proves this with a real `npx skills add` run, which needs Node 22.20 or later.
268
268
 
269
269
  **Trigger evals.** Each run used the same 9 prompts, one fresh session per prompt, with only `skills/` installed: 6 questions about how long software work takes, one of them re-answering a quoted "3 month project", and 3 near misses (baking bread, a git rebase's CPU time, and a request to write a date function). These are dated single passes, evidence rather than guarantees, and they are not README claims.
270
270
 
@@ -495,6 +495,18 @@ Fable 5.1 ruled on 2026-11-08 on four questions users asked after the launch pos
495
495
  - **Declined:** retry bucketing, time to an accepted result, and new segmentation dimensions. The rows hold no attempt linkage, no acceptance event and no repository size.
496
496
  - **The 2026-W46 data change** takes the next patch number when that week has ended, and waits for none of this.
497
497
 
498
+ ### Ruling for 0.12
499
+
500
+ Fable 5.1 ruled on 2026-11-09, with an independent opinion from Codex (gpt-6-astra) on the same brief, both at the highest effort. The master checked every claim in the opinion before the ruling.
501
+
502
+ - **The finding.** In the rows, `risk` is the agent's role (review and research agents `low`, migration agents `high`, everything else `normal`; the classifier, section 4). A plan's `risk` was a judgment about the work. The lookup stops at the first unseen level, so a task marked `high` matched no runs, dropped the model, and for an Opus feature cut the P50 by a factor of 16, in the shorter direction. No backtest fold ever reaches that branch, because row risk follows the agent type.
503
+ - **Warnings say what was not reached (0.11.2).** Which level had no recorded run, which of the task's levels were therefore not used, where the mean and residuals came from, and what is recorded under the parent. No counterfactual is simulated. A synthetic fixture pins the shipped method's numbers, not only its name.
504
+ - **The portable prompt's table (0.11.3)** is by kind, recorded risk and model, with pooled rows that mirror the CLI's back-off and no range under 30 runs. Claim enforcement covers the prompt's cells.
505
+ - **Plan risk is read as the recorded category (0.12.0), at the plan boundary, not in the method.** A written risk is used only where the rows can record that kind at that risk (`review` low, `migration` high, `other` low for research or normal, all else normal). Any other value, and an omitted one, takes the kind's recorded risk, and a warning names the task. `--use-risk` looks the written values up as given, for labelled datasets. `fit`, `locate`, the backtest's queries and the frozen selection are unchanged, so there is no re-registration.
506
+ - **Declined:** reordering or extending the hierarchy, a model fallback at the root or a shared model effect (method changes under the frozen selection), relabelling unseen kinds, and a risk multiplier.
507
+ - **Cleanup (0.12.1, 0.12.2):** count messages agree in number, documentation corrections, and clean errors for a bad rows line and a contribution file another program holds open.
508
+ - The W46 data change takes the next patch when that week ends, and waits for nothing.
509
+
498
510
  ## 12. Sources
499
511
 
500
512
  Repos, audited at: `kiloloop/agent-estimate@8857979`, `MMMProd-Org/claude-eta@5448ae4` (the `mmmprod/claude-eta` URL redirects there), `ZhangHanDong/agent-estimation@71a4697`, `ecappa/web-dev-estimation@c61fb1c`, `MedAdemBHA/mythical-agent-month@b60165f`.
@@ -100,7 +100,7 @@ stdout: `labeled <ref>: <kind>, <risk> risk`.
100
100
  ## estimate
101
101
 
102
102
  ```text
103
- agentpace estimate (PLAN | - | --example) [--data PATH]... [--method NAME] [--use-size] [--draws N] [--seed N] [--json] [--turns FILE] [--record] [--quoted TEXT]
103
+ agentpace estimate (PLAN | - | --example) [--data PATH]... [--method NAME] [--use-size] [--draws N] [--seed N] [--json] [--turns FILE] [--record] [--quoted TEXT] [--review-capacity TEXT]
104
104
  ```
105
105
 
106
106
  Simulates a plan of tasks and prints P50, P80 and P90 per task (agent run time) and for the plan (its span on the session clock).
@@ -118,6 +118,7 @@ Simulates a plan of tasks and prints P50, P80 and P90 per task (agent run time)
118
118
  | `--turns FILE` | `~/.agentpace/turns.jsonl` | Waits measured by `agentpace turns`, for the human part line. The file must exist when given. |
119
119
  | `--record` | off | Keep this forecast in `~/.agentpace/forecasts.jsonl`, to score later. |
120
120
  | `--quoted TEXT` | none | A timeline someone already gave, such as `"3 weeks"`. Shown beside the range as quoted, and stored with the forecast when `--record` is given. |
121
+ | `--review-capacity TEXT` | none | Your own review capacity, such as `"4 PRs a day"`. Shown verbatim on its own line under the human part line, as stated by you: not measured and never applied to the range. Nothing is parsed or computed from it. It must be one line of at most 80 characters once stripped; otherwise the command exits 2. Stored with the forecast when `--record` is given. |
121
122
 
122
123
  stdout: the result, as text or JSON. stderr, with `--record`: `recorded forecast <id> in <path>`, and with `--quoted`, whether the quote was parsed as calendar time or stored as text.
123
124
 
@@ -139,7 +140,7 @@ With `--quoted`, one line sits directly under the total. It holds the quote verb
139
140
  Quoted: "3 weeks" (calendar time, as quoted; unverified, not converted)
140
141
  ```
141
142
 
142
- Then a table of tasks with their P50, P80, P90, how often each was on the critical path and the data cell it drew from, then the critical path, the effective parallelism, the method and the number of runs used, and any warnings. Durations print to two significant figures: seconds under 90 s, minutes under 90 min, hours under 48 h, days beyond.
143
+ Then a table of tasks with their P50, P80, P90, how often each was on the critical path and the data cell it drew from, then the critical path, the effective parallelism, the method and the number of runs used, the runs that did not complete by harness, and any warnings. Durations print to two significant figures: seconds under 90 s, minutes under 90 min, hours under 48 h, days beyond. The line after the method counts every loaded run, never per cell: `Not completed, by harness: <harness> N of M (S%; F failed, U unknown), ...`, then says these are recorded harness outcomes, not task acceptance, left out of the fit, and, when there are any, how many completed rows with no duration are left out too.
143
144
 
144
145
  ### Plan JSON
145
146
 
@@ -195,11 +196,12 @@ One JSON object. Every duration is in seconds, as a number.
195
196
  | `method` | string | The method used. |
196
197
  | `size_used` | boolean | Whether `--use-size` was given. |
197
198
  | `rows_used` | integer | Completed runs the estimate learned from. |
198
- | `rows_excluded` | integer | Runs left out because they did not complete. |
199
+ | `rows_excluded` | integer | Runs left out of the fit: those that did not complete (failed or unknown), and completed runs with no duration. |
200
+ | `outcomes` | object | Recorded harness outcomes of every row the estimate loaded, after duplicates are dropped, and never per cell: `rows`, `completed`, `failed`, `unknown`, `completed_without_duration` (completed rows with a duration of 0), and `by_harness`, a list sorted by harness name of objects with `harness`, `rows`, `completed`, `failed` and `unknown`. An outcome is how the run ended in its harness, not task acceptance, and `unknown` is not counted as a failure. The counts are shown, never used to change the range. `rows` is `completed` + `failed` + `unknown`, `rows_used` is `completed` - `completed_without_duration`, and `rows_excluded` is `failed` + `unknown` + `completed_without_duration`. |
199
201
  | `draws` | integer | Simulations run. |
200
202
  | `seed` | integer | The random seed. |
201
203
  | `warnings` | list of strings | Thin or unseen cells, ignored sizes, an assumed parallelism. Pass them on. |
202
- | `human` | object | The human part. `status` is `not measured` or `measured`, and `included_in_total` is always `false`: no allowance for the human part is added to the total (waits shorter than the idle cap sit inside the session clock already). When measured, it also holds `source`, `per_turn_p50`, `per_turn_p80`, `per_turn_p90` (seconds per human turn), `gaps` and `sessions`. Keys without a value are left out. |
204
+ | `human` | object | The human part. `status` is `not measured` or `measured`, and `included_in_total` is always `false`: no allowance for the human part is added to the total (waits shorter than the idle cap sit inside the session clock already). When measured, it also holds `source`, `per_turn_p50`, `per_turn_p80`, `per_turn_p90` (seconds per human turn), `gaps` and `sessions`. With `--review-capacity`, it holds `review_capacity_stated`, your text as given, stripped. Keys without a value are left out. |
203
205
 
204
206
  The JSON output does not change with `--record` or `--quoted`.
205
207
 
@@ -204,7 +204,7 @@
204
204
  <section class="reproduce try" id="try" aria-labelledby="try-title">
205
205
  <h2 id="try-title">Try it, with no install</h2>
206
206
  <p class="section-note">Needs <a href="https://docs.astral.sh/uv/">uv</a>. The seed data and the example plan ship inside the package.</p>
207
- <pre><code>uvx --from git+https://github.com/mohamedzhioua/agentpace agentpace estimate --example</code></pre>
207
+ <pre><code>uvx agentpace estimate --example</code></pre>
208
208
  <p class="section-note">It estimates this plan, where each task is one agent run:</p>
209
209
  <pre><code id="example-plan">{
210
210
  "agent_cap": 3,
@@ -262,7 +262,7 @@
262
262
 
263
263
  <section class="reproduce" aria-labelledby="reproduce-title">
264
264
  <h2 id="reproduce-title">Check it yourself</h2>
265
- <pre><code>uv tool install git+https://github.com/mohamedzhioua/agentpace
265
+ <pre><code>uv tool install agentpace
266
266
  agentpace ingest # your own runs, stored locally
267
267
  agentpace estimate plan.json # P50, P80 and P90 for a plan, like the one above
268
268
  agentpace share --dry-run # the exact rows you would contribute</code></pre>
@@ -1,6 +1,6 @@
1
1
  # Releasing agentpace
2
2
 
3
- This is the maintainer's runbook. agentpace is not on PyPI yet: every install line installs from git, and `publish.yml` runs only when dispatched by hand. Part 1 is the release every version gets today. Parts 2 to 4 are the one-time PyPI setup, first upload and check, which need the owner's PyPI and GitHub accounts; part 5 is the pull request that follows. Line numbers below are as of v0.8.4; later releases may move them, so search for the quoted text.
3
+ This is the maintainer's runbook. agentpace is on PyPI since 0.9.2: every install line installs from PyPI, and publishing a GitHub release page runs `publish.yml`, which uploads that version. Part 1 is the release every version gets. Parts 2 to 4 are the one-time PyPI setup, first upload and check, which needed the owner's PyPI and GitHub accounts and were done for 0.9.2; part 5 is the switch that followed, done in 0.11.1. They stay as the record. Line numbers below are as of v0.8.4; later releases have moved them, so search for the quoted text.
4
4
 
5
5
  ## 1. Every release
6
6
 
@@ -16,7 +16,7 @@ This is the maintainer's runbook. agentpace is not on PyPI yet: every install li
16
16
 
17
17
  5. Publish a GitHub release page for the tag (`gh release create vX.Y.Z`).
18
18
 
19
- While `publish.yml` is manual, a release page uploads nothing.
19
+ Publishing the release page runs `publish.yml`, which uploads the version to PyPI. Check that run (`gh run list --workflow publish.yml`, then `gh run watch <id>`) and confirm it passes. A run that failed before `uv publish` uploaded anything can be re-run by hand: `gh workflow run publish.yml --ref vX.Y.Z`.
20
20
 
21
21
  ## 2. The PyPI pending publisher (once)
22
22
 
@@ -46,13 +46,13 @@ agentpace uses trusted publishing: PyPI accepts the identity of the `publish.yml
46
46
  Dispatching needs write access to the repository, so the owner runs it; nothing in a pull request can.
47
47
  4. Watch the run (`gh run list --workflow publish.yml`, then `gh run watch <id>`) and confirm it passes.
48
48
 
49
- **A published version is final.** PyPI never accepts a second upload of the same version, even after you delete it, so never re-dispatch a version that uploaded. If anything about it needs fixing, bump the patch version, release it as in part 1, and dispatch the new tag. A run that failed before `uv publish` uploaded anything can be dispatched again.
49
+ **A published version is final.** PyPI never accepts a second upload of the same version, even after you delete it, so never re-dispatch a version that uploaded. If anything about it needs fixing, bump the patch version and release it as in part 1; its release page uploads it. A run that failed before `uv publish` uploaded anything can be dispatched again.
50
50
 
51
- The PyPI page shows the README as it was at the tag. That README still carries the git install lines and the "not on PyPI yet" sentence, so the first version's page says so. The page is right from the next release after the switch in part 5.
51
+ The PyPI page shows the README as it was at the tag. The 0.9.2 README still carried the git install lines and the "not on PyPI yet" sentence, so that version's page says so. The page is right from 0.11.1, the release that made the switch in part 5. 0.10.0 and 0.11.0 were released on GitHub only, before the switch, so PyPI lists 0.9.2 and then 0.11.1.
52
52
 
53
53
  ## 4. The clean-venv check
54
54
 
55
- Before any install line changes, prove the upload works from a fresh environment, away from the checkout and with an empty agentpace home so no local data reaches the output:
55
+ After every upload (and before the first install line changed, for 0.9.2), prove the upload works from a fresh environment, away from the checkout and with an empty agentpace home so no local data reaches the output:
56
56
 
57
57
  ```bash
58
58
  scratch=$(mktemp -d)
@@ -69,7 +69,7 @@ The last command exits 0 only when the installed `estimate --example` output equ
69
69
 
70
70
  ## 5. After the upload: the switch
71
71
 
72
- Only once part 4 passes, one pull request makes every change below, with a CHANGELOG entry and a version bump. Each item quotes the current text.
72
+ Done in 0.11.1. Once part 4 passed for 0.9.2, one pull request made every change below, with a CHANGELOG entry and a version bump. Each item quotes the text as it was before the switch.
73
73
 
74
74
  1. **README "Try it" line** (`README.md:17`):
75
75
 
@@ -131,9 +131,9 @@ Two more places hold the git URL or depend on it, beyond the list above:
131
131
  7. **`tests/test_skills.py::test_a_command_is_on_the_first_screen`** (`tests/test_skills.py:31`) looks for a line starting with `uvx --from` in the README's first 25 lines, so it fails once item 1 changes. Its prefix becomes `uvx agentpace`.
132
132
  8. **The site's "Check it yourself" block** (`docs/index.html:265`) starts with `uv tool install git+https://github.com/mohamedzhioua/agentpace`, which becomes `uv tool install agentpace`.
133
133
 
134
- In the same pull request, add `release: types: [published]` under `on:` in `publish.yml` and update its comment, so from then on a release page uploads its version. Keeping `workflow_dispatch` beside it is harmless.
134
+ The same pull request added `release: types: [published]` under `on:` in `publish.yml` and updated its comment, so a release page now uploads its version. `workflow_dispatch` stays beside it, for re-running a version that did not upload.
135
135
 
136
- When the switch is done, `git grep -n "git+https://github.com/mohamedzhioua/agentpace" -- . ':!CHANGELOG.md' ':!docs/releasing.md'` and `git grep -n -i "not on PyPI" -- . ':!CHANGELOG.md' ':!docs/releasing.md'` both print nothing. `CHANGELOG.md` keeps the phrase as history and this file keeps it in the quotes above; rewrite this file's first paragraph in the same pull request.
136
+ Since the switch, `git grep -n "git+https://github.com/mohamedzhioua/agentpace" -- . ':!CHANGELOG.md' ':!docs/releasing.md'` and `git grep -n -i "not on PyPI" -- . ':!CHANGELOG.md' ':!docs/releasing.md'` both print nothing. `CHANGELOG.md` keeps the phrase as history, and this file keeps it in the quotes above and in part 3's note on the 0.9.2 page.
137
137
 
138
138
  ## The README on PyPI
139
139
 
@@ -20,7 +20,7 @@ This skill triggers on natural-language questions. `/pace-estimate` is the expli
20
20
  Run `agentpace --version`. If that fails, tell the user the install command and stop. Do not install anything without being asked:
21
21
 
22
22
  ```text
23
- uv tool install git+https://github.com/mohamedzhioua/agentpace
23
+ uv tool install agentpace
24
24
  ```
25
25
 
26
26
  If they do not want to install anything, use [assets/portable-prompt.md](assets/portable-prompt.md) instead.
@@ -58,14 +58,14 @@ When the conversation already holds a timeline in days, weeks or months, from th
58
58
  agentpace estimate plan.json --quoted "3 months"
59
59
  ```
60
60
 
61
- The output then has a `Quoted:` line under the total, marking the quote as calendar time, as quoted and unverified. Relay that line as printed, beside the measured range. Never convert the quote into agent time, never divide or multiply it by a constant, and never compare it with the range or give a ratio between them: the only agent time is the measured range, and the human part stays on its own line. Never say what the difference between the quote and the range is, and never call it the human part: that would derive the human part from the agent time.
61
+ The output then has a `Quoted:` line under the total, marking the quote as calendar time, as quoted and unverified. Relay that line as printed, beside the measured range. Never convert the quote into agent time, never divide or multiply it by a constant, and never compare it with the range or give a ratio between them: the only agent time is the measured range, and the human part stays on its own line. Never say what the difference between the quote and the range is, and never call it the human part: that would derive the human part from the agent time. Never say what the quote counts, either: nothing has checked it.
62
62
 
63
63
  If the user wants to see later which number held, offer the same command with `--record` added. Once the work is done, `agentpace score <id> --session <ref>` compares both with what the agents actually took.
64
64
 
65
65
  ## Report the result
66
66
 
67
67
  - Lead with the plan total as a range: P50, P80 and P90, on the session clock. Recommend P80 for planning.
68
- - Pass on the session-clock line and the human part line as the CLI prints them. The first says that gaps between runs count toward the total up to the idle cap, so short waits on the user sit inside it. Unmeasured, the human part line says no allowance for review, testing, decisions and waiting between agent turns is added. Measured by `agentpace turns`, it quotes the user's own wait per human turn, from their local history. Either way, do not derive a number for it from the agent time, and do not multiply the per-turn wait by a guessed number of turns. If the user gives their own figure, repeat it and label it as theirs.
68
+ - Pass on the session-clock line and the human part line as the CLI prints them. The first says that gaps between runs count toward the total up to the idle cap, so short waits on the user sit inside it. Unmeasured, the human part line says no allowance for review, testing, decisions and waiting between agent turns is added. Measured by `agentpace turns`, it quotes the user's own wait per human turn, from their local history. Either way, do not derive a number for it from the agent time, and do not multiply the per-turn wait by a guessed number of turns. If the user gives their own figure, repeat it and label it as theirs. If they state their review capacity, such as "4 PRs a day", pass it verbatim with `--review-capacity "<figure>"`, relay the `Review capacity:` line as printed, and never turn it into a timeline.
69
69
  - Name the critical path and the effective parallelism.
70
70
  - Pass every warning on. A warning means a cell had too few runs, so that range leans on broader data.
71
71
  - Do not state a single number, even when pressed. If someone needs one figure to plan around, give the P80 and call it the P80.
@@ -13,7 +13,7 @@ compatibility: Requires Python 3.10+ and the agentpace CLI.
13
13
 
14
14
  The plan to estimate: $ARGUMENTS
15
15
 
16
- 1. Run `agentpace --version`. If it fails, show this command and stop, because installing is the user's call: `uv tool install git+https://github.com/mohamedzhioua/agentpace`
16
+ 1. Run `agentpace --version`. If it fails, show this command and stop, because installing is the user's call: `uv tool install agentpace`
17
17
  2. If the input is a JSON plan file, use it. Otherwise break the work into tasks and write a plan file with this shape:
18
18
 
19
19
  ```json
@@ -27,5 +27,5 @@ The plan to estimate: $ARGUMENTS
27
27
  ```
28
28
 
29
29
  Kinds: `feature`, `bugfix`, `refactor`, `migration`, `tests`, `docs`, `review`, `other`. Risks: `low`, `normal`, `high`.
30
- 3. Run `agentpace estimate <file>`.
31
- 4. Report the total as P50, P80 and P90, then the session-clock line and the human part line exactly as the CLI prints them, then the critical path, the effective parallelism and every warning. Do not derive a number for the human part from the agent time. Never reduce the result to one number.
30
+ 3. Run `agentpace estimate <file>`. If the conversation already holds a timeline in days, weeks or months, add `--quoted "<quote>"` in the words it was given. Only if the user stated their own review capacity, such as "4 PRs a day", add `--review-capacity "<figure>"` with their words verbatim.
31
+ 4. Report the total as P50, P80 and P90, then the `Quoted:` line as printed when there is one, then the session-clock line and the human part line exactly as the CLI prints them, and the `Review capacity:` line as printed when there is one, never turned into a timeline, then the critical path, the effective parallelism and every warning. Do not derive a number for the human part from the agent time. Never convert the quote, compare it with the range, say what the difference between them is, or say what the quote counts: it is calendar time, as quoted, and nothing has checked it. Never reduce the result to one number.
@@ -13,7 +13,7 @@ compatibility: Requires Python 3.10+ and the agentpace CLI.
13
13
 
14
14
  Input, if any: $ARGUMENTS
15
15
 
16
- 1. Run `agentpace --version`. If it fails, show this command and stop, because installing is the user's call: `uv tool install git+https://github.com/mohamedzhioua/agentpace`
16
+ 1. Run `agentpace --version`. If it fails, show this command and stop, because installing is the user's call: `uv tool install agentpace`
17
17
  2. Pick the source, and tell the user which one you are ingesting. `agentpace ingest` reads Claude Code subagent runs, the default. For another harness, add `--source` with one of `claude-code`, `codex`, `gemini-cli`, `opencode` or `cline`. It reads timestamps, model ids, token and tool counts only, and keeps rows in `~/.agentpace`. Nothing leaves this machine.
18
18
  - `gemini-cli`, `opencode` and `cline` are experimental: each adapter was built from the tool's source and is not yet verified on real sessions, and `agentpace validate` rejects their rows, so a data pull request with them stays red until the adapter is verified. Say so when you use one, and pass on the note `ingest` prints. Anyone who wants to verify one can follow [docs/verify-adapter.md](https://github.com/mohamedzhioua/agentpace/blob/main/docs/verify-adapter.md).
19
19
  3. Run `agentpace runs --last 10` and show the list.
@@ -13,7 +13,7 @@ compatibility: Requires Python 3.10+ and the agentpace CLI.
13
13
 
14
14
  Sharing is opt-in. Do nothing here unless the user asked for it.
15
15
 
16
- 1. Run `agentpace --version`. If it fails, show this command and stop, because installing is the user's call: `uv tool install git+https://github.com/mohamedzhioua/agentpace`
16
+ 1. Run `agentpace --version`. If it fails, show this command and stop, because installing is the user's call: `uv tool install agentpace`
17
17
  2. Run `agentpace share --dry-run`. It prints the exact rows and what they reveal, and writes nothing.
18
18
  3. Tell the user how many rows there are, and pass on the notice in full. Offer to show the rows.
19
19
  4. Stop and ask. Run `agentpace share --write` only after the user says yes to writing the file. It needs a clone of the agentpace repo, because the file goes in `data/runs`.
@@ -1,3 +1,3 @@
1
1
  """agentpace: real run times for coding agents, as P50/P80/P90 ranges."""
2
2
 
3
- __version__ = "0.9.2"
3
+ __version__ = "0.11.2"
@@ -39,6 +39,17 @@ def _kind_table(rows: Sequence[Row]) -> str:
39
39
  return "\n" + "\n".join(lines) + "\n"
40
40
 
41
41
 
42
+ def _not_completed_by_harness(rows: Sequence[Row]) -> str:
43
+ """Recorded runs that did not complete (failed or unknown), by harness, from the loaded rows."""
44
+ parts = []
45
+ for harness in est.outcome_summary(rows).by_harness:
46
+ missing = harness.failed + harness.unknown
47
+ parts.append(
48
+ f"{harness.harness} {missing:,} of {harness.rows:,} ({missing / harness.rows:.1%})"
49
+ )
50
+ return ", ".join(parts)
51
+
52
+
42
53
  # The timeline the README's quote example re-answers, passed as `--quoted` exactly as given.
43
54
  QUOTE_EXAMPLE = "3 months"
44
55
 
@@ -148,6 +159,7 @@ def values(
148
159
  "project_top_sessions": project_top_sessions,
149
160
  "contributor_split": contributor,
150
161
  "loss_count": str(len(report["losses"])),
162
+ "not_completed_by_harness": _not_completed_by_harness(rows),
151
163
  "kind_table": _kind_table(rows),
152
164
  "example_output": _example_output(rows, plan_path),
153
165
  "quote_example_output": _example_output(rows, quote_plan_path, QUOTE_EXAMPLE),
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
+ import dataclasses
6
7
  import errno
7
8
  import json
8
9
  import os
@@ -287,7 +288,17 @@ def _experimental_rows_note(rows: Sequence[schema.Row]) -> str | None:
287
288
  )
288
289
 
289
290
 
291
+ def _stated_figure(text: str) -> str:
292
+ """A review capacity the user states, kept verbatim: one short line, never parsed for a
293
+ number or a unit, and never used to compute anything."""
294
+ stated = text.strip()
295
+ if not stated or len(stated.splitlines()) > 1 or len(stated) > 80:
296
+ raise ValueError('--review-capacity needs one short line, such as "4 PRs a day"')
297
+ return stated
298
+
299
+
290
300
  def cmd_estimate(args: argparse.Namespace) -> int:
301
+ capacity = None if args.review_capacity is None else _stated_figure(args.review_capacity)
291
302
  text = _plan_text(args)
292
303
  try:
293
304
  document = json.loads(text)
@@ -303,6 +314,8 @@ def cmd_estimate(args: argparse.Namespace) -> int:
303
314
  if args.turns and not Path(args.turns).exists():
304
315
  raise ValueError("the turns file does not exist")
305
316
  human = _human_part(Path(args.turns) if args.turns else paths.turns_file())
317
+ if capacity is not None:
318
+ human = dataclasses.replace(human, review_capacity_stated=capacity)
306
319
  plan = estimate.estimate(model, tasks, cap, args.draws, args.seed, human)
307
320
  print(render.plan_json(plan) if args.json else render.plan_text(plan, args.quoted))
308
321
  if args.record:
@@ -663,6 +676,12 @@ def build_parser() -> argparse.ArgumentParser:
663
676
  help='a timeline someone already gave, such as "3 weeks": shown beside the range as '
664
677
  "quoted, and stored with the forecast when --record is given",
665
678
  )
679
+ estimate_cmd.add_argument(
680
+ "--review-capacity",
681
+ metavar="TEXT",
682
+ help='your own review capacity, such as "4 PRs a day": shown on its own line as stated by '
683
+ "you, never applied to the range, and stored with the forecast when --record is given",
684
+ )
666
685
  estimate_cmd.set_defaults(handler=cmd_estimate)
667
686
 
668
687
  score_cmd = commands.add_parser(