loopmath 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. {loopmath-0.1.0 → loopmath-0.2.0}/PKG-INFO +39 -12
  2. {loopmath-0.1.0 → loopmath-0.2.0}/README.md +38 -11
  3. {loopmath-0.1.0 → loopmath-0.2.0}/pyproject.toml +3 -2
  4. {loopmath-0.1.0 → loopmath-0.2.0}/spec/ocp-v0.2.schema.json +18 -18
  5. {loopmath-0.1.0 → loopmath-0.2.0}/spec/ocp-v0.schema.json +12 -12
  6. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/__init__.py +1 -1
  7. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/opencode_store.py +22 -5
  8. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/belief/__init__.py +0 -2
  9. loopmath-0.2.0/src/loopmath/belief/block.py +169 -0
  10. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/belief/commands.py +30 -7
  11. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/belief/compose.py +5 -5
  12. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/belief/design.py +163 -55
  13. loopmath-0.2.0/src/loopmath/belief/features.py +218 -0
  14. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/belief/fit.py +132 -23
  15. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/belief/forest.py +11 -5
  16. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/belief/gaussian.py +144 -57
  17. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/belief/lookahead.py +99 -41
  18. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/belief/priors.py +21 -7
  19. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/belief/state.py +219 -36
  20. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/cli.py +7 -5
  21. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/cli_graph.py +3 -4
  22. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/cli_registry.py +101 -17
  23. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/cli_support.py +2 -4
  24. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/fit_bayes.py +1 -1
  25. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/fit_masks.py +1 -1
  26. loopmath-0.2.0/src/loopmath/gitwalk.py +49 -0
  27. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/artifact_git.py +33 -8
  28. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/artifacts.py +11 -4
  29. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/bashparse.py +21 -0
  30. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/bashread_ops.py +2 -3
  31. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/bashwrite_ops.py +2 -3
  32. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/cache.py +2 -2
  33. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/codexio_parse.py +39 -37
  34. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/dataset_edges.py +1 -1
  35. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/extract.py +19 -3
  36. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler.py +1 -1
  37. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/launch.py +15 -7
  38. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/ocp_support.py +5 -3
  39. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ingest/__init__.py +50 -16
  40. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ingest/base.py +26 -5
  41. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ingest/claude_code.py +2 -1
  42. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ingest/codex.py +6 -5
  43. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ingest/codex_support.py +1 -1
  44. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ingest/ocp_convert.py +3 -0
  45. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/logmatch/__init__.py +0 -2
  46. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/logmatch/artifacts.py +1 -4
  47. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/logmatch/clip.py +2 -4
  48. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/logmatch/costs.py +4 -7
  49. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/logmatch/match.py +2 -4
  50. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/logmatch/settle.py +8 -11
  51. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/logmatch/tariff.py +0 -2
  52. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ocp/__init__.py +0 -2
  53. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ocp/canonical.py +6 -6
  54. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ocp/commands.py +3 -3
  55. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ocp/conformance.py +12 -11
  56. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ocp/contractv3.py +3 -5
  57. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ocp/emit.py +5 -5
  58. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ocp/migrate.py +4 -5
  59. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ocp/schema/ocp-v0.2.schema.json +18 -18
  60. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ocp/schema/ocp-v0.3.schema.json +29 -28
  61. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ocp/schema/ocp-v0.schema.json +12 -12
  62. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/onboard/__init__.py +0 -2
  63. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/onboard/commands.py +25 -18
  64. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/onboard/history.py +118 -16
  65. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/onboard/label.py +30 -13
  66. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/onboard/usual.py +4 -5
  67. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/output.py +6 -1
  68. loopmath-0.2.0/src/loopmath/pool.py +50 -0
  69. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/price.py +5 -5
  70. loopmath-0.2.0/src/loopmath/priors/__init__.py +113 -0
  71. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/benchmarks.py +6 -6
  72. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/commands.py +1 -1
  73. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/e0.py +3 -3
  74. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/history.py +5 -5
  75. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/ocpdoc.py +6 -7
  76. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/reduce.py +4 -4
  77. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/registry.py +2 -2
  78. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/rq1.py +6 -6
  79. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/recommend/__init__.py +0 -2
  80. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/recommend/backlog.py +15 -10
  81. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/recommend/commands.py +128 -30
  82. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/recommend/curve.py +1 -1
  83. loopmath-0.2.0/src/loopmath/recommend/engine.py +617 -0
  84. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/recommend/gain.py +10 -3
  85. loopmath-0.2.0/src/loopmath/recommend/message.py +246 -0
  86. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/recommend/receipts.py +3 -3
  87. loopmath-0.2.0/src/loopmath/recommend/search.py +1096 -0
  88. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/recommend/stats.py +1 -1
  89. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/recommend/storeread.py +44 -2
  90. loopmath-0.2.0/src/loopmath/research_defaults.py +5 -0
  91. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/share/__init__.py +0 -2
  92. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/share/commands.py +1 -3
  93. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/share/export.py +17 -17
  94. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/share/import_.py +8 -8
  95. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/skill/__init__.py +0 -2
  96. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/skill/commands.py +29 -9
  97. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/skill/doctor.py +35 -12
  98. loopmath-0.2.0/src/loopmath/skill/install.py +538 -0
  99. loopmath-0.2.0/src/loopmath/skill/skills/loopmath/SKILL.md +35 -0
  100. loopmath-0.2.0/src/loopmath/skill/skills/loopmath-import-runs/SKILL.md +63 -0
  101. loopmath-0.2.0/src/loopmath/skill/skills/loopmath-onboard/SKILL.md +81 -0
  102. loopmath-0.2.0/src/loopmath/skill/skills/loopmath-plan-task/SKILL.md +95 -0
  103. loopmath-0.2.0/src/loopmath/skill/skills/loopmath-record-run/SKILL.md +84 -0
  104. loopmath-0.2.0/src/loopmath/skill/skills/loopmath-update-fit/SKILL.md +45 -0
  105. loopmath-0.2.0/src/loopmath/skill/skills/reference.md +107 -0
  106. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/store/__init__.py +1 -1
  107. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/store/commands.py +322 -68
  108. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/store/config.py +93 -7
  109. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/store/finish.py +5 -5
  110. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/store/home.py +2 -2
  111. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/store/ids.py +3 -3
  112. loopmath-0.2.0/src/loopmath/store/record.py +464 -0
  113. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/store/runs.py +5 -2
  114. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/store/status.py +58 -2
  115. loopmath-0.2.0/src/loopmath/taskmodel.py +648 -0
  116. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/types.py +5 -5
  117. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/views/__init__.py +0 -2
  118. loopmath-0.2.0/src/loopmath/views/assets/estimates.css +50 -0
  119. loopmath-0.2.0/src/loopmath/views/assets/estimates.js +808 -0
  120. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/views/assets/graph.js +29 -3
  121. loopmath-0.2.0/src/loopmath/views/assets/plans.css +9 -0
  122. loopmath-0.2.0/src/loopmath/views/assets/plans.js +355 -0
  123. loopmath-0.2.0/src/loopmath/views/assets/results.css +18 -0
  124. loopmath-0.2.0/src/loopmath/views/assets/results.js +276 -0
  125. loopmath-0.2.0/src/loopmath/views/assets/viz.css +219 -0
  126. loopmath-0.2.0/src/loopmath/views/assets/viz.js +347 -0
  127. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/views/common.py +13 -8
  128. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/views/plans.py +8 -6
  129. loopmath-0.2.0/src/loopmath/views/posterior.py +1337 -0
  130. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/views/runs.py +42 -23
  131. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/__init__.py +0 -2
  132. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/candidates.py +82 -20
  133. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/format.py +1 -1
  134. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/ids.py +2 -2
  135. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/infer.py +9 -9
  136. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/models.py +26 -6
  137. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/ocp.py +4 -4
  138. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/shapes.py +3 -3
  139. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath.egg-info/PKG-INFO +39 -12
  140. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath.egg-info/SOURCES.txt +27 -1
  141. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_adapter_opencode.py +1 -1
  142. loopmath-0.2.0/tests/test_adapter_opencode_output_tokens.py +68 -0
  143. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_cli_graph.py +1 -1
  144. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_cli_labeler.py +1 -1
  145. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_cli_main_module.py +1 -1
  146. loopmath-0.2.0/tests/test_cli_plan_hidden.py +73 -0
  147. loopmath-0.2.0/tests/test_gitwalk.py +45 -0
  148. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_codexio.py +67 -1
  149. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_extract.py +1 -1
  150. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_labeler.py +1 -1
  151. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_launch.py +1 -1
  152. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_ocp.py +11 -1
  153. loopmath-0.2.0/tests/test_home_guard.py +51 -0
  154. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_ocp_conformance.py +4 -4
  155. loopmath-0.2.0/tests/test_output_html_path.py +36 -0
  156. loopmath-0.2.0/tests/test_pool.py +38 -0
  157. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_preview_script.py +1 -1
  158. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_price.py +2 -2
  159. loopmath-0.2.0/tests/test_taskmodel_features.py +126 -0
  160. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_types_control.py +1 -1
  161. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_types_piece_prediction.py +1 -1
  162. loopmath-0.1.0/src/loopmath/priors/__init__.py +0 -57
  163. loopmath-0.1.0/src/loopmath/recommend/engine.py +0 -330
  164. loopmath-0.1.0/src/loopmath/recommend/message.py +0 -159
  165. loopmath-0.1.0/src/loopmath/skill/SKILL.md +0 -157
  166. loopmath-0.1.0/src/loopmath/skill/install.py +0 -242
  167. loopmath-0.1.0/src/loopmath/taskmodel.py +0 -254
  168. loopmath-0.1.0/src/loopmath/views/assets/plans.css +0 -27
  169. loopmath-0.1.0/src/loopmath/views/assets/plans.js +0 -331
  170. loopmath-0.1.0/src/loopmath/views/posterior.py +0 -1737
  171. {loopmath-0.1.0 → loopmath-0.2.0}/LICENSE +0 -0
  172. {loopmath-0.1.0 → loopmath-0.2.0}/setup.cfg +0 -0
  173. {loopmath-0.1.0 → loopmath-0.2.0}/spec/ocp_conformance.py +0 -0
  174. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/__main__.py +0 -0
  175. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapter_host.py +0 -0
  176. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/__init__.py +0 -0
  177. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/atrium.py +0 -0
  178. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/base.py +0 -0
  179. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/bb.py +0 -0
  180. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/bb_support.py +0 -0
  181. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/omp.py +0 -0
  182. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/omp_facts.py +0 -0
  183. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/omp_links.py +0 -0
  184. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/omp_parse.py +0 -0
  185. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/omp_types.py +0 -0
  186. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/opencode.py +0 -0
  187. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/orca.py +0 -0
  188. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/otel_genai.py +0 -0
  189. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/otel_genai_graph.py +0 -0
  190. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/otel_genai_parse.py +0 -0
  191. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/otel_genai_types.py +0 -0
  192. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/otel_genai_usage.py +0 -0
  193. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/paseo.py +0 -0
  194. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/pi.py +0 -0
  195. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/pi_support.py +0 -0
  196. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/adapters/registry.py +0 -0
  197. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/belief/check_pymc.py +0 -0
  198. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/belief/outcome.py +0 -0
  199. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/e0/__init__.py +0 -0
  200. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/e0/arms.py +0 -0
  201. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/e0/cli_e0.py +0 -0
  202. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/e0/estimate.py +0 -0
  203. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/e0/estimate_rework.py +0 -0
  204. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/e0/figures.py +0 -0
  205. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/e0/io.py +0 -0
  206. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/e0/verb.py +0 -0
  207. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/explain.py +0 -0
  208. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/fit.py +0 -0
  209. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/fit_assembly.py +0 -0
  210. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/fit_predict.py +0 -0
  211. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/fit_pricing.py +0 -0
  212. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/fit_transfer.py +0 -0
  213. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/gaps.py +0 -0
  214. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/grade.py +0 -0
  215. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/grade_support.py +0 -0
  216. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/__init__.py +0 -0
  217. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/artifact_join.py +0 -0
  218. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/artifact_kinds.py +0 -0
  219. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/bashwrites.py +0 -0
  220. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/codexio.py +0 -0
  221. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/codexio_scan.py +0 -0
  222. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/dataset.py +0 -0
  223. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/dataset_build.py +0 -0
  224. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/dataset_core.py +0 -0
  225. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/dataset_nodes.py +0 -0
  226. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/html_common.py +0 -0
  227. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/html_costcurve.py +0 -0
  228. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/html_css.py +0 -0
  229. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/html_data.py +0 -0
  230. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/html_force.py +0 -0
  231. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/html_render.py +0 -0
  232. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/html_swimlanes.py +0 -0
  233. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_cli.py +0 -0
  234. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_common.py +0 -0
  235. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_parse.py +0 -0
  236. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_pricing.py +0 -0
  237. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_prompt.py +0 -0
  238. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_prompt_build.py +0 -0
  239. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_prompt_dispatch.py +0 -0
  240. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_prompt_evidence.py +0 -0
  241. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_prompt_node.py +0 -0
  242. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_prompt_records.py +0 -0
  243. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_prompt_result.py +0 -0
  244. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_prompt_session.py +0 -0
  245. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_prompt_tools.py +0 -0
  246. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_prompt_validation.py +0 -0
  247. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_prompt_workflow.py +0 -0
  248. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_report.py +0 -0
  249. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labeler_score.py +0 -0
  250. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/labels.py +0 -0
  251. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/launch_origin.py +0 -0
  252. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/ocp.py +0 -0
  253. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/ocp_emit.py +0 -0
  254. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/render.py +0 -0
  255. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/runfile.py +0 -0
  256. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/runfile_export.py +0 -0
  257. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/runfile_finish.py +0 -0
  258. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/scan.py +0 -0
  259. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/schema.py +0 -0
  260. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/graph/token_completeness.py +0 -0
  261. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ingest/claude_code_support.py +0 -0
  262. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ingest/ocp.py +0 -0
  263. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ingest/ocp_analysis.py +0 -0
  264. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ingest/ocp_artifact.py +0 -0
  265. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ingest/ocp_common.py +0 -0
  266. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ingest/ocp_projection.py +0 -0
  267. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/ocp/version.py +0 -0
  268. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/price_validation.py +0 -0
  269. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/prices.toml +0 -0
  270. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/benchmarks.toml +0 -0
  271. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/build.py +0 -0
  272. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/bundle/README.md +0 -0
  273. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/bundle/e0.jsonl.gz +0 -0
  274. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/bundle/manifest.json +0 -0
  275. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/bundle/rq1.jsonl.gz +0 -0
  276. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/bundle/sweep.jsonl.gz +0 -0
  277. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/show.py +0 -0
  278. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/sweep.py +0 -0
  279. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/priors/validate.py +0 -0
  280. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/receipts.py +0 -0
  281. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/report/__init__.py +0 -0
  282. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/report/html.py +0 -0
  283. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/report/terminal.py +0 -0
  284. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/report/terminal_exclusions.py +0 -0
  285. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/report/terminal_format.py +0 -0
  286. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/report/terminal_read.py +0 -0
  287. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/research_paths.py +0 -0
  288. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/scoring.py +0 -0
  289. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/scoring_comparisons.py +0 -0
  290. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/scoring_predictive.py +0 -0
  291. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/store/budget.py +0 -0
  292. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/store/fitjob.py +0 -0
  293. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/store/lock.py +0 -0
  294. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/store/report.py +0 -0
  295. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/surface.py +0 -0
  296. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/surface_bootstrap.py +0 -0
  297. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/surface_estimation.py +0 -0
  298. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/surface_frame.py +0 -0
  299. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/surface_presentation.py +0 -0
  300. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/surface_support.py +0 -0
  301. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/views/assets/README.md +0 -0
  302. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/views/assets/base.css +0 -0
  303. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/views/assets/common.js +0 -0
  304. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/views/assets/graph.css +0 -0
  305. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/views/assets/runs.js +0 -0
  306. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/catalog/best_of_n.toml +0 -0
  307. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/catalog/implement_review.toml +0 -0
  308. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/catalog/plan_implement.toml +0 -0
  309. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/catalog/plan_implement_review.toml +0 -0
  310. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/catalog/solo.toml +0 -0
  311. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/catalog/swarm.toml +0 -0
  312. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/commands.py +0 -0
  313. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/diff.py +0 -0
  314. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath/workflows/graphview.py +0 -0
  315. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath.egg-info/dependency_links.txt +0 -0
  316. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath.egg-info/entry_points.txt +0 -0
  317. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath.egg-info/requires.txt +0 -0
  318. {loopmath-0.1.0 → loopmath-0.2.0}/src/loopmath.egg-info/top_level.txt +0 -0
  319. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_adapter_atrium.py +0 -0
  320. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_adapter_bb.py +0 -0
  321. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_adapter_omp.py +0 -0
  322. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_adapter_orca.py +0 -0
  323. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_adapter_otel_genai.py +0 -0
  324. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_adapter_paseo.py +0 -0
  325. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_adapter_services.py +0 -0
  326. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_adapters.py +0 -0
  327. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_adapters_pi.py +0 -0
  328. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_arms.py +0 -0
  329. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_cli_prices.py +0 -0
  330. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_cli_startup.py +0 -0
  331. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_color_proof.py +0 -0
  332. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_duplicate_guards.py +0 -0
  333. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_estimate.py +0 -0
  334. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_explain.py +0 -0
  335. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_fit.py +0 -0
  336. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_gaps.py +0 -0
  337. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_grade.py +0 -0
  338. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_artifact_recording.py +0 -0
  339. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_artifacts.py +0 -0
  340. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_bashwrites.py +0 -0
  341. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_cache.py +0 -0
  342. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_dataset.py +0 -0
  343. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_glue.py +0 -0
  344. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_html.py +0 -0
  345. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_labeler_evidence_matrix.py +0 -0
  346. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_render.py +0 -0
  347. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_runfile.py +0 -0
  348. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_graph_schema.py +0 -0
  349. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_html.py +0 -0
  350. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_ingest.py +0 -0
  351. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_ingest_claude_code.py +0 -0
  352. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_ingest_codex.py +0 -0
  353. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_ingest_ocp.py +0 -0
  354. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_io.py +0 -0
  355. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_not_run_summary.py +0 -0
  356. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_ocp_golden.py +0 -0
  357. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_ocp_open_enums.py +0 -0
  358. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_ocp_versioning.py +0 -0
  359. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_packaging_metadata.py +0 -0
  360. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_packaging_smoke.py +0 -0
  361. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_receipts.py +0 -0
  362. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_release_paths.py +0 -0
  363. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_runfile_privacy_n1b.py +0 -0
  364. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_scoring.py +0 -0
  365. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_split_rebindings.py +0 -0
  366. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_surface.py +0 -0
  367. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_terminal.py +0 -0
  368. {loopmath-0.1.0 → loopmath-0.2.0}/tests/test_v01_scaffold.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loopmath
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Plan agent workflows, record how they went, and learn from your own Claude Code and Codex logs
5
5
  Author: QKV Labs
6
6
  License-Expression: MIT
@@ -65,17 +65,31 @@ python3 -m venv .venv
65
65
  ## First run
66
66
 
67
67
  ```sh
68
- loopmath doctor # what loopmath can see: logs, agents, store, fit, prices, skill
69
- loopmath skill install --target both # teach Claude Code and Codex how to use loopmath
68
+ loopmath skill install # teach your coding agents how to use loopmath
70
69
  ```
71
70
 
72
- `skill install` writes one `SKILL.md` for Claude Code (under `~/.claude/skills/loopmath/`) and one for Codex (under `~/.codex/skills/loopmath/`). If your Codex has no skills folder, it writes the file to `~/.codex/loopmath/SKILL.md` instead and adds a short marked block to `~/.codex/AGENTS.md` that points to it. Add `--scope project` to install into the current repository. It is safe to run again, and it rewrites only its own files. `loopmath skill uninstall` removes them.
71
+ Then ask your agent to onboard, for example "onboard loopmath". The agent runs a dry run, asks you one question (which model labels your sessions, with the cost of each option), onboards, fits, and opens the results page.
73
72
 
74
- Next, turn your history into a starting point:
73
+ `skill install` writes six skills, one per job, and a shared `reference.md` with every command and JSON field they use. An agent loads only the skill for the job at hand, and each phrase belongs to one skill:
74
+
75
+ | Skill | Ask your agent |
76
+ |---|---|
77
+ | `loopmath` | loopmath named without a job: "use loopmath", "what can loopmath do", "what next with loopmath" |
78
+ | `loopmath-onboard` | "onboard", "set up", "start" or "try" loopmath; or loopmath has no runs yet |
79
+ | `loopmath-import-runs` | OCP files (`*.ocp.json`), a folder of finished runs, or the output of an orchestrator or experiment harness that writes OCP |
80
+ | `loopmath-update-fit` | "update", "refresh" or "rerun" the fit; what loopmath learned; runs imported or recorded without a refit |
81
+ | `loopmath-plan-task` | "plan a task with loopmath"; which workflow, model or effort to use; a coding task of more than a few minutes in a repo that uses loopmath |
82
+ | `loopmath-record-run` | a task planned with loopmath is done; "record", "log" or "save" a run; both runs of a pair need a blind judge |
83
+
84
+ By default the skills go to every agent whose home exists: Claude Code (`~/.claude/skills/<name>/`, or under `$CLAUDE_CONFIG_DIR`) and Codex (`~/.codex/skills/<name>/`, or under `$CODEX_HOME`). `--target claude-code`, `codex` or `both` picks them yourself. If your Codex has no skills folder, the files go to `~/.codex/loopmath/<name>/` instead, with a short marked block in `~/.codex/AGENTS.md` that lists them. Add `--scope project` to install into the current repository (or `--dir PATH` for another one).
85
+
86
+ It is safe to run again. It records the files it wrote, with their hashes, in `.loopmath-skills.json` beside the skill folders, and it replaces or removes only those. A skill file you changed is kept, with a note. Any other file where a skill goes stops the install before it writes anything, and names the path to move aside. The single skill that 0.1 installed is replaced when it is as 0.1 wrote it. `loopmath skill show NAME` prints one skill, and `loopmath skill uninstall` removes the files it wrote. `loopmath doctor` shows what loopmath can see: logs, agents, store, fit, prices and skills.
87
+
88
+ To onboard by hand instead:
75
89
 
76
90
  ```sh
77
- loopmath onboard --labeler claude:claude-haiku-4-5 --dry-run # see what it found and what labelling costs
78
- loopmath onboard --labeler claude:claude-haiku-4-5 --yes # record it and run a first fit
91
+ loopmath onboard --dry-run # what it found, and what each labeller costs
92
+ loopmath onboard --labeler claude:claude-haiku-4-5 --yes # record it and run a first fit
79
93
  ```
80
94
 
81
95
  `onboard` reads your recent Claude Code and Codex sessions (the last 90 days unless you pass `--since`). It groups them into runs, labels each group's task type with a model you choose, names your usual workflow per task type and repository, and fits. loopmath never picks the labelling model for you. `--labeler` takes one of:
@@ -85,17 +99,28 @@ loopmath onboard --labeler claude:claude-haiku-4-5 --yes # record it and
85
99
  - `command:<cmd>`, any local command such as `command:ollama run <model>`, so nothing leaves the machine;
86
100
  - `none`, to skip labelling.
87
101
 
102
+ Reading the history uses up to 8 worker processes (one per CPU, at most 8). Set `LOOPMATH_WORKERS=1` to keep everything in one process, or another number to change the count.
103
+
88
104
  loopmath ships with a prior built from our own sweeps and experiments, so `recommend` gives an answer before you have any history. Your own runs then move it.
89
105
 
90
106
  ## The loop
91
107
 
92
- The skill tells an orchestrator agent to follow these steps. You can run the same commands by hand.
108
+ The plan and record skills take an agent through the loop in three commands:
109
+
110
+ ```sh
111
+ loopmath recommend --type bug_fix --repo acme/api --title "Fix the parser crash" --json --brief --html
112
+ loopmath run start --rec REC --choice goal --base-commit SHA --json # the harness, model and effort per piece
113
+ loopmath run record --run RUN --session UUID --verified tests=pass --json # costs from the logs, the outcome, the receipt
114
+ ```
115
+
116
+ The same loop step by step, for a tool of your own:
93
117
 
94
118
  ```sh
95
119
  # 1. Plan: classify the task, then ask.
96
120
  loopmath task-types
97
121
  loopmath recommend --type bug_fix --repo acme/api --feature size=s --feature lang=python --json
98
- # the usual workflow, the success-cost curve, alternatives, two exploration picks, a suggested pair
122
+ # the usual workflow (without one, your best recorded workflow is the reference), the success-cost curve,
123
+ # alternatives, two exploration picks, a suggested pair
99
124
 
100
125
  # 2. Record: one run per workflow you run, one attempt per agent you launch.
101
126
  loopmath run start --type bug_fix --repo acme/api --base-commit SHA --config CFG --source usual --rec REC
@@ -119,6 +144,8 @@ A pair runs the goal workflow and an exploration pick from the same base commit,
119
144
 
120
145
  A run counts as a success when it is accepted under your rule. By default that means the task's tests pass. A rule can also be a score target: `loopmath recommend ... --target 'heldout_perf>=2400'` or `--target 'runtime_s<=200'` gives the expected score and the chance of reaching the target. Set standing rules and other options with `loopmath config set KEY VALUE`, and the spending cap with `loopmath budget --usd X --period month`.
121
146
 
147
+ Runs recorded by another tool that writes OCP v0.3, such as an experiment runner, come in with `loopmath run import DIR --finish`: several files or a directory, with one refit at the end (`--no-fit` skips it). Runs in your store always count as yours, whatever source their documents name. `loopmath fit --without SOURCE` leaves out one shipped prior source, and `loopmath status` names the options of the current fit.
148
+
122
149
  ## The three views
123
150
 
124
151
  Each view is a self-contained HTML file written by a command; there is no server. The same numbers are available with `--json`.
@@ -133,9 +160,9 @@ Without a path, `--html` writes the page under `~/.loopmath/views/` and prints w
133
160
 
134
161
  | Group | Commands |
135
162
  |---|---|
136
- | Plan | `task-types`, `workflows` (list, show, validate, diff), `recommend`, `plan` |
163
+ | Plan | `task-types`, `workflows` (list, show, validate, diff), `recommend` |
137
164
  | Record | `run` (start, attempt, artifact, finish, import), `outcome`, `budget`, `config` (get, set) |
138
- | Learn | `fit`, `onboard`, `share` |
165
+ | Learn | `fit`, `onboard`, `share`, `prior` |
139
166
  | View | `runs`, `posterior`, `status`, `report`, `doctor` |
140
167
  | Skill | `skill` (install, uninstall, show) |
141
168
  | OCP | `ocp` (validate, migrate) |
@@ -230,7 +257,7 @@ loopmath graph --workspace NAME --format html --out graph.html
230
257
 
231
258
  ## More
232
259
 
233
- - [OCP, the run format](spec/OCP.md), with its [schemas](spec/) and [versioning promise](spec/VERSIONING.md)
260
+ - [OCP, the run format](spec/OCP.md): the normative note that goes with the [schemas](spec/), and the [versioning promise](spec/VERSIONING.md)
234
261
  - [Release policy](docs/release.md)
235
262
  - [Packaged model prices](src/loopmath/prices.toml)
236
263
 
@@ -27,17 +27,31 @@ python3 -m venv .venv
27
27
  ## First run
28
28
 
29
29
  ```sh
30
- loopmath doctor # what loopmath can see: logs, agents, store, fit, prices, skill
31
- loopmath skill install --target both # teach Claude Code and Codex how to use loopmath
30
+ loopmath skill install # teach your coding agents how to use loopmath
32
31
  ```
33
32
 
34
- `skill install` writes one `SKILL.md` for Claude Code (under `~/.claude/skills/loopmath/`) and one for Codex (under `~/.codex/skills/loopmath/`). If your Codex has no skills folder, it writes the file to `~/.codex/loopmath/SKILL.md` instead and adds a short marked block to `~/.codex/AGENTS.md` that points to it. Add `--scope project` to install into the current repository. It is safe to run again, and it rewrites only its own files. `loopmath skill uninstall` removes them.
33
+ Then ask your agent to onboard, for example "onboard loopmath". The agent runs a dry run, asks you one question (which model labels your sessions, with the cost of each option), onboards, fits, and opens the results page.
35
34
 
36
- Next, turn your history into a starting point:
35
+ `skill install` writes six skills, one per job, and a shared `reference.md` with every command and JSON field they use. An agent loads only the skill for the job at hand, and each phrase belongs to one skill:
36
+
37
+ | Skill | Ask your agent |
38
+ |---|---|
39
+ | `loopmath` | loopmath named without a job: "use loopmath", "what can loopmath do", "what next with loopmath" |
40
+ | `loopmath-onboard` | "onboard", "set up", "start" or "try" loopmath; or loopmath has no runs yet |
41
+ | `loopmath-import-runs` | OCP files (`*.ocp.json`), a folder of finished runs, or the output of an orchestrator or experiment harness that writes OCP |
42
+ | `loopmath-update-fit` | "update", "refresh" or "rerun" the fit; what loopmath learned; runs imported or recorded without a refit |
43
+ | `loopmath-plan-task` | "plan a task with loopmath"; which workflow, model or effort to use; a coding task of more than a few minutes in a repo that uses loopmath |
44
+ | `loopmath-record-run` | a task planned with loopmath is done; "record", "log" or "save" a run; both runs of a pair need a blind judge |
45
+
46
+ By default the skills go to every agent whose home exists: Claude Code (`~/.claude/skills/<name>/`, or under `$CLAUDE_CONFIG_DIR`) and Codex (`~/.codex/skills/<name>/`, or under `$CODEX_HOME`). `--target claude-code`, `codex` or `both` picks them yourself. If your Codex has no skills folder, the files go to `~/.codex/loopmath/<name>/` instead, with a short marked block in `~/.codex/AGENTS.md` that lists them. Add `--scope project` to install into the current repository (or `--dir PATH` for another one).
47
+
48
+ It is safe to run again. It records the files it wrote, with their hashes, in `.loopmath-skills.json` beside the skill folders, and it replaces or removes only those. A skill file you changed is kept, with a note. Any other file where a skill goes stops the install before it writes anything, and names the path to move aside. The single skill that 0.1 installed is replaced when it is as 0.1 wrote it. `loopmath skill show NAME` prints one skill, and `loopmath skill uninstall` removes the files it wrote. `loopmath doctor` shows what loopmath can see: logs, agents, store, fit, prices and skills.
49
+
50
+ To onboard by hand instead:
37
51
 
38
52
  ```sh
39
- loopmath onboard --labeler claude:claude-haiku-4-5 --dry-run # see what it found and what labelling costs
40
- loopmath onboard --labeler claude:claude-haiku-4-5 --yes # record it and run a first fit
53
+ loopmath onboard --dry-run # what it found, and what each labeller costs
54
+ loopmath onboard --labeler claude:claude-haiku-4-5 --yes # record it and run a first fit
41
55
  ```
42
56
 
43
57
  `onboard` reads your recent Claude Code and Codex sessions (the last 90 days unless you pass `--since`). It groups them into runs, labels each group's task type with a model you choose, names your usual workflow per task type and repository, and fits. loopmath never picks the labelling model for you. `--labeler` takes one of:
@@ -47,17 +61,28 @@ loopmath onboard --labeler claude:claude-haiku-4-5 --yes # record it and
47
61
  - `command:<cmd>`, any local command such as `command:ollama run <model>`, so nothing leaves the machine;
48
62
  - `none`, to skip labelling.
49
63
 
64
+ Reading the history uses up to 8 worker processes (one per CPU, at most 8). Set `LOOPMATH_WORKERS=1` to keep everything in one process, or another number to change the count.
65
+
50
66
  loopmath ships with a prior built from our own sweeps and experiments, so `recommend` gives an answer before you have any history. Your own runs then move it.
51
67
 
52
68
  ## The loop
53
69
 
54
- The skill tells an orchestrator agent to follow these steps. You can run the same commands by hand.
70
+ The plan and record skills take an agent through the loop in three commands:
71
+
72
+ ```sh
73
+ loopmath recommend --type bug_fix --repo acme/api --title "Fix the parser crash" --json --brief --html
74
+ loopmath run start --rec REC --choice goal --base-commit SHA --json # the harness, model and effort per piece
75
+ loopmath run record --run RUN --session UUID --verified tests=pass --json # costs from the logs, the outcome, the receipt
76
+ ```
77
+
78
+ The same loop step by step, for a tool of your own:
55
79
 
56
80
  ```sh
57
81
  # 1. Plan: classify the task, then ask.
58
82
  loopmath task-types
59
83
  loopmath recommend --type bug_fix --repo acme/api --feature size=s --feature lang=python --json
60
- # the usual workflow, the success-cost curve, alternatives, two exploration picks, a suggested pair
84
+ # the usual workflow (without one, your best recorded workflow is the reference), the success-cost curve,
85
+ # alternatives, two exploration picks, a suggested pair
61
86
 
62
87
  # 2. Record: one run per workflow you run, one attempt per agent you launch.
63
88
  loopmath run start --type bug_fix --repo acme/api --base-commit SHA --config CFG --source usual --rec REC
@@ -81,6 +106,8 @@ A pair runs the goal workflow and an exploration pick from the same base commit,
81
106
 
82
107
  A run counts as a success when it is accepted under your rule. By default that means the task's tests pass. A rule can also be a score target: `loopmath recommend ... --target 'heldout_perf>=2400'` or `--target 'runtime_s<=200'` gives the expected score and the chance of reaching the target. Set standing rules and other options with `loopmath config set KEY VALUE`, and the spending cap with `loopmath budget --usd X --period month`.
83
108
 
109
+ Runs recorded by another tool that writes OCP v0.3, such as an experiment runner, come in with `loopmath run import DIR --finish`: several files or a directory, with one refit at the end (`--no-fit` skips it). Runs in your store always count as yours, whatever source their documents name. `loopmath fit --without SOURCE` leaves out one shipped prior source, and `loopmath status` names the options of the current fit.
110
+
84
111
  ## The three views
85
112
 
86
113
  Each view is a self-contained HTML file written by a command; there is no server. The same numbers are available with `--json`.
@@ -95,9 +122,9 @@ Without a path, `--html` writes the page under `~/.loopmath/views/` and prints w
95
122
 
96
123
  | Group | Commands |
97
124
  |---|---|
98
- | Plan | `task-types`, `workflows` (list, show, validate, diff), `recommend`, `plan` |
125
+ | Plan | `task-types`, `workflows` (list, show, validate, diff), `recommend` |
99
126
  | Record | `run` (start, attempt, artifact, finish, import), `outcome`, `budget`, `config` (get, set) |
100
- | Learn | `fit`, `onboard`, `share` |
127
+ | Learn | `fit`, `onboard`, `share`, `prior` |
101
128
  | View | `runs`, `posterior`, `status`, `report`, `doctor` |
102
129
  | Skill | `skill` (install, uninstall, show) |
103
130
  | OCP | `ocp` (validate, migrate) |
@@ -192,7 +219,7 @@ loopmath graph --workspace NAME --format html --out graph.html
192
219
 
193
220
  ## More
194
221
 
195
- - [OCP, the run format](spec/OCP.md), with its [schemas](spec/) and [versioning promise](spec/VERSIONING.md)
222
+ - [OCP, the run format](spec/OCP.md): the normative note that goes with the [schemas](spec/), and the [versioning promise](spec/VERSIONING.md)
196
223
  - [Release policy](docs/release.md)
197
224
  - [Packaged model prices](src/loopmath/prices.toml)
198
225
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "loopmath"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Plan agent workflows, record how they went, and learn from your own Claude Code and Codex logs"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -88,7 +88,8 @@ where = ["src"]
88
88
  loopmath = [
89
89
  "prices.toml",
90
90
  "workflows/catalog/*.toml",
91
- "skill/SKILL.md",
91
+ "skill/skills/*.md",
92
+ "skill/skills/*/SKILL.md",
92
93
  "priors/*.toml",
93
94
  "priors/bundle/*",
94
95
  "views/assets/*",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "urn:ocp:run:0.2",
4
4
  "title": "OCP v0.2 run document",
5
- "description": "Orchestration Context Protocol v0.2. One JSON document describes one orchestration run: DAG topology (nodes, edges), per-node attempts with model and reasoning-effort labels, token cost records, acceptance outcomes with evidence tiers, timestamps, producer identity, and a declared privacy profile. v0.2 adds, over v0.1: an 'artifacts' entity (files one attempt wrote and others read) with 'artifact' edges; the 'launch' edge kind (one harness starting another through its CLI, distinct from in-harness 'spawn'); attempt 'origin', 'role' and 'phase' records, each with an evidence tier; cost fields for the 5-minute and 1-hour cache-creation buckets; and a 'tier' on every edge. Unknown fields are ignored by consumers (forward compatibility); the sanctioned place for producer-specific data is the 'ext' object present on every entity, with dotted-namespace keys such as 'dev.dagr.graph'. String vocabularies are open sets with recommended lists except for evidence tier, privacy profile, attempt status and outcome result. Referential rules a consumer must check that JSON Schema cannot express: edge endpoints and attempt.node name existing node ids; group.parent edges are acyclic; dep and fan_in edges form a DAG (spawn, launch and artifact edges are exempt); event refs name existing nodes/attempts; terminal attempt statuses agree with outcome.result; artifact producer/writers/consumers and origin.launched_by name existing attempts; an 'artifact' edge names an existing artifact and its from_attempt/to_attempt (both required) belong to its from/to nodes, from_attempt is the artifact's producer and to_attempt is one of its consumers; artifact.producer equals writers[0], writers is non-empty with no duplicates, a non-null n_writes is at least the number of writers and a non-null n_reads at least the number of consumers (a null count is unknown and is neither checked nor counted as satisfying); attempts need not carry origin, role or phase, but each record present carries every listed field, with null for unknowns; an attempt whose origin names a launcher has a matching 'launch' edge, and origin.external true together with a non-null launched_by is a contradiction; cache_creation_5m_tokens + cache_creation_1h_tokens equals cache_creation_tokens when all three are present; every edge carries a tier, whoever the producer is; when the producer is the extractor (producer.name 'dagr', optionally suffixed after a non-word character) every cost record has basis 'measured'; under privacy profile 'metadata_only' no free-text field may contain prompt or transcript text. Every one of these is a conformance error, not a warning. Values outside recommended vocabularies produce warnings. The reference checker for these rules ships at spec/ocp_conformance.py.",
5
+ "description": "Orchestration Context Protocol v0.2. One JSON document describes one orchestration run: DAG topology (nodes, edges), per-node attempts with model and reasoning-effort labels, token cost records, acceptance outcomes with evidence tiers, timestamps, producer identity, and a declared privacy profile. v0.2 adds, over v0.1: an 'artifacts' entity (files one attempt wrote and others read) with 'artifact' edges; the 'launch' edge kind (one harness starting another through its CLI, distinct from in-harness 'spawn'); attempt 'origin', 'role' and 'phase' records, each with an evidence tier; cost fields for the 5-minute and 1-hour cache-creation buckets; and a 'tier' on every edge. Unknown fields are ignored by consumers (forward compatibility); the sanctioned place for producer-specific data is the 'ext' object present on every entity, with dotted-namespace keys such as 'com.example.graph'. String vocabularies are open sets with recommended lists except for evidence tier, privacy profile, attempt status and outcome result. Referential rules a consumer must check that JSON Schema cannot express: edge endpoints and attempt.node name existing node ids; group.parent edges are acyclic; dep and fan_in edges form a DAG (spawn, launch and artifact edges are exempt); event refs name existing nodes/attempts; terminal attempt statuses agree with outcome.result; artifact producer/writers/consumers and origin.launched_by name existing attempts; an 'artifact' edge names an existing artifact and its from_attempt/to_attempt (both required) belong to its from/to nodes, from_attempt is the artifact's producer and to_attempt is one of its consumers; artifact.producer equals writers[0], writers is non-empty with no duplicates, a non-null n_writes is at least the number of writers and a non-null n_reads at least the number of consumers (a null count is unknown and is neither checked nor counted as satisfying); attempts need not carry origin, role or phase, but each record present carries every listed field, with null for unknowns; an attempt whose origin names a launcher has a matching 'launch' edge, and origin.external true together with a non-null launched_by is a contradiction; cache_creation_5m_tokens + cache_creation_1h_tokens equals cache_creation_tokens when all three are present; every edge carries a tier, whoever the producer is; when the producer is the extractor (producer.name 'loopmath', or 'dagr' before the rename, optionally suffixed after a non-word character) every cost record has basis 'measured'; under privacy profile 'metadata_only' no free-text field may contain prompt or transcript text. Every one of these is a conformance error, not a warning. Values outside recommended vocabularies produce warnings. The reference checker for these rules ships at spec/ocp_conformance.py.",
6
6
  "type": "object",
7
7
  "required": ["ocp", "producer", "privacy", "run", "nodes"],
8
8
  "properties": {
@@ -16,7 +16,7 @@
16
16
  "groups": {
17
17
  "type": "array",
18
18
  "items": { "$ref": "#/$defs/group" },
19
- "description": "Optional named visual/organizational scopes (contract v3 'projects'). Grouping is orthogonal to dependency structure."
19
+ "description": "Optional named visual/organizational scopes, such as projects. Grouping is orthogonal to dependency structure."
20
20
  },
21
21
  "nodes": {
22
22
  "type": "array",
@@ -63,7 +63,7 @@
63
63
  },
64
64
  "ext": {
65
65
  "type": "object",
66
- "description": "Producer-specific extension data. Keys should be dotted-namespaced, e.g. 'dev.dagr.graph', 'langgraph.thread_id'. Consumers must ignore namespaces they do not understand."
66
+ "description": "Producer-specific extension data. Keys should be dotted-namespaced, e.g. 'com.example.graph', 'langgraph.thread_id'. Consumers must ignore namespaces they do not understand."
67
67
  },
68
68
  "evidenceTier": {
69
69
  "enum": ["verified", "heuristic", "reported"],
@@ -73,11 +73,11 @@
73
73
  "type": "object",
74
74
  "required": ["name"],
75
75
  "properties": {
76
- "name": { "type": "string", "maxLength": 200, "description": "The emitting software (plugin or converter), e.g. 'ocp-from-contractv3'. The dagr extractor uses 'dagr' (optionally suffixed after a non-word character, e.g. 'dagr/graph'); that name switches on the extractor-only rule (cost.basis 'measured')." },
76
+ "name": { "type": "string", "maxLength": 200, "description": "The emitting software (plugin or converter), e.g. 'my-orchestrator-ocp'. loopmath's log extractor names itself 'loopmath' ('dagr' in documents written before its rename), optionally suffixed after a non-word character, e.g. 'loopmath/graph'; that name switches on the extractor-only rule E171 (cost.basis 'measured')." },
77
77
  "version": { "type": "string", "maxLength": 100 },
78
- "framework": { "type": "string", "maxLength": 200, "description": "The orchestration framework the run executed under, e.g. 'herdr-dagr', 'claude-code', 'langgraph', 'crewai'." },
78
+ "framework": { "type": "string", "maxLength": 200, "description": "The orchestration framework the run executed under, e.g. 'my-orchestrator', 'claude-code', 'langgraph', 'crewai'." },
79
79
  "framework_version": { "type": "string", "maxLength": 100 },
80
- "source_contract": { "type": "string", "maxLength": 200, "description": "Upstream native format this document was derived from, e.g. 'dagr/3' or 'dagr_graph/1'." },
80
+ "source_contract": { "type": "string", "maxLength": 200, "description": "Upstream native format this document was derived from, e.g. 'my-orchestrator/1.2'." },
81
81
  "emitted_at": { "$ref": "#/$defs/timestamp" },
82
82
  "capabilities": {
83
83
  "type": "object",
@@ -153,7 +153,7 @@
153
153
  "x-recommended": ["queued", "working", "review", "blocked", "done", "failed", "rejected", "canceled", "settled_unverified"],
154
154
  "$comment": "This list is recommended vocabulary; readers must accept other values.",
155
155
  "maxLength": 100,
156
- "description": "Producer's current projection over the node's attempts. Recommended vocabulary (from dagr contract v3): queued, working, review, blocked, done, failed, rejected, canceled, settled_unverified. Consumers learning from settled runs should trust attempt outcomes over this field."
156
+ "description": "Producer's current projection over the node's attempts. Recommended vocabulary: queued, working, review, blocked, done, failed, rejected, canceled, settled_unverified. Consumers learning from settled runs should trust attempt outcomes over this field."
157
157
  },
158
158
  "labels": { "$ref": "#/$defs/labels" },
159
159
  "ext": { "$ref": "#/$defs/ext" }
@@ -161,7 +161,7 @@
161
161
  },
162
162
  "edge": {
163
163
  "type": "object",
164
- "$comment": "'tier' is required on every edge of a v0.2 document, whatever its kind (dep, fan_in, spawn, launch, artifact) and whoever the producer is. EXTRACTOR-SPEC section 5, P1 amendment (b), settled 14:15 by the Analyst: attempts 2 and 4 were reviewed to opposite readings of the sentence; the stricter one stands. Its value is one of evidenceTier's three words and nothing else.",
164
+ "$comment": "'tier' is required on every edge of a v0.2 document, whatever its kind (dep, fan_in, spawn, launch, artifact) and whoever the producer is. Its value is one of evidenceTier's three words and nothing else.",
165
165
  "required": ["from", "to", "tier"],
166
166
  "properties": {
167
167
  "from": { "type": "string", "maxLength": 200, "description": "Source node id (the prerequisite / input / spawner / launcher / producer's node)." },
@@ -193,7 +193,7 @@
193
193
  "type": "object",
194
194
  "required": ["value", "tier"],
195
195
  "properties": {
196
- "value": { "type": ["string", "null"], "x-recommended": ["plan", "review", "spec", "report", "code", "test", "config", "doc", "data", "log", "other"], "$comment": "This list is recommended vocabulary; readers must accept other values.", "maxLength": 100, "description": "Open set. Recommended vocabulary: plan, review, spec, report, code, test, config, doc, data, log, other. null when the kind is unknown (amendment (b), settled 14:15); the key is always present and the tier stays required beside it." },
196
+ "value": { "type": ["string", "null"], "x-recommended": ["plan", "review", "spec", "report", "code", "test", "config", "doc", "data", "log", "other"], "$comment": "This list is recommended vocabulary; readers must accept other values.", "maxLength": 100, "description": "Open set. Recommended vocabulary: plan, review, spec, report, code, test, config, doc, data, log, other. null when the kind is unknown; the key is always present and the tier stays required beside it." },
197
197
  "tier": { "$ref": "#/$defs/evidenceTier" },
198
198
  "evidence": { "$ref": "#/$defs/shortText", "description": "What the kind was inferred from, e.g. 'path pattern' or 'heredoc first line'." },
199
199
  "ext": { "$ref": "#/$defs/ext" }
@@ -213,7 +213,7 @@
213
213
  "items": { "type": "string", "maxLength": 200 },
214
214
  "description": "Attempt ids that read the artifact after a write by another attempt, in first-read order, each listed once. May be empty (written, never read by another attempt)."
215
215
  },
216
- "first_write_at": { "anyOf": [{ "$ref": "#/$defs/timestamp" }, { "type": "null" }], "description": "Timestamp of the first write event; null when the producer cannot compute it. The key is always present (amendment 13:10 (a)): unknown is an explicit null, never an absent key." },
216
+ "first_write_at": { "anyOf": [{ "$ref": "#/$defs/timestamp" }, { "type": "null" }], "description": "Timestamp of the first write event; null when the producer cannot compute it. The key is always present: unknown is an explicit null, never an absent key." },
217
217
  "n_writes": { "type": ["integer", "null"], "minimum": 1, "description": "Number of write events across all writers; null when the producer cannot count them. A known count must be at least the number of writers; a smaller count undercounts and is a conformance error. A null count is unknown: it neither satisfies nor fails that check." },
218
218
  "n_reads": { "type": ["integer", "null"], "minimum": 0, "description": "Number of read events across all consumers; null when the producer cannot count them. A known count must be at least the number of consumers; a smaller count undercounts and is a conformance error. A null count is unknown: it neither satisfies nor fails that check." },
219
219
  "labels": { "$ref": "#/$defs/labels" },
@@ -241,7 +241,7 @@
241
241
  "description": "What the attempt did in the run. All three fields are present; evidence is null when there is nothing to cite.",
242
242
  "required": ["value", "tier", "evidence"],
243
243
  "properties": {
244
- "value": { "type": ["string", "null"], "x-recommended": ["lead", "solo", "planner", "dev", "reviewer", "cli", "subagent"], "$comment": "This list is recommended vocabulary; readers must accept other values.", "maxLength": 100, "description": "Open set. Recommended vocabulary: lead (top-level session that spawned or launched others), solo (top-level session that did not), planner, dev, reviewer, cli (launched through a CLI with no role signal), subagent. null when the role is unknown (amendment (b), settled 14:15); the key is always present and the tier stays required beside it." },
244
+ "value": { "type": ["string", "null"], "x-recommended": ["lead", "solo", "planner", "dev", "reviewer", "cli", "subagent"], "$comment": "This list is recommended vocabulary; readers must accept other values.", "maxLength": 100, "description": "Open set. Recommended vocabulary: lead (top-level session that spawned or launched others), solo (top-level session that did not), planner, dev, reviewer, cli (launched through a CLI with no role signal), subagent. null when the role is unknown; the key is always present and the tier stays required beside it." },
245
245
  "tier": { "$ref": "#/$defs/evidenceTier" },
246
246
  "evidence": { "anyOf": [{ "$ref": "#/$defs/shortText" }, { "type": "null" }], "description": "What the label rests on, e.g. \"declared type 'Plan'\" or 'launch command mentions review'; null when nothing can be cited." },
247
247
  "ext": { "$ref": "#/$defs/ext" }
@@ -252,7 +252,7 @@
252
252
  "description": "When in the run the attempt happened. All three fields are present; evidence is null when there is nothing to cite.",
253
253
  "required": ["value", "tier", "evidence"],
254
254
  "properties": {
255
- "value": { "type": ["string", "null"], "x-recommended": ["build", "post", "external"], "$comment": "This list is recommended vocabulary; readers must accept other values.", "maxLength": 100, "description": "Open set. Recommended vocabulary: build (during the lead's activity), post (after the lead ended, e.g. a post-build review), external (outside the run's workspace). null when the phase is unknown (amendment (b), settled 14:15); the key is always present and the tier stays required beside it." },
255
+ "value": { "type": ["string", "null"], "x-recommended": ["build", "post", "external"], "$comment": "This list is recommended vocabulary; readers must accept other values.", "maxLength": 100, "description": "Open set. Recommended vocabulary: build (during the lead's activity), post (after the lead ended, e.g. a post-build review), external (outside the run's workspace). null when the phase is unknown; the key is always present and the tier stays required beside it." },
256
256
  "tier": { "$ref": "#/$defs/evidenceTier" },
257
257
  "evidence": { "anyOf": [{ "$ref": "#/$defs/shortText" }, { "type": "null" }], "description": "What the label rests on; null when nothing can be cited." },
258
258
  "ext": { "$ref": "#/$defs/ext" }
@@ -261,7 +261,7 @@
261
261
  "modelRef": {
262
262
  "type": "object",
263
263
  "properties": {
264
- "raw": { "type": "string", "maxLength": 200, "description": "The label exactly as the producer knew it, always preserved (e.g. 'fable·xhigh', 'opus5.xhigh'). Canonicalization is the consumer's job; the raw label is the ground truth." },
264
+ "raw": { "type": "string", "maxLength": 200, "description": "The label exactly as the producer knew it, always preserved (e.g. 'opus5.xhigh'). A label the source wrote with the effort in it, such as 'fable·xhigh', is kept as written; a producer never builds one itself (see effort). Canonicalization is the consumer's job; the raw label is the ground truth." },
265
265
  "id": { "type": "string", "maxLength": 200, "description": "Canonical provider model id when known, e.g. 'claude-fable-5'." },
266
266
  "family": { "type": "string", "maxLength": 100, "description": "Coarse family label for grouping attempts by model family, e.g. 'fable', 'opus5', 'sol5.6'." },
267
267
  "provider": { "type": "string", "maxLength": 100, "description": "e.g. 'anthropic', 'openai'." },
@@ -278,8 +278,8 @@
278
278
  "cache_creation_tokens": { "type": "integer", "minimum": 0, "description": "Input tokens written to cache, all retention buckets together, when the provider bills them separately. When the two bucket fields are also present their sum must equal this." },
279
279
  "cache_creation_5m_tokens": { "type": "integer", "minimum": 0, "description": "Cache-creation tokens in the 5-minute retention bucket." },
280
280
  "cache_creation_1h_tokens": { "type": "integer", "minimum": 0, "description": "Cache-creation tokens in the 1-hour retention bucket, priced higher than the 5-minute bucket." },
281
- "output_tokens": { "type": "integer", "minimum": 0 },
282
- "reasoning_tokens": { "type": "integer", "minimum": 0, "description": "Thinking/reasoning tokens when the provider reports them separately from output." },
281
+ "output_tokens": { "type": "integer", "minimum": 0, "description": "Output tokens, reasoning (thinking) tokens included. When the source reports reasoning separately from output, the producer adds it in here." },
282
+ "reasoning_tokens": { "type": "integer", "minimum": 0, "description": "The reasoning (thinking) share of output_tokens, as a breakdown: output_tokens already includes these tokens, so a reader never adds the two." },
283
283
  "requests": { "type": "integer", "minimum": 0, "description": "Number of model API requests aggregated into this record." },
284
284
  "usd": { "type": "number", "minimum": 0, "description": "Billed cost in US dollars when the producer knows it. Consumers should prefer recomputing from tokens and a dated price table." },
285
285
  "basis": {
@@ -319,7 +319,7 @@
319
319
  "type": "string",
320
320
  "x-recommended": ["verified", "reported", "heuristic", "asserted"],
321
321
  "$comment": "This list is recommended vocabulary; readers must accept other values.",
322
- "description": "How the result is known (dagr contract v3 tiers). verified: mechanically checked (tests, provenance receipt). reported: the actor asserted it through a typed envelope. heuristic: inferred. asserted: bare claim. Missing evidence must be treated as 'asserted', never upgraded."
322
+ "description": "How the result is known. verified: mechanically checked (tests, provenance receipt). reported: the actor asserted it through a typed envelope. heuristic: inferred. asserted: bare claim. Missing evidence must be treated as 'asserted', never upgraded."
323
323
  },
324
324
  "via": { "type": "string", "maxLength": 200, "description": "Node id of the gate or review that produced this acceptance signal, when one did." },
325
325
  "receipt": { "$ref": "#/$defs/shortText" },
@@ -329,7 +329,7 @@
329
329
  },
330
330
  "attempt": {
331
331
  "type": "object",
332
- "$comment": "origin, role and phase are deliberately NOT in 'required'. EXTRACTOR-SPEC section 5, P1 amendment 13:10 (a), Clarified 13:40: 'Attempts are NOT required to carry origin, role or phase at all (a v0.1 attempt has none); the extractor's own output always carries all three, and P2 is where that is enforced, not the schema.' Once one of the three records is present, every field listed for it is required inside that record (see the origin, role and phase definitions).",
332
+ "$comment": "origin, role and phase are deliberately NOT in 'required': an attempt need not carry origin, role or phase at all (a v0.1 attempt has none); the extractor's own output always carries all three, and the extractor, not the schema, enforces that. Once one of the three records is present, every field listed for it is required inside that record (see the origin, role and phase definitions).",
333
333
  "required": ["id", "node", "status"],
334
334
  "properties": {
335
335
  "id": { "type": "string", "minLength": 1, "maxLength": 200, "description": "Globally unique within the document." },
@@ -338,7 +338,7 @@
338
338
  "actor": { "type": "string", "maxLength": 200, "description": "Logical agent/role that ran the attempt (not a volatile runtime locator)." },
339
339
  "harness": { "type": "string", "maxLength": 100, "description": "The agent harness that ran the attempt, e.g. 'claude-code', 'codex'." },
340
340
  "model": { "$ref": "#/$defs/modelRef" },
341
- "effort": { "type": "string", "maxLength": 100, "description": "Reasoning-effort label as configured, e.g. low, medium, high, xhigh, max. Open set; producers must not fold it into the model label." },
341
+ "effort": { "type": "string", "maxLength": 100, "description": "Reasoning-effort label as configured, e.g. low, medium, high, xhigh, max. Open set. Producers must not fold it into the model label: a producer never builds a fused label such as 'fable·xhigh' itself. When the source already wrote one, model.raw keeps it as written, and effort known separately goes here." },
342
342
  "cause": { "$ref": "#/$defs/cause" },
343
343
  "origin": { "$ref": "#/$defs/origin", "description": "How this attempt came to run: which scanned attempt launched it through a CLI, in which workspace, or that an unscanned program did." },
344
344
  "role": { "$ref": "#/$defs/role", "description": "What part the attempt played in the run (lead, planner, dev, reviewer, ...), with the tier and evidence the label rests on." },
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "urn:ocp:run:0.1",
4
4
  "title": "OCP v0 run document",
5
- "description": "Orchestration Context Protocol v0. One JSON document describes one orchestration run: DAG topology (nodes, edges), per-node attempts with model and reasoning-effort labels, token cost records, acceptance outcomes with evidence tiers, timestamps, producer identity, and a declared privacy profile. Unknown fields are ignored by consumers (forward compatibility); the sanctioned place for producer-specific data is the 'ext' object present on every entity, with dotted-namespace keys such as 'dev.dagr.policy'. Referential rules a consumer must check that JSON Schema cannot express: edge endpoints and attempt.node name existing node ids, group.parent edges are acyclic, dep and fan_in edges form a DAG, event refs name existing nodes/attempts, terminal attempt statuses agree with outcome.result, and under privacy profile 'metadata_only' no free-text field may contain prompt or transcript text. A reference checker for these rules ships at spec/ocp_conformance.py.",
5
+ "description": "Orchestration Context Protocol v0. One JSON document describes one orchestration run: DAG topology (nodes, edges), per-node attempts with model and reasoning-effort labels, token cost records, acceptance outcomes with evidence tiers, timestamps, producer identity, and a declared privacy profile. Unknown fields are ignored by consumers (forward compatibility); the sanctioned place for producer-specific data is the 'ext' object present on every entity, with dotted-namespace keys such as 'com.example.policy'. Referential rules a consumer must check that JSON Schema cannot express: edge endpoints and attempt.node name existing node ids, group.parent edges are acyclic, dep and fan_in edges form a DAG, event refs name existing nodes/attempts, terminal attempt statuses agree with outcome.result, and under privacy profile 'metadata_only' no free-text field may contain prompt or transcript text. A reference checker for these rules ships at spec/ocp_conformance.py.",
6
6
  "type": "object",
7
7
  "required": ["ocp", "producer", "privacy", "run", "nodes"],
8
8
  "properties": {
@@ -16,7 +16,7 @@
16
16
  "groups": {
17
17
  "type": "array",
18
18
  "items": { "$ref": "#/$defs/group" },
19
- "description": "Optional named visual/organizational scopes (contract v3 'projects'). Grouping is orthogonal to dependency structure."
19
+ "description": "Optional named visual/organizational scopes, such as projects. Grouping is orthogonal to dependency structure."
20
20
  },
21
21
  "nodes": {
22
22
  "type": "array",
@@ -58,17 +58,17 @@
58
58
  },
59
59
  "ext": {
60
60
  "type": "object",
61
- "description": "Producer-specific extension data. Keys should be dotted-namespaced, e.g. 'dev.dagr.policy', 'langgraph.thread_id'. Consumers must ignore namespaces they do not understand."
61
+ "description": "Producer-specific extension data. Keys should be dotted-namespaced, e.g. 'com.example.policy', 'langgraph.thread_id'. Consumers must ignore namespaces they do not understand."
62
62
  },
63
63
  "producer": {
64
64
  "type": "object",
65
65
  "required": ["name"],
66
66
  "properties": {
67
- "name": { "type": "string", "maxLength": 200, "description": "The emitting software (plugin or converter), e.g. 'ocp-from-contractv3'." },
67
+ "name": { "type": "string", "maxLength": 200, "description": "The emitting software (plugin or converter), e.g. 'my-orchestrator-ocp'." },
68
68
  "version": { "type": "string", "maxLength": 100 },
69
- "framework": { "type": "string", "maxLength": 200, "description": "The orchestration framework the run executed under, e.g. 'herdr-dagr', 'langgraph', 'crewai'." },
69
+ "framework": { "type": "string", "maxLength": 200, "description": "The orchestration framework the run executed under, e.g. 'my-orchestrator', 'langgraph', 'crewai'." },
70
70
  "framework_version": { "type": "string", "maxLength": 100 },
71
- "source_contract": { "type": "string", "maxLength": 200, "description": "Upstream native format this document was derived from, e.g. 'dagr/3'." },
71
+ "source_contract": { "type": "string", "maxLength": 200, "description": "Upstream native format this document was derived from, e.g. 'my-orchestrator/1.2'." },
72
72
  "emitted_at": { "$ref": "#/$defs/timestamp" },
73
73
  "ext": { "$ref": "#/$defs/ext" }
74
74
  }
@@ -123,7 +123,7 @@
123
123
  "state": {
124
124
  "type": "string",
125
125
  "maxLength": 100,
126
- "description": "Producer's current projection over the node's attempts. Recommended vocabulary (from dagr contract v3): queued, working, review, blocked, done, failed, rejected, canceled, settled_unverified. Consumers learning from settled runs should trust attempt outcomes over this field."
126
+ "description": "Producer's current projection over the node's attempts. Recommended vocabulary: queued, working, review, blocked, done, failed, rejected, canceled, settled_unverified. Consumers learning from settled runs should trust attempt outcomes over this field."
127
127
  },
128
128
  "labels": { "$ref": "#/$defs/labels" },
129
129
  "ext": { "$ref": "#/$defs/ext" }
@@ -146,7 +146,7 @@
146
146
  "modelRef": {
147
147
  "type": "object",
148
148
  "properties": {
149
- "raw": { "type": "string", "maxLength": 200, "description": "The label exactly as the producer knew it, always preserved (e.g. 'fable·xhigh', 'opus5.xhigh'). Canonicalization is the consumer's job; the raw label is the ground truth." },
149
+ "raw": { "type": "string", "maxLength": 200, "description": "The label exactly as the producer knew it, always preserved (e.g. 'opus5.xhigh'). A label the source wrote with the effort in it, such as 'fable·xhigh', is kept as written; a producer never builds one itself (see effort). Canonicalization is the consumer's job; the raw label is the ground truth." },
150
150
  "id": { "type": "string", "maxLength": 200, "description": "Canonical provider model id when known, e.g. 'claude-fable-5'." },
151
151
  "family": { "type": "string", "maxLength": 100, "description": "Coarse family label for arm grouping, e.g. 'fable', 'opus5', 'sol5.6'." },
152
152
  "provider": { "type": "string", "maxLength": 100, "description": "e.g. 'anthropic', 'openai'." },
@@ -160,8 +160,8 @@
160
160
  "input_tokens": { "type": "integer", "minimum": 0, "description": "Uncached input tokens." },
161
161
  "cached_input_tokens": { "type": "integer", "minimum": 0, "description": "Input tokens served from cache (cache reads)." },
162
162
  "cache_creation_tokens": { "type": "integer", "minimum": 0, "description": "Input tokens written to cache, when the provider bills them separately." },
163
- "output_tokens": { "type": "integer", "minimum": 0 },
164
- "reasoning_tokens": { "type": "integer", "minimum": 0, "description": "Thinking/reasoning tokens when the provider reports them separately from output." },
163
+ "output_tokens": { "type": "integer", "minimum": 0, "description": "Output tokens, reasoning (thinking) tokens included. When the source reports reasoning separately from output, the producer adds it in here." },
164
+ "reasoning_tokens": { "type": "integer", "minimum": 0, "description": "The reasoning (thinking) share of output_tokens, as a breakdown: output_tokens already includes these tokens, so a reader never adds the two." },
165
165
  "requests": { "type": "integer", "minimum": 0, "description": "Number of model API requests aggregated into this record." },
166
166
  "usd": { "type": "number", "minimum": 0, "description": "Billed cost in US dollars when the producer knows it. Consumers should prefer recomputing from tokens and a dated price table." },
167
167
  "basis": {
@@ -195,7 +195,7 @@
195
195
  },
196
196
  "evidence": {
197
197
  "enum": ["verified", "reported", "heuristic", "asserted"],
198
- "description": "How the result is known (dagr contract v3 tiers). verified: mechanically checked (tests, provenance receipt). reported: the actor asserted it through a typed envelope. heuristic: inferred. asserted: bare claim. Missing evidence must be treated as 'asserted', never upgraded."
198
+ "description": "How the result is known. verified: mechanically checked (tests, provenance receipt). reported: the actor asserted it through a typed envelope. heuristic: inferred. asserted: bare claim. Missing evidence must be treated as 'asserted', never upgraded."
199
199
  },
200
200
  "via": { "type": "string", "maxLength": 200, "description": "Node id of the gate or review that produced this acceptance signal, when one did." },
201
201
  "receipt": { "$ref": "#/$defs/shortText" },
@@ -212,7 +212,7 @@
212
212
  "n": { "type": "integer", "minimum": 1, "description": "1-based ordinal within the node." },
213
213
  "actor": { "type": "string", "maxLength": 200, "description": "Logical agent/role that ran the attempt (not a volatile runtime locator)." },
214
214
  "model": { "$ref": "#/$defs/modelRef" },
215
- "effort": { "type": "string", "maxLength": 100, "description": "Reasoning-effort label as configured, e.g. low, medium, high, xhigh, max. Open set; producers must not fold it into the model label." },
215
+ "effort": { "type": "string", "maxLength": 100, "description": "Reasoning-effort label as configured, e.g. low, medium, high, xhigh, max. Open set. Producers must not fold it into the model label: a producer never builds a fused label such as 'fable·xhigh' itself. When the source already wrote one, model.raw keeps it as written, and effort known separately goes here." },
216
216
  "cause": { "$ref": "#/$defs/cause" },
217
217
  "status": {
218
218
  "enum": ["queued", "working", "done", "failed", "rejected", "canceled", "settled_unverified", "lost"],
@@ -5,4 +5,4 @@ The command line (`loopmath`, or its alias `loop`) is the interface; see the
5
5
  README and `loopmath --help`.
6
6
  """
7
7
 
8
- __version__ = "0.1.0"
8
+ __version__ = "0.2.0"
@@ -116,6 +116,24 @@ def _nested_nonnegative_int(value: Any, path: tuple[str, ...]) -> int | None:
116
116
  return current
117
117
 
118
118
 
119
+ def _message_tokens(tokens: Any, output_name: str) -> int | None:
120
+ """One assistant message's count for an OCP cost field, or None when unknown.
121
+
122
+ OCP's output_tokens includes reasoning, with reasoning_tokens as a breakdown.
123
+ OpenCode stores them apart: 1.17.11's session usage keeps output as
124
+ outputTokens - reasoningTokens and reasoning on its own, and its stats command
125
+ adds the two back. So output_tokens is output + reasoning here; a message with
126
+ no reasoning count adds 0.
127
+ """
128
+ value = _nested_nonnegative_int(tokens, _TOKEN_FIELDS[output_name])
129
+ if output_name != "output_tokens" or value is None:
130
+ return value
131
+ if _as_mapping(tokens).get("reasoning") is None:
132
+ return value
133
+ reasoning = _nested_nonnegative_int(tokens, ("reasoning",))
134
+ return None if reasoning is None else value + reasoning
135
+
136
+
119
137
  def _nonnegative_number(value: Any) -> int | float | None:
120
138
  if (
121
139
  isinstance(value, bool)
@@ -309,10 +327,9 @@ def _cost_record(
309
327
  ) -> tuple[dict[str, Any], dict[str, int]]:
310
328
  cost: dict[str, Any] = {"requests": len(assistants), "basis": "measured"}
311
329
  incomplete: dict[str, int] = {}
312
- for output_name, source_path in _TOKEN_FIELDS.items():
330
+ for output_name in _TOKEN_FIELDS:
313
331
  values = [
314
- _nested_nonnegative_int(item.get("tokens"), source_path)
315
- for item in assistants
332
+ _message_tokens(item.get("tokens"), output_name) for item in assistants
316
333
  ]
317
334
  if all(value is not None for value in values):
318
335
  cost[output_name] = sum(value for value in values if value is not None)
@@ -352,9 +369,9 @@ def _message_models(
352
369
  ),
353
370
  "requests": len(messages),
354
371
  }
355
- for output_name, source_path in _TOKEN_FIELDS.items():
372
+ for output_name in _TOKEN_FIELDS:
356
373
  values = [
357
- _nested_nonnegative_int(message.get("tokens"), source_path)
374
+ _message_tokens(message.get("tokens"), output_name)
358
375
  for message in messages
359
376
  ]
360
377
  if all(value is not None for value in values):
@@ -1,6 +1,4 @@
1
1
  """The belief model: grouping forest, cost, gate, success and score heads, Gaussian engine, look-ahead (spec 04).
2
-
3
- Owner: lane 05. Spec: design/0.1/. Stub written by the scaffold.
4
2
  """
5
3
 
6
4
  from __future__ import annotations