tuningfork-lab 0.6.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 (601) hide show
  1. tuningfork_lab-0.6.0/.gitattributes +3 -0
  2. tuningfork_lab-0.6.0/.github/workflows/tests.yml +35 -0
  3. tuningfork_lab-0.6.0/.gitignore +19 -0
  4. tuningfork_lab-0.6.0/CHANGELOG.md +566 -0
  5. tuningfork_lab-0.6.0/CLAUDE.md +722 -0
  6. tuningfork_lab-0.6.0/CONTRIBUTING.md +52 -0
  7. tuningfork_lab-0.6.0/LICENSE +201 -0
  8. tuningfork_lab-0.6.0/NOTICE +23 -0
  9. tuningfork_lab-0.6.0/PKG-INFO +315 -0
  10. tuningfork_lab-0.6.0/README.md +293 -0
  11. tuningfork_lab-0.6.0/SECURITY.md +43 -0
  12. tuningfork_lab-0.6.0/benchmark/architecture-diagram/LICENSE-hermes +21 -0
  13. tuningfork_lab-0.6.0/benchmark/architecture-diagram/architecture-diagram/SKILL.md +148 -0
  14. tuningfork_lab-0.6.0/benchmark/architecture-diagram/architecture-diagram/templates/template.html +319 -0
  15. tuningfork_lab-0.6.0/benchmark/architecture-diagram/checks/diagram.py +331 -0
  16. tuningfork_lab-0.6.0/benchmark/architecture-diagram/evals.json +24 -0
  17. tuningfork_lab-0.6.0/benchmark/driver/run.py +1157 -0
  18. tuningfork_lab-0.6.0/docs/adapters/TEMPLATE.md +115 -0
  19. tuningfork_lab-0.6.0/docs/adapters/claude-code.md +209 -0
  20. tuningfork_lab-0.6.0/docs/adapters/hermes.md +321 -0
  21. tuningfork_lab-0.6.0/docs/agents.md +197 -0
  22. tuningfork_lab-0.6.0/docs/commands.md +616 -0
  23. tuningfork_lab-0.6.0/docs/images/README.md +109 -0
  24. tuningfork_lab-0.6.0/docs/images/hero-dark.svg +168 -0
  25. tuningfork_lab-0.6.0/docs/images/hero-light.svg +168 -0
  26. tuningfork_lab-0.6.0/docs/images/hero.py +308 -0
  27. tuningfork_lab-0.6.0/docs/images/shots.mjs +447 -0
  28. tuningfork_lab-0.6.0/docs/images/ui-ab.png +0 -0
  29. tuningfork_lab-0.6.0/docs/images/ui-demo.gif +0 -0
  30. tuningfork_lab-0.6.0/docs/images/ui-divergence.png +0 -0
  31. tuningfork_lab-0.6.0/docs/images/ui-guide.png +0 -0
  32. tuningfork_lab-0.6.0/docs/images/ui-live.png +0 -0
  33. tuningfork_lab-0.6.0/docs/images/ui-refine.png +0 -0
  34. tuningfork_lab-0.6.0/docs/images/ui-run.png +0 -0
  35. tuningfork_lab-0.6.0/docs/images/ui-scenario.png +0 -0
  36. tuningfork_lab-0.6.0/docs/images/ui-tokens.png +0 -0
  37. tuningfork_lab-0.6.0/docs/setup.md +428 -0
  38. tuningfork_lab-0.6.0/docs/web-ui.md +46 -0
  39. tuningfork_lab-0.6.0/examples/README.md +2702 -0
  40. tuningfork_lab-0.6.0/examples/find-the-file/README.md +429 -0
  41. tuningfork_lab-0.6.0/examples/find-the-file/evals.json +33 -0
  42. tuningfork_lab-0.6.0/examples/find-the-file/fix/SKILL.md +29 -0
  43. tuningfork_lab-0.6.0/examples/find-the-file/tally-report/SKILL.md +29 -0
  44. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/LICENSE-hermes +21 -0
  45. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/README.md +200 -0
  46. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/diagrams/after-e4b3bec5.html +307 -0
  47. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/diagrams/after-e4b3bec5.png +0 -0
  48. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/diagrams/before-0fa851af.html +309 -0
  49. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/diagrams/before-0fa851af.png +0 -0
  50. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/diffs/H.diff +69 -0
  51. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/diffs/T.diff +33 -0
  52. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/diffs/X.diff +27 -0
  53. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/h-added/references/common-patterns.md +36 -0
  54. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/h-added/references/layout-patterns.md +34 -0
  55. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/h-added/references/service-mapping.md +19 -0
  56. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/h-added/templates/aws-web-app-layout.html +225 -0
  57. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/h-added/templates/aws-web-app-standard.html +216 -0
  58. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/h-chain.md +48 -0
  59. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/results.json +339 -0
  60. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/t-session.md +54 -0
  61. tuningfork_lab-0.6.0/examples/hermes-architecture-diagram/evidence/task-words.md +28 -0
  62. tuningfork_lab-0.6.0/examples/hermes-design-md/LICENSE-hermes +21 -0
  63. tuningfork_lab-0.6.0/examples/hermes-design-md/README.md +638 -0
  64. tuningfork_lab-0.6.0/examples/hermes-design-md/checks/design.py +114 -0
  65. tuningfork_lab-0.6.0/examples/hermes-design-md/design-md/SKILL.md +199 -0
  66. tuningfork_lab-0.6.0/examples/hermes-design-md/design-md/templates/starter.md +99 -0
  67. tuningfork_lab-0.6.0/examples/hermes-design-md/evals.json +43 -0
  68. tuningfork_lab-0.6.0/examples/hermes-design-md/fix/SKILL-section.md +204 -0
  69. tuningfork_lab-0.6.0/examples/hermes-design-md/fix/SKILL.md +201 -0
  70. tuningfork_lab-0.6.0/examples/tally-report/SKILL.md +16 -0
  71. tuningfork_lab-0.6.0/examples/tally-report.evals.json +54 -0
  72. tuningfork_lab-0.6.0/examples/variants/tally-report/SKILL.v2.md +29 -0
  73. tuningfork_lab-0.6.0/pyproject.toml +93 -0
  74. tuningfork_lab-0.6.0/scripts/tuningfork +33 -0
  75. tuningfork_lab-0.6.0/scripts/tuningfork.ps1 +35 -0
  76. tuningfork_lab-0.6.0/src/tuningfork/__init__.py +3 -0
  77. tuningfork_lab-0.6.0/src/tuningfork/__main__.py +6 -0
  78. tuningfork_lab-0.6.0/src/tuningfork/access.py +129 -0
  79. tuningfork_lab-0.6.0/src/tuningfork/analysis/__init__.py +53 -0
  80. tuningfork_lab-0.6.0/src/tuningfork/analysis/conditions.py +149 -0
  81. tuningfork_lab-0.6.0/src/tuningfork/analysis/distribution.py +511 -0
  82. tuningfork_lab-0.6.0/src/tuningfork/analysis/divergence.py +177 -0
  83. tuningfork_lab-0.6.0/src/tuningfork/analysis/forking.py +304 -0
  84. tuningfork_lab-0.6.0/src/tuningfork/analysis/normalize.py +423 -0
  85. tuningfork_lab-0.6.0/src/tuningfork/analysis/reasoning.py +87 -0
  86. tuningfork_lab-0.6.0/src/tuningfork/analysis/screen.py +300 -0
  87. tuningfork_lab-0.6.0/src/tuningfork/analysis/stats.py +171 -0
  88. tuningfork_lab-0.6.0/src/tuningfork/analysis/tokens.py +623 -0
  89. tuningfork_lab-0.6.0/src/tuningfork/analysis/validation.py +323 -0
  90. tuningfork_lab-0.6.0/src/tuningfork/api/__init__.py +6 -0
  91. tuningfork_lab-0.6.0/src/tuningfork/api/connections.py +183 -0
  92. tuningfork_lab-0.6.0/src/tuningfork/api/control.py +179 -0
  93. tuningfork_lab-0.6.0/src/tuningfork/api/data.py +429 -0
  94. tuningfork_lab-0.6.0/src/tuningfork/api/guard.py +218 -0
  95. tuningfork_lab-0.6.0/src/tuningfork/api/imports.py +159 -0
  96. tuningfork_lab-0.6.0/src/tuningfork/api/jobs.py +118 -0
  97. tuningfork_lab-0.6.0/src/tuningfork/api/refinements.py +214 -0
  98. tuningfork_lab-0.6.0/src/tuningfork/api/scenarios.py +158 -0
  99. tuningfork_lab-0.6.0/src/tuningfork/api/skills.py +130 -0
  100. tuningfork_lab-0.6.0/src/tuningfork/api/trash.py +87 -0
  101. tuningfork_lab-0.6.0/src/tuningfork/backends/__init__.py +24 -0
  102. tuningfork_lab-0.6.0/src/tuningfork/backends/base.py +192 -0
  103. tuningfork_lab-0.6.0/src/tuningfork/backends/formats/__init__.py +47 -0
  104. tuningfork_lab-0.6.0/src/tuningfork/backends/formats/base.py +79 -0
  105. tuningfork_lab-0.6.0/src/tuningfork/backends/formats/gemma4.py +258 -0
  106. tuningfork_lab-0.6.0/src/tuningfork/backends/formats/qwen3_coder.py +506 -0
  107. tuningfork_lab-0.6.0/src/tuningfork/backends/formats/qwen_json.py +412 -0
  108. tuningfork_lab-0.6.0/src/tuningfork/backends/llamacpp/__init__.py +108 -0
  109. tuningfork_lab-0.6.0/src/tuningfork/backends/llamacpp/anthropic.py +149 -0
  110. tuningfork_lab-0.6.0/src/tuningfork/backends/llamacpp/raw.py +272 -0
  111. tuningfork_lab-0.6.0/src/tuningfork/backends/probe.py +844 -0
  112. tuningfork_lab-0.6.0/src/tuningfork/backends/raw.py +103 -0
  113. tuningfork_lab-0.6.0/src/tuningfork/breakpoints/__init__.py +27 -0
  114. tuningfork_lab-0.6.0/src/tuningfork/breakpoints/conditions.py +369 -0
  115. tuningfork_lab-0.6.0/src/tuningfork/breakpoints/engine.py +441 -0
  116. tuningfork_lab-0.6.0/src/tuningfork/breakpoints/events.py +71 -0
  117. tuningfork_lab-0.6.0/src/tuningfork/cli.py +5210 -0
  118. tuningfork_lab-0.6.0/src/tuningfork/config.py +747 -0
  119. tuningfork_lab-0.6.0/src/tuningfork/control.py +334 -0
  120. tuningfork_lab-0.6.0/src/tuningfork/demo/__init__.py +7 -0
  121. tuningfork_lab-0.6.0/src/tuningfork/demo/agent.py +137 -0
  122. tuningfork_lab-0.6.0/src/tuningfork/demo/lab.py +523 -0
  123. tuningfork_lab-0.6.0/src/tuningfork/demo/mock.py +434 -0
  124. tuningfork_lab-0.6.0/src/tuningfork/demo/task.py +123 -0
  125. tuningfork_lab-0.6.0/src/tuningfork/demo/toy.py +309 -0
  126. tuningfork_lab-0.6.0/src/tuningfork/endpoints.py +213 -0
  127. tuningfork_lab-0.6.0/src/tuningfork/experiment/__init__.py +55 -0
  128. tuningfork_lab-0.6.0/src/tuningfork/experiment/forking.py +253 -0
  129. tuningfork_lab-0.6.0/src/tuningfork/experiment/sampling.py +926 -0
  130. tuningfork_lab-0.6.0/src/tuningfork/experiment/scoring.py +254 -0
  131. tuningfork_lab-0.6.0/src/tuningfork/experiment/skill.py +448 -0
  132. tuningfork_lab-0.6.0/src/tuningfork/harness/__init__.py +31 -0
  133. tuningfork_lab-0.6.0/src/tuningfork/harness/base.py +194 -0
  134. tuningfork_lab-0.6.0/src/tuningfork/harness/claude_code.py +314 -0
  135. tuningfork_lab-0.6.0/src/tuningfork/harness/hermes.py +599 -0
  136. tuningfork_lab-0.6.0/src/tuningfork/jobs.py +571 -0
  137. tuningfork_lab-0.6.0/src/tuningfork/launches.py +189 -0
  138. tuningfork_lab-0.6.0/src/tuningfork/mcp/__init__.py +10 -0
  139. tuningfork_lab-0.6.0/src/tuningfork/mcp/backend.py +167 -0
  140. tuningfork_lab-0.6.0/src/tuningfork/mcp/http.py +100 -0
  141. tuningfork_lab-0.6.0/src/tuningfork/mcp/schema.py +164 -0
  142. tuningfork_lab-0.6.0/src/tuningfork/mcp/server.py +414 -0
  143. tuningfork_lab-0.6.0/src/tuningfork/mcp/stdio.py +98 -0
  144. tuningfork_lab-0.6.0/src/tuningfork/mcp/tools.py +1591 -0
  145. tuningfork_lab-0.6.0/src/tuningfork/ops/__init__.py +36 -0
  146. tuningfork_lab-0.6.0/src/tuningfork/ops/addskill.py +396 -0
  147. tuningfork_lab-0.6.0/src/tuningfork/ops/approve.py +619 -0
  148. tuningfork_lab-0.6.0/src/tuningfork/ops/batch.py +487 -0
  149. tuningfork_lab-0.6.0/src/tuningfork/ops/catalog.py +985 -0
  150. tuningfork_lab-0.6.0/src/tuningfork/ops/chain.py +501 -0
  151. tuningfork_lab-0.6.0/src/tuningfork/ops/checks.py +461 -0
  152. tuningfork_lab-0.6.0/src/tuningfork/ops/common.py +319 -0
  153. tuningfork_lab-0.6.0/src/tuningfork/ops/compact.py +1198 -0
  154. tuningfork_lab-0.6.0/src/tuningfork/ops/connections.py +506 -0
  155. tuningfork_lab-0.6.0/src/tuningfork/ops/diff.py +339 -0
  156. tuningfork_lab-0.6.0/src/tuningfork/ops/experiments.py +922 -0
  157. tuningfork_lab-0.6.0/src/tuningfork/ops/forking.py +317 -0
  158. tuningfork_lab-0.6.0/src/tuningfork/ops/forks.py +707 -0
  159. tuningfork_lab-0.6.0/src/tuningfork/ops/glossary.py +351 -0
  160. tuningfork_lab-0.6.0/src/tuningfork/ops/holds.py +271 -0
  161. tuningfork_lab-0.6.0/src/tuningfork/ops/imports.py +1485 -0
  162. tuningfork_lab-0.6.0/src/tuningfork/ops/inspect.py +978 -0
  163. tuningfork_lab-0.6.0/src/tuningfork/ops/launch.py +80 -0
  164. tuningfork_lab-0.6.0/src/tuningfork/ops/packet.py +270 -0
  165. tuningfork_lab-0.6.0/src/tuningfork/ops/private.py +245 -0
  166. tuningfork_lab-0.6.0/src/tuningfork/ops/proposer.py +433 -0
  167. tuningfork_lab-0.6.0/src/tuningfork/ops/refine.py +796 -0
  168. tuningfork_lab-0.6.0/src/tuningfork/ops/scenarios.py +503 -0
  169. tuningfork_lab-0.6.0/src/tuningfork/ops/screen.py +910 -0
  170. tuningfork_lab-0.6.0/src/tuningfork/ops/skills.py +539 -0
  171. tuningfork_lab-0.6.0/src/tuningfork/ops/spend.py +209 -0
  172. tuningfork_lab-0.6.0/src/tuningfork/ops/tally.py +338 -0
  173. tuningfork_lab-0.6.0/src/tuningfork/ops/targets.py +251 -0
  174. tuningfork_lab-0.6.0/src/tuningfork/ops/timeline.py +732 -0
  175. tuningfork_lab-0.6.0/src/tuningfork/ops/tokens.py +335 -0
  176. tuningfork_lab-0.6.0/src/tuningfork/ops/trash.py +606 -0
  177. tuningfork_lab-0.6.0/src/tuningfork/ops/validate.py +486 -0
  178. tuningfork_lab-0.6.0/src/tuningfork/ops/variants.py +134 -0
  179. tuningfork_lab-0.6.0/src/tuningfork/ops/where.py +592 -0
  180. tuningfork_lab-0.6.0/src/tuningfork/owners.py +50 -0
  181. tuningfork_lab-0.6.0/src/tuningfork/paths.py +160 -0
  182. tuningfork_lab-0.6.0/src/tuningfork/proxy/__init__.py +1 -0
  183. tuningfork_lab-0.6.0/src/tuningfork/proxy/app.py +988 -0
  184. tuningfork_lab-0.6.0/src/tuningfork/proxy/held.py +446 -0
  185. tuningfork_lab-0.6.0/src/tuningfork/proxy/launches.py +210 -0
  186. tuningfork_lab-0.6.0/src/tuningfork/proxy/local.py +148 -0
  187. tuningfork_lab-0.6.0/src/tuningfork/proxy/recorder.py +386 -0
  188. tuningfork_lab-0.6.0/src/tuningfork/proxy/replay.py +99 -0
  189. tuningfork_lab-0.6.0/src/tuningfork/proxy/sse.py +346 -0
  190. tuningfork_lab-0.6.0/src/tuningfork/replay.py +490 -0
  191. tuningfork_lab-0.6.0/src/tuningfork/runner.py +1156 -0
  192. tuningfork_lab-0.6.0/src/tuningfork/sandbox.py +339 -0
  193. tuningfork_lab-0.6.0/src/tuningfork/snapshots.py +270 -0
  194. tuningfork_lab-0.6.0/src/tuningfork/specs.py +724 -0
  195. tuningfork_lab-0.6.0/src/tuningfork/store/__init__.py +6 -0
  196. tuningfork_lab-0.6.0/src/tuningfork/store/blobs.py +88 -0
  197. tuningfork_lab-0.6.0/src/tuningfork/store/db.py +2817 -0
  198. tuningfork_lab-0.6.0/src/tuningfork/store/masking.py +156 -0
  199. tuningfork_lab-0.6.0/src/tuningfork/store/tree.py +118 -0
  200. tuningfork_lab-0.6.0/src/tuningfork/ui/__init__.py +33 -0
  201. tuningfork_lab-0.6.0/src/tuningfork/ui/static/app.css +1745 -0
  202. tuningfork_lab-0.6.0/src/tuningfork/ui/static/index.html +104 -0
  203. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/api.js +92 -0
  204. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/app.js +415 -0
  205. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/auth.js +88 -0
  206. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/checktry.js +258 -0
  207. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/cost.js +23 -0
  208. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/dom.js +308 -0
  209. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/forms.js +246 -0
  210. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/holdtools.js +131 -0
  211. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/jobs.js +332 -0
  212. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/launcher.js +95 -0
  213. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/live.js +129 -0
  214. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/markdown.js +306 -0
  215. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/nav.js +30 -0
  216. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/payload.js +287 -0
  217. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/results.js +587 -0
  218. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/start.js +593 -0
  219. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/stepinfo.js +231 -0
  220. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/targets.js +144 -0
  221. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/timeline.js +1002 -0
  222. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/tokens.js +412 -0
  223. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/trash.js +113 -0
  224. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/variants.js +197 -0
  225. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/batch.js +194 -0
  226. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/chains.js +91 -0
  227. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/compare.js +305 -0
  228. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/connections.js +555 -0
  229. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/experiments.js +365 -0
  230. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/home.js +397 -0
  231. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/import.js +570 -0
  232. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/jobs.js +446 -0
  233. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/live.js +848 -0
  234. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/refine.js +1352 -0
  235. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/run.js +246 -0
  236. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/runs.js +264 -0
  237. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/scenarios.js +574 -0
  238. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/skills.js +466 -0
  239. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/step.js +144 -0
  240. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/views/trash.js +90 -0
  241. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/where.js +141 -0
  242. tuningfork_lab-0.6.0/src/tuningfork/ui/static/js/words.js +55 -0
  243. tuningfork_lab-0.6.0/src/tuningfork/wire/__init__.py +28 -0
  244. tuningfork_lab-0.6.0/src/tuningfork/wire/anthropic.py +605 -0
  245. tuningfork_lab-0.6.0/src/tuningfork/wire/base.py +176 -0
  246. tuningfork_lab-0.6.0/src/tuningfork/wire/completion.py +136 -0
  247. tuningfork_lab-0.6.0/src/tuningfork/wire/openai.py +162 -0
  248. tuningfork_lab-0.6.0/tests/__init__.py +0 -0
  249. tuningfork_lab-0.6.0/tests/browser/README.md +201 -0
  250. tuningfork_lab-0.6.0/tests/browser/cdp.mjs +174 -0
  251. tuningfork_lab-0.6.0/tests/browser/jobs_ui.mjs +2194 -0
  252. tuningfork_lab-0.6.0/tests/browser/live_ui.mjs +170 -0
  253. tuningfork_lab-0.6.0/tests/conftest.py +133 -0
  254. tuningfork_lab-0.6.0/tests/fixtures/formats/capture.py +150 -0
  255. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/braces-1.json +71 -0
  256. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/braces-2.json +176 -0
  257. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/nested-1.json +219 -0
  258. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/nested-2.json +229 -0
  259. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/no-call-with-tools-1.json +105 -0
  260. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/no-call-with-tools-2.json +59 -0
  261. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/numbers-1.json +171 -0
  262. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/numbers-2.json +159 -0
  263. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/one-call-1.json +85 -0
  264. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/one-call-2.json +93 -0
  265. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/parallel-1.json +166 -0
  266. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/parallel-2.json +158 -0
  267. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/text-1.json +122 -0
  268. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/text-2.json +138 -0
  269. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/text-nothink-1.json +18 -0
  270. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/text-nothink-2.json +18 -0
  271. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/text-then-call-1.json +112 -0
  272. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/text-then-call-2.json +113 -0
  273. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/unicode-1.json +129 -0
  274. tuningfork_lab-0.6.0/tests/fixtures/formats/gemma4/unicode-2.json +158 -0
  275. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/braces-1.json +96 -0
  276. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/braces-2.json +96 -0
  277. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/call-nothink-1.json +63 -0
  278. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/call-nothink-2.json +63 -0
  279. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/multiline-1.json +78 -0
  280. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/multiline-2.json +78 -0
  281. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/nested-1.json +126 -0
  282. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/nested-2.json +126 -0
  283. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/no-call-with-tools-1.json +45 -0
  284. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/no-call-with-tools-2.json +40 -0
  285. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/no-think-switch-1.json +20 -0
  286. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/no-think-switch-2.json +20 -0
  287. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/numbers-1.json +87 -0
  288. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/numbers-2.json +87 -0
  289. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/one-call-1.json +62 -0
  290. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/one-call-2.json +62 -0
  291. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/parallel-1.json +87 -0
  292. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/parallel-2.json +87 -0
  293. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/text-1.json +29 -0
  294. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/text-2.json +29 -0
  295. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/text-nothink-1.json +20 -0
  296. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/text-nothink-2.json +20 -0
  297. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/text-then-call-1.json +73 -0
  298. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/text-then-call-2.json +74 -0
  299. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/unicode-1.json +84 -0
  300. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen2.5/qwen2.5-32b-instruct/unicode-2.json +84 -0
  301. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/braces-1.json +371 -0
  302. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/braces-2.json +522 -0
  303. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/call-nothink-1.json +63 -0
  304. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/call-nothink-2.json +63 -0
  305. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/multiline-1.json +275 -0
  306. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/multiline-2.json +333 -0
  307. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/nested-1.json +340 -0
  308. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/nested-2.json +347 -0
  309. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/no-call-with-tools-1.json +135 -0
  310. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/no-call-with-tools-2.json +146 -0
  311. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/no-think-switch-1.json +24 -0
  312. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/no-think-switch-2.json +24 -0
  313. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/numbers-1.json +231 -0
  314. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/numbers-2.json +184 -0
  315. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/one-call-1.json +134 -0
  316. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/one-call-2.json +135 -0
  317. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/parallel-1.json +197 -0
  318. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/parallel-2.json +215 -0
  319. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/text-1.json +233 -0
  320. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/text-2.json +186 -0
  321. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/text-nothink-1.json +20 -0
  322. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/text-nothink-2.json +20 -0
  323. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/text-then-call-1.json +461 -0
  324. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/text-then-call-2.json +318 -0
  325. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/unicode-1.json +202 -0
  326. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b/unicode-2.json +207 -0
  327. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/braces-1.json +96 -0
  328. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/braces-2.json +96 -0
  329. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/call-nothink-1.json +63 -0
  330. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/call-nothink-2.json +63 -0
  331. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/multiline-1.json +78 -0
  332. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/multiline-2.json +78 -0
  333. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/nested-1.json +126 -0
  334. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/nested-2.json +126 -0
  335. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/no-call-with-tools-1.json +47 -0
  336. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/no-call-with-tools-2.json +47 -0
  337. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/no-think-switch-1.json +20 -0
  338. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/no-think-switch-2.json +20 -0
  339. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/numbers-1.json +87 -0
  340. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/numbers-2.json +87 -0
  341. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/one-call-1.json +62 -0
  342. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/one-call-2.json +62 -0
  343. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/parallel-1.json +87 -0
  344. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/parallel-2.json +87 -0
  345. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/text-1.json +29 -0
  346. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/text-2.json +29 -0
  347. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/text-nothink-1.json +20 -0
  348. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/text-nothink-2.json +20 -0
  349. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/text-then-call-1.json +74 -0
  350. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/text-then-call-2.json +74 -0
  351. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/unicode-1.json +84 -0
  352. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-30b-a3b-instruct-2507/unicode-2.json +84 -0
  353. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/braces-1.json +5132 -0
  354. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/braces-2.json +469 -0
  355. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/call-nothink-1.json +156 -0
  356. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/call-nothink-2.json +150 -0
  357. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/multiline-1.json +261 -0
  358. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/multiline-2.json +2905 -0
  359. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/nested-1.json +656 -0
  360. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/nested-2.json +630 -0
  361. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/no-call-with-tools-1.json +286 -0
  362. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/no-call-with-tools-2.json +207 -0
  363. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/no-think-switch-1.json +1711 -0
  364. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/no-think-switch-2.json +466 -0
  365. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/numbers-1.json +374 -0
  366. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/numbers-2.json +384 -0
  367. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/one-call-1.json +182 -0
  368. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/one-call-2.json +172 -0
  369. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/parallel-1.json +477 -0
  370. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/parallel-2.json +463 -0
  371. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/text-1.json +249 -0
  372. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/text-2.json +742 -0
  373. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/text-nothink-1.json +265 -0
  374. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/text-nothink-2.json +272 -0
  375. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/text-then-call-1.json +2898 -0
  376. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/text-then-call-2.json +3475 -0
  377. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/unicode-1.json +643 -0
  378. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3/qwen3-4b-thinking-2507/unicode-2.json +878 -0
  379. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/braces-1.json +101 -0
  380. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/braces-2.json +101 -0
  381. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/call-nothink-1.json +65 -0
  382. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/call-nothink-2.json +65 -0
  383. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/multiline-1.json +83 -0
  384. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/multiline-2.json +83 -0
  385. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/nested-1.json +146 -0
  386. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/nested-2.json +146 -0
  387. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/no-call-with-tools-1.json +48 -0
  388. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/no-call-with-tools-2.json +40 -0
  389. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/no-marker-1.json +63 -0
  390. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/no-marker-2.json +63 -0
  391. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/no-think-switch-1.json +21 -0
  392. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/no-think-switch-2.json +20 -0
  393. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/numbers-1.json +89 -0
  394. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/numbers-2.json +89 -0
  395. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/one-call-1.json +64 -0
  396. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/one-call-2.json +64 -0
  397. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/parallel-1.json +91 -0
  398. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/parallel-2.json +91 -0
  399. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/text-1.json +29 -0
  400. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/text-2.json +29 -0
  401. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/text-nothink-1.json +21 -0
  402. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/text-nothink-2.json +21 -0
  403. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/text-then-call-1.json +91 -0
  404. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/text-then-call-2.json +95 -0
  405. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/text-then-no-marker-1.json +98 -0
  406. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/text-then-no-marker-2.json +98 -0
  407. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/unicode-1.json +90 -0
  408. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-30b-a3b-instruct/unicode-2.json +90 -0
  409. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/braces-1.json +101 -0
  410. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/braces-2.json +101 -0
  411. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/call-nothink-1.json +65 -0
  412. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/call-nothink-2.json +65 -0
  413. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/multiline-1.json +83 -0
  414. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/multiline-2.json +83 -0
  415. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/nested-1.json +146 -0
  416. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/nested-2.json +146 -0
  417. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/no-call-with-tools-1.json +47 -0
  418. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/no-call-with-tools-2.json +47 -0
  419. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/no-marker-1.json +51 -0
  420. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/no-marker-2.json +55 -0
  421. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/no-think-switch-1.json +20 -0
  422. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/no-think-switch-2.json +20 -0
  423. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/numbers-1.json +99 -0
  424. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/numbers-2.json +99 -0
  425. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/one-call-1.json +64 -0
  426. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/one-call-2.json +64 -0
  427. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/parallel-1.json +91 -0
  428. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/parallel-2.json +91 -0
  429. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/text-1.json +29 -0
  430. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/text-2.json +29 -0
  431. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/text-nothink-1.json +20 -0
  432. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/text-nothink-2.json +20 -0
  433. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/text-then-call-1.json +65 -0
  434. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/text-then-call-2.json +76 -0
  435. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/text-then-no-marker-1.json +1061 -0
  436. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/text-then-no-marker-2.json +94 -0
  437. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/unicode-1.json +90 -0
  438. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3-coder/qwen3-coder-next/unicode-2.json +90 -0
  439. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/braces-1.json +199 -0
  440. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/braces-2.json +197 -0
  441. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/call-nothink-1.json +69 -0
  442. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/call-nothink-2.json +69 -0
  443. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/multiline-1.json +186 -0
  444. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/multiline-2.json +171 -0
  445. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/nested-1.json +312 -0
  446. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/nested-2.json +312 -0
  447. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/no-call-with-tools-1.json +89 -0
  448. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/no-call-with-tools-2.json +94 -0
  449. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/numbers-1.json +205 -0
  450. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/numbers-2.json +220 -0
  451. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/one-call-1.json +114 -0
  452. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/one-call-2.json +114 -0
  453. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/parallel-1.json +163 -0
  454. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/parallel-2.json +190 -0
  455. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/text-1.json +252 -0
  456. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/text-2.json +253 -0
  457. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/text-nothink-1.json +20 -0
  458. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/text-nothink-2.json +20 -0
  459. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/text-then-call-1.json +160 -0
  460. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/text-then-call-2.json +154 -0
  461. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/unicode-1.json +218 -0
  462. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.5-27b/unicode-2.json +200 -0
  463. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/braces-1.json +523 -0
  464. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/braces-2.json +861 -0
  465. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/call-nothink-1.json +69 -0
  466. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/call-nothink-2.json +69 -0
  467. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/multiline-1.json +191 -0
  468. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/multiline-2.json +393 -0
  469. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/nested-1.json +325 -0
  470. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/nested-2.json +884 -0
  471. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/no-call-with-tools-1.json +222 -0
  472. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/no-call-with-tools-2.json +310 -0
  473. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/numbers-1.json +395 -0
  474. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/numbers-2.json +297 -0
  475. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/one-call-1.json +114 -0
  476. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/one-call-2.json +111 -0
  477. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/parallel-1.json +169 -0
  478. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/parallel-2.json +251 -0
  479. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/text-1.json +495 -0
  480. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/text-2.json +219 -0
  481. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/text-nothink-1.json +21 -0
  482. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/text-nothink-2.json +20 -0
  483. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/text-then-call-1.json +448 -0
  484. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/text-then-call-2.json +737 -0
  485. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/unicode-1.json +445 -0
  486. tuningfork_lab-0.6.0/tests/fixtures/formats/qwen3.5/qwen3.6-27b/unicode-2.json +178 -0
  487. tuningfork_lab-0.6.0/tests/golden/ab-exploratory.txt +23 -0
  488. tuningfork_lab-0.6.0/tests/golden/ab.txt +28 -0
  489. tuningfork_lab-0.6.0/tests/golden/diff.txt +5 -0
  490. tuningfork_lab-0.6.0/tests/golden/divergence-lens.txt +10 -0
  491. tuningfork_lab-0.6.0/tests/golden/divergence.txt +10 -0
  492. tuningfork_lab-0.6.0/tests/golden/experiment-ab.txt +25 -0
  493. tuningfork_lab-0.6.0/tests/golden/experiment-resample-lens.txt +11 -0
  494. tuningfork_lab-0.6.0/tests/golden/experiment-resample.txt +10 -0
  495. tuningfork_lab-0.6.0/tests/golden/experiments.txt +6 -0
  496. tuningfork_lab-0.6.0/tests/golden/resample.txt +16 -0
  497. tuningfork_lab-0.6.0/tests/golden/runs-called.txt +4 -0
  498. tuningfork_lab-0.6.0/tests/golden/runs-quiet.txt +4 -0
  499. tuningfork_lab-0.6.0/tests/golden/runs.txt +7 -0
  500. tuningfork_lab-0.6.0/tests/golden/show.txt +7 -0
  501. tuningfork_lab-0.6.0/tests/golden/step-full.txt +33 -0
  502. tuningfork_lab-0.6.0/tests/golden/step.txt +12 -0
  503. tuningfork_lab-0.6.0/tests/golden/tasks.txt +9 -0
  504. tuningfork_lab-0.6.0/tests/golden/timeline-full.txt +33 -0
  505. tuningfork_lab-0.6.0/tests/golden/timeline.txt +14 -0
  506. tuningfork_lab-0.6.0/tests/helpers.py +337 -0
  507. tuningfork_lab-0.6.0/tests/test_access.py +192 -0
  508. tuningfork_lab-0.6.0/tests/test_addskill.py +222 -0
  509. tuningfork_lab-0.6.0/tests/test_analysis.py +318 -0
  510. tuningfork_lab-0.6.0/tests/test_api.py +379 -0
  511. tuningfork_lab-0.6.0/tests/test_approve.py +317 -0
  512. tuningfork_lab-0.6.0/tests/test_approve_front.py +163 -0
  513. tuningfork_lab-0.6.0/tests/test_batch.py +274 -0
  514. tuningfork_lab-0.6.0/tests/test_breakpoints.py +450 -0
  515. tuningfork_lab-0.6.0/tests/test_canaries.py +256 -0
  516. tuningfork_lab-0.6.0/tests/test_chain.py +355 -0
  517. tuningfork_lab-0.6.0/tests/test_chat_import.py +881 -0
  518. tuningfork_lab-0.6.0/tests/test_check_along.py +284 -0
  519. tuningfork_lab-0.6.0/tests/test_checks.py +169 -0
  520. tuningfork_lab-0.6.0/tests/test_claude_code.py +476 -0
  521. tuningfork_lab-0.6.0/tests/test_cli.py +580 -0
  522. tuningfork_lab-0.6.0/tests/test_cli_control.py +283 -0
  523. tuningfork_lab-0.6.0/tests/test_cli_golden.py +191 -0
  524. tuningfork_lab-0.6.0/tests/test_compact.py +287 -0
  525. tuningfork_lab-0.6.0/tests/test_connections.py +642 -0
  526. tuningfork_lab-0.6.0/tests/test_demo.py +191 -0
  527. tuningfork_lab-0.6.0/tests/test_diff.py +235 -0
  528. tuningfork_lab-0.6.0/tests/test_distribution.py +325 -0
  529. tuningfork_lab-0.6.0/tests/test_docs.py +68 -0
  530. tuningfork_lab-0.6.0/tests/test_endpoints.py +243 -0
  531. tuningfork_lab-0.6.0/tests/test_example_hermes_design_md.py +77 -0
  532. tuningfork_lab-0.6.0/tests/test_experiments.py +714 -0
  533. tuningfork_lab-0.6.0/tests/test_fork_variant.py +68 -0
  534. tuningfork_lab-0.6.0/tests/test_forking.py +443 -0
  535. tuningfork_lab-0.6.0/tests/test_forks.py +706 -0
  536. tuningfork_lab-0.6.0/tests/test_formats.py +691 -0
  537. tuningfork_lab-0.6.0/tests/test_guard.py +244 -0
  538. tuningfork_lab-0.6.0/tests/test_harness.py +426 -0
  539. tuningfork_lab-0.6.0/tests/test_held.py +595 -0
  540. tuningfork_lab-0.6.0/tests/test_held_out.py +182 -0
  541. tuningfork_lab-0.6.0/tests/test_hold_tools.py +240 -0
  542. tuningfork_lab-0.6.0/tests/test_imports.py +30 -0
  543. tuningfork_lab-0.6.0/tests/test_jobs.py +543 -0
  544. tuningfork_lab-0.6.0/tests/test_jobs_e2e.py +270 -0
  545. tuningfork_lab-0.6.0/tests/test_jobs_harness.py +134 -0
  546. tuningfork_lab-0.6.0/tests/test_launches.py +553 -0
  547. tuningfork_lab-0.6.0/tests/test_live_server.py +103 -0
  548. tuningfork_lab-0.6.0/tests/test_mcp_http.py +167 -0
  549. tuningfork_lab-0.6.0/tests/test_mcp_schema.py +149 -0
  550. tuningfork_lab-0.6.0/tests/test_mcp_stdio.py +206 -0
  551. tuningfork_lab-0.6.0/tests/test_mcp_tools.py +896 -0
  552. tuningfork_lab-0.6.0/tests/test_ops_common.py +192 -0
  553. tuningfork_lab-0.6.0/tests/test_packet.py +81 -0
  554. tuningfork_lab-0.6.0/tests/test_paths.py +102 -0
  555. tuningfork_lab-0.6.0/tests/test_private.py +160 -0
  556. tuningfork_lab-0.6.0/tests/test_probe.py +435 -0
  557. tuningfork_lab-0.6.0/tests/test_progress.py +105 -0
  558. tuningfork_lab-0.6.0/tests/test_proposer.py +186 -0
  559. tuningfork_lab-0.6.0/tests/test_proxy.py +1120 -0
  560. tuningfork_lab-0.6.0/tests/test_qwen3_coder_tokens.py +143 -0
  561. tuningfork_lab-0.6.0/tests/test_qwen3_tokens.py +412 -0
  562. tuningfork_lab-0.6.0/tests/test_qwen_tokens.py +148 -0
  563. tuningfork_lab-0.6.0/tests/test_reads.py +102 -0
  564. tuningfork_lab-0.6.0/tests/test_real_server.py +177 -0
  565. tuningfork_lab-0.6.0/tests/test_reasoning.py +151 -0
  566. tuningfork_lab-0.6.0/tests/test_refine.py +178 -0
  567. tuningfork_lab-0.6.0/tests/test_refine_front.py +282 -0
  568. tuningfork_lab-0.6.0/tests/test_reports.py +373 -0
  569. tuningfork_lab-0.6.0/tests/test_runner.py +830 -0
  570. tuningfork_lab-0.6.0/tests/test_sandbox.py +351 -0
  571. tuningfork_lab-0.6.0/tests/test_scenarios.py +284 -0
  572. tuningfork_lab-0.6.0/tests/test_scoring.py +223 -0
  573. tuningfork_lab-0.6.0/tests/test_screen.py +191 -0
  574. tuningfork_lab-0.6.0/tests/test_screen_stats.py +171 -0
  575. tuningfork_lab-0.6.0/tests/test_screen_to_end.py +380 -0
  576. tuningfork_lab-0.6.0/tests/test_servers.py +104 -0
  577. tuningfork_lab-0.6.0/tests/test_skill_substitution.py +336 -0
  578. tuningfork_lab-0.6.0/tests/test_skills.py +230 -0
  579. tuningfork_lab-0.6.0/tests/test_snapshots.py +132 -0
  580. tuningfork_lab-0.6.0/tests/test_specs.py +218 -0
  581. tuningfork_lab-0.6.0/tests/test_spend.py +92 -0
  582. tuningfork_lab-0.6.0/tests/test_sse.py +328 -0
  583. tuningfork_lab-0.6.0/tests/test_storage.py +557 -0
  584. tuningfork_lab-0.6.0/tests/test_store.py +547 -0
  585. tuningfork_lab-0.6.0/tests/test_store_masking.py +458 -0
  586. tuningfork_lab-0.6.0/tests/test_tally.py +232 -0
  587. tuningfork_lab-0.6.0/tests/test_targets.py +225 -0
  588. tuningfork_lab-0.6.0/tests/test_timeline.py +381 -0
  589. tuningfork_lab-0.6.0/tests/test_tokens.py +362 -0
  590. tuningfork_lab-0.6.0/tests/test_tokens_ops.py +170 -0
  591. tuningfork_lab-0.6.0/tests/test_trash.py +433 -0
  592. tuningfork_lab-0.6.0/tests/test_trash_refinements.py +162 -0
  593. tuningfork_lab-0.6.0/tests/test_ui.py +245 -0
  594. tuningfork_lab-0.6.0/tests/test_validate.py +319 -0
  595. tuningfork_lab-0.6.0/tests/test_validation_gate.py +146 -0
  596. tuningfork_lab-0.6.0/tests/test_variants.py +67 -0
  597. tuningfork_lab-0.6.0/tests/test_where.py +257 -0
  598. tuningfork_lab-0.6.0/tests/test_wire.py +571 -0
  599. tuningfork_lab-0.6.0/tests/test_words.py +38 -0
  600. tuningfork_lab-0.6.0/tests/test_wsl_wrapper.py +76 -0
  601. tuningfork_lab-0.6.0/uv.lock +918 -0
@@ -0,0 +1,3 @@
1
+ # Keep LF in the repository and in working copies, on Windows too: a CRLF checkout breaks
2
+ # shell scripts with obscure `\r` errors, and changes test fixtures that must stay byte-exact.
3
+ * text=auto eol=lf
@@ -0,0 +1,35 @@
1
+ # The contribution gate: the whole suite, with no GPU and no model, on every supported Python.
2
+ name: tests
3
+
4
+ on:
5
+ push:
6
+ branches: [main]
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ concurrency:
14
+ group: tests-${{ github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ test:
19
+ name: Python ${{ matrix.python }}
20
+ runs-on: ubuntu-latest
21
+ timeout-minutes: 30
22
+ strategy:
23
+ fail-fast: false
24
+ matrix:
25
+ python: ["3.12", "3.13", "3.14"]
26
+ steps:
27
+ # Pinned to commits: a tag can move, and setup-uv publishes no major-version tag.
28
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
30
+ with:
31
+ python-version: ${{ matrix.python }}
32
+ - name: Install
33
+ run: uv sync --all-groups --locked
34
+ - name: Test
35
+ run: uv run pytest -q
@@ -0,0 +1,19 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+
11
+ # Local notes and settings, never committed: CLAUDE.local.md, a probe's *.local.json
12
+ *.local.md
13
+ *.local.json
14
+
15
+ # Local recording state
16
+ *.db
17
+ *.db-wal
18
+ *.db-shm
19
+ .tuningfork/
@@ -0,0 +1,566 @@
1
+ # Changelog
2
+
3
+ ## 0.6.0 — on PyPI, a demo without a GPU, and redaction reworked
4
+
5
+ tuningfork is on PyPI as `tuningfork-lab`, and `tuningfork demo` shows the whole loop in the web
6
+ UI with no GPU and no model. The README is shorter. Redaction, which masks keys in what tuningfork
7
+ records, was reworked: it masks keys rather than code, never breaks the JSON it masks, and knows
8
+ more formats, and the recordings folder's store masks everything it keeps, once.
9
+
10
+ - **On PyPI as `tuningfork-lab`.** `uv tool install "tuningfork-lab[mcp]"` installs the
11
+ `tuningfork` command, its MCP server included; without `[mcp]`, the rest. The import stays
12
+ `tuningfork`. The name `tuningfork` is too close to an existing project's to register.
13
+ - **`tuningfork demo`** opens the web UI on made-up runs, which tuningfork's own proxy serves in
14
+ front of a scripted model, so every button works without a GPU:
15
+ `uvx --from tuningfork-lab tuningfork demo`. It records only into a folder of its own, never
16
+ into `~/.tuningfork`.
17
+ - **The README** says what tuningfork is, why, and how to start: the demo first, then your own
18
+ agent. Its command tables lead to `docs/commands.md`, and the web UI's pictures moved to
19
+ `docs/web-ui.md`.
20
+ - **Redaction masks keys, not code.** A value under a secret's name is masked only where it reads
21
+ as a key: a name, a call, a path or a number is kept. Hermes's own source, read as tool results,
22
+ had 731 masks in 147 of its 877 files, and has 2 now, both example keys. Its dependencies went
23
+ from 273 masks to 7, and Python's standard library from 27 to none. A mask still costs a step
24
+ its scoring and its forking tokens, and a resample sends `<redacted>` where the model saw the
25
+ text: a plan says so now.
26
+ - **More formats are masked by their shape:** provider tokens (`gsk_`, `hf_`, `glpat-`,
27
+ `github_pat_`, `AIza…` and more), Stripe's keys, JWTs, Discord and Telegram bot tokens, private
28
+ keys, a URL's password, HTTP Basic credentials, AWS's and Azure's secret keys, and Slack
29
+ webhooks. Of 47 key formats in 12 places a key stands, 537 are masked, against 258.
30
+ - **A mask never breaks JSON.** A number is never masked, and of 30,000 fuzzed payloads none is
31
+ dropped, against 2,459. JSON written inside a string stays JSON.
32
+ - **A key the model streams in pieces** is no longer kept in the raw bytes beside its answer,
33
+ which a fork read back and gave its harness again. Bytes that hold a secret are written again
34
+ from the masked answer, and a fork refuses to replay that step.
35
+ - **The store masks everything it keeps, once.** A check's output in a run's result and a
36
+ launch's exit, a job's plan and result, an imported chat's steps, a step's error text, a label
37
+ queued for the next run, and an upstream's password were kept in clear. A value masked in one form of a
38
+ step is masked in its others, so a server's error that quotes a password is masked, and so is a
39
+ key the model writes in its own call syntax, in its tokens. An answer sent at once keeps no
40
+ logprobs that spell a key, and a stream labelled otherwise is read as a stream. What a run is
41
+ given is kept as given: its task, a wording tried, a scenario's conversation.
42
+
43
+ Changed:
44
+
45
+ - **`/_tuningfork/health`, Connections and `serve`** show an upstream's user and password as
46
+ `<redacted>`, and Connections says to type them back into its restart command.
47
+ - **A step's parameters** count its masks (`masked`) and name the patterns that made them
48
+ (`redaction`), which `tuningfork step` prints.
49
+ - **`Settings.redaction_patterns` is gone:** a `Store` holds its patterns.
50
+
51
+ Recordings made before keep what they stored: masking them again with today's rules is planned. A
52
+ fork of a run recorded before still matches its calls, each mask standing for what it hid.
53
+
54
+ ## 0.5.0 — Qwen models, token by token
55
+
56
+ Token-level work — scoring an answer, forking tokens, a resample's kept tokens marked by part,
57
+ the model's own odds of each tool — reads Qwen3.5 and 3.6, Qwen3, Qwen2.5 and Qwen3-Coder now,
58
+ beside Gemma 4, each as llama.cpp reads it. A Claude Code step is scored on the tokens the model
59
+ wrote, and a recorded request or answer no longer keeps a key on a line of its own in clear. The
60
+ README opens in plainer words.
61
+
62
+ - **Qwen3.5 and Qwen3.6** (`qwen3.5`, as the server check names the format), read as llama.cpp's
63
+ Qwen3-Coder parser reads them: an answer can begin inside a reasoning its prompt opened, and
64
+ each argument is typed by the tool's schema, as the server types it.
65
+ - **Qwen3, the 2507 updates included, and Qwen2.5** (`qwen3`, `qwen2.5`), whose calls are JSON
66
+ inside `<tool_call>`, read as the parser llama.cpp builds from their templates reads them. A
67
+ Qwen3 template that renders no reasoning, as ggml-org's for Qwen3-2507 Instruct does, gets
68
+ Qwen2.5's parser from the server, and is named `qwen2.5`.
69
+ - **Qwen3-Coder** (`qwen3-coder`), read by the same parser of llama.cpp's as Qwen3.5, in its
70
+ other setting: no reasoning, and a first call with its `<tool_call>` or without it.
71
+ - **Texts the server reads alike** give the same parsed answer: for Qwen3, an empty reasoning
72
+ and none; for Qwen3 and Qwen2.5, a newline and a blank line between calls; for Qwen3-Coder, a
73
+ first call with its `<tool_call>` and one without. Scoring keeps the text with the recorded
74
+ number of tokens, and where several have it, the one the model finds likelier where they part.
75
+ `tuningfork tokens` and a step's *Tokens* view then say "rebuilt as the likelier of 2 texts the
76
+ server reads alike", and a forking probe from it warns.
77
+ - **Checked on real servers:** Qwen3.5 27B and Qwen3.6 27B; Qwen3-30B-A3B,
78
+ Qwen3-30B-A3B-Instruct-2507 and Qwen3-4B-Thinking-2507; Qwen2.5-32B-Instruct;
79
+ Qwen3-Coder-30B-A3B-Instruct and Qwen3-Coder-Next. On each, the server's own answers were read
80
+ as it read them and written back token for token, and the real-server tests
81
+ (`tests/test_real_server.py`) pass.
82
+ - **A character written in two tokens.** llama.cpp can report it as one probability entry, which
83
+ holds the whole character, and Qwen does this. Scoring and a forking probe's continuations split
84
+ it back into one entry a token, by the token ids the server returns: a token merged away has no
85
+ probability of its own, shown as "—". Without this, scoring would refuse such an answer. A
86
+ resample's kept tokens keep the one entry: the chat endpoint returns no ids to split it by.
87
+ - **Setting up a server for them** (`docs/setup.md`): a command for Qwen3.6 27B,
88
+ Qwen3-30B-A3B-Instruct-2507 and Qwen3-Coder-30B-A3B, how Qwen3-Coder-Next fits one 32 GB GPU,
89
+ and how to extend a model file whose context is too short for Hermes Agent. Qwen3.5 and 3.6
90
+ need `--ctx-checkpoints 0`, and `--swa-full`, an alternative for Gemma 4, does not help them.
91
+ The glossary and the Guide's banner say so.
92
+ - **A Claude Code step is scored on the tokens the model wrote.** A streamed tool call's
93
+ arguments are kept as the server sent them. They were written again from the parsed input, with
94
+ characters outside ASCII escaped, numbers normalized (`61.50` became `61.5`) and the model's
95
+ spacing lost. Scoring could then rebuild other tokens than the model's, and said "rebuilt
96
+ approximately". A step recorded before is scored from the stream the server sent, which the
97
+ recording kept. An answer that was not streamed comes parsed, and its arguments are still
98
+ written from it.
99
+ - **The README** opens in plain words, and its quickstart comes early: install, a five-minute
100
+ walkthrough without a GPU, then your own agent. Its figure is redrawn as the loop in four steps,
101
+ with counts only, and its tagline is now "Tune agent skills to your own model." The worked
102
+ example, *See it work*, now follows *Let an agent drive it*.
103
+ - **find-the-file's forking token, measured again** with 40 continuations a word, where the
104
+ earlier probe drew 10: after "Wait", 30 searched for the file, and after "According", none
105
+ (p = 1.9 × 10⁻¹³). Its section 4 now calls "Wait" a forking token.
106
+ - **The examples guide's Claude Code steps**, recorded again with the arguments kept as sent.
107
+
108
+ Changed:
109
+
110
+ - **Redaction reads stored text as it reads.** It matched its patterns against JSON as written,
111
+ where a newline is a backslash and an `n`: an `sk-` key or a `Bearer` token on a line of its
112
+ own, or after a tab, then followed a letter, and was stored in clear in every harness's
113
+ recordings. Nor did an assignment whose value is in double quotes, such as `api_key = "…"` or
114
+ `"token": "…"`, match inside a message, a call's arguments or a tool's result, where JSON
115
+ escapes every quote. The patterns now also run over the text with its JSON escapes read, three
116
+ levels deep: a payload, the arguments or JSON result in it, and a JSON file that result shows.
117
+ A key with an escape inside it is masked whole, in a Claude Code call's arguments too. A token
118
+ stream reads the model's own escapes the same way. Recordings made before keep what they
119
+ stored, and a streamed answer's raw bytes, kept beside it, can still hold a key the server sent
120
+ in pieces.
121
+ - **A Claude Code step's arguments read as the server sent them** wherever they are printed as
122
+ text: `tuningfork step` and `timeline`, a held call, and MCP's `show_step`. Before, they read as
123
+ Python's `json.dumps` of the input, with a space after each `:` and `,`, characters outside
124
+ ASCII escaped and numbers normalized. The web UI shows each argument as a field, as before.
125
+ Steps recorded before keep what they stored. A behaviour, a target and a breakpoint read the
126
+ parsed arguments, so none of them changes.
127
+
128
+ Nothing already in the recordings folder changes: no new columns, nothing to migrate.
129
+
130
+ QwQ is not read yet: llama.cpp reads it with a parser of its own.
131
+
132
+ ## 0.4.0 — the web UI, redesigned
133
+
134
+ The web UI, page by page, after a review of it: a sidebar in the loop's order, plainer words, one
135
+ meaning a colour, each page leading with its take-away, and the work on a step beside the run.
136
+ The CLI, MCP and the docs keep tuningfork's precise words.
137
+
138
+ - **A sidebar in the loop's order**, where the top bar's ten tabs were: the Guide and its Words,
139
+ Connections; then Start (Scenarios, Run a task…, Import a chat, Live, Skills, Jobs), Record
140
+ (Runs, Chains), Measure (Experiments), Improve (Refine) and the Trash. Its foot says how the
141
+ proxy, the server and the harnesses are. What needs a running proxy stays in it, greyed, saying
142
+ why. Narrower, it shows only its icons; on a phone, it opens from a bar across the top.
143
+ - **Plain words on the pages**: an arm is a *wording*, a target *what you count* (a refinement's,
144
+ *what the model should do*), a screen a *test at this step*, a validation *full test runs*, the
145
+ gate the *approval checks*, a spec a *task file*. The CLI, MCP, the docs and every sentence the
146
+ server writes keep the precise words. The glossary holds both: the Words page (`#/words`, where
147
+ older `#/guide?term=` links land) shows them side by side, and `tuningfork glossary` finds a
148
+ term by either and prints the page's phrase.
149
+ - **One meaning a colour**: green and red for a check's verdict — a run's, a fork's, a gate's, a
150
+ significant change in how often a task's check passes; amber a caution; the accent what can be
151
+ acted on, what is current or significant, the model's own answer and every rate, a pass rate
152
+ included; purple a skill's own words; teal, with ✎, what a person or tuningfork changed; grey
153
+ everything else. Red keeps only what every page means by it besides: a diff's removed lines
154
+ beside its green added ones, a button that deletes, a field to put right. The Guide shows the
155
+ legend in a line.
156
+ - **Each page leads with its take-away**: a run's verdict in a strip under its title, with the
157
+ call that made the check's complaint where the check was run along the run; on the divergence
158
+ page, the step whose behaviour best predicts a pass; Connections' setup at a glance; a
159
+ refinement's four stages in a line.
160
+ - **A run's page reads top to bottom, its work beside it.** Every step's row has *Show details*
161
+ and *Work from here*. One step's details are open at a time. A step's actions, and the forms
162
+ they open, sit in a column beside the run, each step's kept as it was left, and the address
163
+ names that work, so a reload brings it back; Escape hides it. The rail's lines move you through
164
+ the run and open nothing. On an imported or live run, *Fork from here…* is greyed, and points to
165
+ a scenario of the run instead.
166
+ - **A refinement's page has a section for each stage**, numbered as the stepper numbers it: the
167
+ two ways to propose side by side; the proposals, with the screen of the ticked ones (a *test at
168
+ this step*) under them; the validations (*full test runs*); approval. The current stage's form
169
+ is open as the page opens, and no form opens by itself after.
170
+ - **A finished job hands off to what it made**: its page says what it found in a line and leads
171
+ there, its log folded. The Jobs list is in three parts — running and queued, each queued job
172
+ saying its place in its line; finished; stopped or failed — and names each job by the section
173
+ of its work, links what it made, and says who started it: you, from which page, or an agent
174
+ over MCP. `tuningfork jobs list` and `jobs show` say who started it too. A job's link to the
175
+ step it worked at opens that step.
176
+ - **Add a skill** (`tuningfork add-skill`, `forget-skill`, the Skills page's *Add a skill…*, a
177
+ run's *Keep its skill…*): a folder on this machine, remembered where it is, or an uploaded
178
+ folder or a run's skill, kept as tuningfork's own copy. An approved edit is written to the
179
+ folder, or to the copy. Forgetting one drops its record and never deletes: a copy is moved aside
180
+ to `<home>/skills/.forgotten/`. No MCP tool adds one. A skill's page and `tuningfork skill` say
181
+ how its folder was added, and its page runs it again from the scenarios that name it.
182
+ - **Token work is held to the server check.** Scoring an answer and finding forking tokens are
183
+ greyed on the page, and refused before anything is sent, where the check found the server
184
+ without what they need. The check now measures the chat endpoint's logprobs too, which a
185
+ resample's kept tokens need (*Keep each sample's tokens*, new on its form), in one more short
186
+ call. A server on the internet that asks for a key, or is not llama.cpp's, is said to be a
187
+ hosted model's API: tuningfork records your agent's calls through it, its own key passed on.
188
+ The switch on Connections takes one that asks for a key, where it refused it before.
189
+ - **The forking-token probe says what reached each behaviour**: whether the target counts it,
190
+ and which tokens led to it — its page, `tuningfork experiment`, MCP. A flip is the accent's
191
+ colour, its direction said in words. Where the step's answer is a tool call, the form offers
192
+ that tool, with any arguments, as the target first.
193
+ - **Forms**: one nobody has touched says what it needs as a note, not in red. *Make a scenario…*
194
+ shows what it will make first, and chooses its check as a new scenario's editor does. *Run a
195
+ task…* takes a task file's path beside the scenario, greyed while a scenario is chosen.
196
+ - **The README's pictures**, retaken in the redesigned pages, with a new one: a chat had
197
+ elsewhere, made a scenario. `docs/images/shots.mjs` takes them all, the GIF included, in one
198
+ command, and keeps back any picture that shows a path naming a person or a place, or a word you
199
+ list (`TUNINGFORK_SHOTS_PRIVATE`).
200
+
201
+ Changed:
202
+
203
+ - **An A/B is judged at the α it was declared with** wherever it is reported again — its page,
204
+ `tuningfork experiment`, MCP's `show_experiment` — where 0.05 was used whatever was declared.
205
+ An A/B made before its α was kept takes its job's, else 0.05; `--alpha` judges at another
206
+ level, and says so.
207
+ - **Letters agree with the resample a target came from**: an A/B, a screen or a probe of a letter
208
+ target letters its behaviours as that resample did, so its B is the resample's B. One stored
209
+ before finds its resample by its target's description, so read again, its behaviours may take
210
+ new letters.
211
+ - **A resample that asked for each sample's tokens and got none says so**: its page said every
212
+ sample had kept them. The page now counts only the samples that did.
213
+ - **A dry run's refusal is an answer**: asked `quiet`, as the web UI's forms ask, `POST /jobs`
214
+ and the scenario endpoint refuse with `200 {refused}`, not an HTTP error. Without `quiet`,
215
+ nothing changes.
216
+ - **A plan gives its notes apart from its warnings**: an import's and a scenario's (`notes` in
217
+ the API, `note` lines in the terminal).
218
+ - **A scenario's batch or chain names the scenario when refused** for the task file the scenario
219
+ keeps: the field is `scenario`, the one filled in, never `spec`, which nobody typed.
220
+ - **Every job under way is listed** by `GET /jobs`, `tuningfork jobs list` and MCP's `list_jobs`,
221
+ however many came after it: the list was cut at the newest 50 (`list_jobs`: 20, or as many as
222
+ it asked for), so a running job with that many newer ones dropped out. Over MCP's stdio
223
+ transport, `list_jobs` still gives at most the 50 newest beside them, whatever it asks for.
224
+ - **A launch is refused where the server asks for a key**: a harness tuningfork launches carries
225
+ none, and each harness's card on Connections says so. The server check says such a server
226
+ cannot be checked.
227
+ - **A proposing model's address that cannot be read is refused** when it is set: a mistyped port
228
+ was kept, and failed only when the proposer was asked. One saved before is shown as another
229
+ machine's until it is set again. `GET` and `PUT /proposer` give `remote`.
230
+
231
+ ## 0.3.0 — chats your agent already had, and any run as a scenario
232
+
233
+ A conversation your agent had elsewhere comes in as a run, and any run becomes a scenario: a task
234
+ whose runs continue its conversation, so a skill is measured where it was really used.
235
+
236
+ - **A chat, imported** (`tuningfork import`, the Runs page's **Import a chat…**): Hermes Agent's
237
+ session file or its export, or a list of OpenAI-style messages, becomes a run whose every
238
+ assistant turn is a step, so resample, A/B, the token tools and a refinement work at it. Each
239
+ step's request is rebuilt as the harness sends one: the system prompt, everything before it,
240
+ each message as Hermes cleans its own copy before a call. A file that kept no tools (Hermes's
241
+ export keeps none) takes them from a run recorded through the same harness (`--tools-from`,
242
+ **Tools from**). Every skill the chat shows is kept with the run, and an A/B, a refinement or
243
+ a scenario names the one it studies (`--skill`, a **Skill** picker).
244
+ - **Any run as a scenario** (`tuningfork scenario <run> <name>`, a run page's **Make a
245
+ scenario…**, the Scenarios page's **From a run…**): an imported run, one tuningfork launched,
246
+ or one recorded live.
247
+ - **Its prompt** is the person's message the skill came in after, in their words or yours
248
+ (`--prompt`), and the conversation before it is each run's context. The proxy puts it after
249
+ the harness's own instructions in every call (`tuningfork.context`; harnesses that speak
250
+ OpenAI chat), so the skill still comes in live and an edited version reaches it.
251
+ - **Several skills in one chat:** the steps of that turn before the studied skill came in,
252
+ another skill's among them, go to each run as done. Where the chat went on to another skill,
253
+ each run stops there and the check judges the workspace as it is then
254
+ (`tuningfork.stop_at_next_skill`).
255
+ - **A launched run's scenario** takes the run's workspace there as its input files, from the
256
+ snapshots, and its task's check, with the files that check reads. Any other run lists the
257
+ files its calls named, for you to add.
258
+ - **Its eval is named by the scenario:** its runs are a task of their own, never counted with
259
+ the task of the run it came from.
260
+ - **The form** asks for the skill first, then sets each run beside the chat: what each run is
261
+ given (its harness's own system prompt and tools, the conversation before, the prompt),
262
+ then each step, given as done, made by its model, or past where it stops.
263
+ - **A run's page:** the tools the run was offered, above its system prompt; each tool's result
264
+ under the step that called it in the rail; a person's turn told apart from the notes a harness
265
+ writes itself (Hermes's nudge after a cut reply, Claude Code's reminders).
266
+ - **A run recorded live says so**, where a label would name it: the Runs list, its page,
267
+ `tuningfork runs` and `show`. When its first call continued a conversation already going on, a
268
+ long session, its rail lists that conversation first, and each of the person's turns there
269
+ folds to its line.
270
+ - **The README says where your harness points**, what `--upstream` is, and how to change the
271
+ port.
272
+
273
+ Changed:
274
+
275
+ - **A new run is marked a branch of another only where the two share more than their first
276
+ message**, at the message where they part. Two sessions of one harness can share a system
277
+ prompt and nothing else: one was marked a fork of the other, at its own length. Marks recorded
278
+ before stay as they were.
279
+ - **The recordings' database gains a column**, each step's context: it is added when an older
280
+ folder is opened.
281
+
282
+ Claude Code's calls cannot yet carry a conversation before the prompt, so a scenario of a Claude
283
+ Code run starts at its first turn.
284
+
285
+ ## 0.2.3 — on Windows, and a code review's fixes
286
+
287
+ Three rules changed in review, and what was recorded before reads by them now:
288
+
289
+ - **A fork run to the end that timed out counts as a failure**, in a screen, an A/B and a
290
+ resample to the end, as the validation gate already counted it. Left out, an edit that made
291
+ the agent loop until it was stopped counted only its forks that finished, and could survive
292
+ its screen. A screen read again counts its timed-out forks, and its report says how many.
293
+ - **Gate G0 reads a proposal's latest complete screen**, not any screen it survived: a screen
294
+ of more proposals, or one run to the end, can undo an earlier survival. Validation refuses a
295
+ proposal its latest screen dropped, and a validation's gate, read again, fails G0 when a
296
+ screen since has dropped it, and says which.
297
+ - **A fork an experiment drew goes to the trash, and is deleted, only with its experiment**, as
298
+ a thread goes with its run: its verdict is the experiment's. Trashed alone, even deleted for
299
+ good, it still counted. One trashed alone before can be restored, and is never deleted alone.
300
+
301
+ **On Windows** (`docs/setup.md`): installing WSL and a distro, working inside it as on Linux,
302
+ the web UI in the Windows browser, and a model server running on Windows itself. Or keep the
303
+ checkout where Windows editors see it: `scripts/tuningfork.ps1` (PowerShell) and
304
+ `scripts/tuningfork` (Git Bash) run any tuningfork command inside WSL, the venv and the
305
+ recordings in the distro's Linux file system. They run nothing until `TUNINGFORK_WSL_DISTRO`
306
+ names the distro, and hand each argument over as it was typed.
307
+
308
+ Fixed, each found in a review of the code and reproduced before it was fixed:
309
+
310
+ - **The proxy:** a harness that hung up before its answer began kept its slot in the gate for
311
+ good, and with `--max-inflight 1` every later call waited for a restart. `serve --max-output`
312
+ raised a limit written as a float (`1024.0`) to the cap. Ctrl-C continued a cancelled job's
313
+ held calls before dropping them, so a fork killed at a breakpoint still sent its call.
314
+ - **Recording and forks:** a call refused while the proxy was paused kept its step number: the
315
+ run skipped a number, forks of later steps went live a call late, and its last step could not
316
+ be forked. A fork's call at its step, refused that way, lost its "live from here" note.
317
+ - **The trash:** deleting a run for good while a fork of it still ran took the fork's folders
318
+ from under its harness. A launch a batch killed hard stayed running for good and held back
319
+ the trash: launches record the process running them now, and read as interrupted once it has
320
+ gone. Deleting a chain link's run for good deleted the skill the link left.
321
+ - **Counting:** gate G2's "enough runs" counted runs the infrastructure broke. A screen to the
322
+ end from several checkpoints could tilt the original's rate toward one of them. The tally
323
+ named a mistake every run kept as one the passing runs repaired, and counted the half line a
324
+ cut output starts with as a finding. A check output longer than the 2,000 characters a
325
+ verdict keeps says so now: on the run's page, in MCP's `show_run`, in the tally and in a trial
326
+ of the check.
327
+ - **Refinements and approvals:** a record cut off mid-write stayed truncated, and a truncated
328
+ `approval.json` blocked its refinement for good. A crash between an approval's last two
329
+ writes left it open for good: it is reported (`unmarked`) and finished by repair. A changelog
330
+ that is not UTF-8 stopped approvals. A file that mixes line endings was written back all
331
+ CRLF: it takes the ending most of its lines use now, and the plan says so first. A proposal's
332
+ edit matched with its indentation ignored turned that line of a CRLF skill into LF.
333
+ - **Jobs and scenarios:** two cancels of a queued job at once left it queued until a restart;
334
+ the same work submitted twice at once started two jobs; a scenario deleted twice in one
335
+ second landed in the first copy's trash folder.
336
+ - **MCP and the CLI:** `job_status` showed a held-out task's jobs whole, and `list_scenarios`
337
+ its pass rates. Pointed at a proxy on another machine, the CLI read the local file its
338
+ `/health` named and sent it there as the token.
339
+ - **The web UI:** the batch form kept the old spec's task list; a stopped run read "no check";
340
+ a live run's timeline drew its "not recorded" rows again at every update; on the Live page, a
341
+ question still being answered at a held call showed as an error; on the Jobs page, a late
342
+ answer filled the step list of the run chosen last; "Where does it hold?" lost what was typed
343
+ when the page redrew; a job's page could go back to "running" after the job ended; and two
344
+ step forms asked for quickly could leave the wrong one open, or both.
345
+
346
+ ## 0.2.2 — a README for newcomers, and what its pictures showed
347
+
348
+ - **The README, rebuilt for someone deciding in a few minutes whether to try tuningfork:** what
349
+ it does in one figure drawn from real runs, a quickstart with no GPU on the demo lab, where
350
+ every button works, the commands by the question each answers, and the web UI in screenshots
351
+ and a GIF. Reference moves to `docs/setup.md` (installing, the model server's flags and why,
352
+ the harnesses, the sandbox, a task's format), `docs/commands.md` (every command, by the
353
+ question it answers) and `docs/agents.md` (the MCP server, client by client);
354
+ `docs/images/README.md` says how each picture is taken. `tests/test_docs.py` fails on a
355
+ command the reference does not name, and on a link, to a page or an image, that leads nowhere.
356
+ - **`examples/find-the-file/`: a step where the model wavers**, the runs behind the figure,
357
+ Hermes Agent on Gemma 4 31B: ten runs pass 4, the step where they split resampled, the
358
+ model's odds for the words where its reasoning forks, one line of the skill tested there,
359
+ and ten runs with it passing 10, each command shown with its output.
360
+ - **An A/B shows the edit it tested:** its report starts with a line diff of the two texts,
361
+ read back from what it recorded rather than from files that can change since (its page,
362
+ `tuningfork experiment`, MCP's `show_experiment`), where one text holds the skill twice too.
363
+ The demo's seeded A/B swaps a real saved variant.
364
+ - **Green and red only for a check's verdict.** A run's page ends with its verdict, coloured by
365
+ it, where a failed run used to end in a green reply. Any other significant result, an A/B's
366
+ shift or a step screen's survivor, is the accent's colour, its direction said in words, and
367
+ the behaviours a declared target counts are badged *target*, the others *not target*. The
368
+ divergence page colours each run by its own verdict.
369
+ - **A refinement's page leads with its evidence:** where it stands, each proposal's screen and
370
+ the approved one's validation, linked to the detail below. What a proposer is shown moves
371
+ under the proposals, and the skill's paths into a closed *About this refinement*.
372
+ - **A forking-token probe that found nothing says what it could have seen:** "No forced token
373
+ decided what the model did", and the smallest change its continuations would show
374
+ (`detectable`), on its page, the token panel and the CLI.
375
+ - **The server advice is current:** the front page and the glossary recommend
376
+ `--no-cache-prompt`, and `--ctx-checkpoints 0` or `--swa-full`.
377
+ - **Start takes the first click after typing in a field.** The field's change as it lost the
378
+ focus disabled Start under the click, on every form that starts work.
379
+ - **A run stopped before it finished says so,** not that its task defines no check, and a run
380
+ launched from the command line shows its verdict without a reload.
381
+ - **p-values below 0.01 print by their first figure** (`0.005`), never `0.00`. The tally's
382
+ footer, in `tuningfork tally` and MCP's `tally_failures`, says in plain words how it counts:
383
+ an agent reads new words there.
384
+
385
+ ## 0.2.1 — the web UI catches up
386
+
387
+ - **Screens run to the end, from the refinement page.** Its screen form asks how each sample
388
+ is judged — an answer at the step, or a fork run from the step to the end, the task's check
389
+ deciding — and to the end takes the runs to fork from, a quick look, and drawing the
390
+ original anew. A refinement can be started with the task's check as its target. A quick look
391
+ reads as one wherever it shows — the refinement's page and its screens, the screen's own page,
392
+ `refine show` — its rates and never a verdict, not "not significant" against an original of
393
+ 0/0. A screen to the end shows each run it forked from, with each arm's passes there and
394
+ whether the effect held at every one; its page asks Where does it hold? of its forks, arm by
395
+ arm, and folds away the step's behaviours, which are each fork's own. A proposal's chip says
396
+ it survived when any screen found so: it showed the oldest screen's verdict.
397
+ - **What goes with passing**, on the divergence page: the step whose behaviour best predicts
398
+ the verdict, and the tool whose use does, at whatever step a run called it — or that no run
399
+ passed, and where to look then. The divergence report carries the step's split for every
400
+ front end (`verdict_split`), with a run that took the behaviour to look at.
401
+ - **The check run along the runs**, started from the divergence page (the `check-along` job):
402
+ the tally beside it then names the call that made each finding.
403
+ - **A chain, link by link** (the Chains page, `GET /chains`, `/chains/{id}`): how it ran, what
404
+ each link's harness was told, its verdict (or none, where the check never ran), who changed
405
+ the skill after it and the lines they changed. A chain job's page leads to it.
406
+ - **What work cost** beside a run, a job and an experiment (`GET /spend`).
407
+ - **A run's sandbox** on its page: where its harness worked, the folders hidden from it, the
408
+ instant its clock started at, and how the harness was set up.
409
+ - A running job says about how long it has left. The Connections page shows the cap on output,
410
+ and its restart command keeps the cap and the MCP route's settings.
411
+
412
+ ## 0.2.0 — agents that refine skills, and loops to compare them with
413
+
414
+ - **A chain of runs** (`tuningfork chain run`, the Run a task form, the `chain` job kind):
415
+ a task run N times in order, each run given the skill as the run before left it — how a
416
+ harness's own self-improvement accumulates. Each link is recorded and judged as a batch's
417
+ run is; the chain keeps every version of the skill, who changed it, the lines that
418
+ changed, and the last link's folder as what the loop made. With `--tell`, each link's
419
+ harness is given the check's verdict and output as the user's next turn of the same
420
+ session, before its own review: the signal a person who looked at the result would give,
421
+ which a harness's loop otherwise never has. With `--budget-seconds`, the chain stops once
422
+ its links' model calls have taken that long, and a link that could outrun the budget is
423
+ cut to what is left of it, so two loops given the same seconds spend the same seconds.
424
+ - **Options a launch of Hermes takes** (`--harness-option`, `harness_options`): turn its
425
+ self-improvement off (a plugin written into the launch's home removes its skill-editing
426
+ tool, so a run cannot change the skill it is measured with), let its background review
427
+ finish before the process exits (in one-shot mode the review was cut off at exit), make
428
+ the skill review due after every turn, and give it MCP servers — tuningfork's own tools
429
+ among them. Recorded with each run.
430
+ - **The MCP surface oriented for a weak reader.** A 26B driving the refine loop looked for the
431
+ skill as a file, aimed its targets at the first place tool calls differed, and never read a
432
+ failed run's check output; Hermes shows the model no server instructions, so the tools'
433
+ descriptions and answers now say it: show_skill is the only way to read the skill, show_run
434
+ leads with what a failed run's check said and names its last tool call, `show_files` (new)
435
+ shows what a run wrote, find_divergence names the step whose behaviour best predicts the
436
+ verdict or says the difference is in the content, start_refinement refuses a target no
437
+ recorded answer at the step satisfies (`unseen_ok` allows new behaviour) and says when the
438
+ refinement was open already, and the refine toolset's hints walk the refine loop. An edit
439
+ whose old text matches the skill once indentation is ignored is applied with the skill's
440
+ indentation, and a refusal quotes the closest lines as they stand. A refusal is returned as an
441
+ answer, not an error result: Hermes counts error results toward a circuit breaker that cuts a
442
+ server off after three in a row, and an agent that fumbled three edits lost every tool for a
443
+ minute; only tuningfork's own failure is an error result now.
444
+ - **A target a screen can move, and the check as a target.** Each behaviour seen at a step
445
+ now comes with how the recorded runs that took it fared, and a target tried against them
446
+ (step_behaviours, the targets page) says how many passing runs' answers it counts and how
447
+ many failing runs': what a 26B driving the refine loop lacked, having declared a target
448
+ every answer satisfied (20/20 against 20/20) and one no answer could contain (the check's
449
+ own sentence). start_refinement refuses the first as it did the second, and a target the
450
+ failing runs' answers satisfy as readily as the passing runs' (its screen would move nothing
451
+ the verdict follows), with the failing runs' check output in the hint. A fork is now set up
452
+ as its source's launch was (Hermes's `self_improvement`, which changes its system prompt):
453
+ before, a fork of a run recorded with the option departed from the recording at its first
454
+ step, and every sample of a screen to the end was excluded. For a failure only the check can see — a computed property
455
+ of what the run wrote — a refinement's target may be the task's check (`--target-check`,
456
+ `target_check`), and a screen may run to the end (`refine screen --to-end`, the
457
+ `screen-to-end` job kind, `--mcp-tools refine-to-end`): each arm's samples forks from the
458
+ checkpoint through the harness, the check deciding, Fisher and Holm as at the step; the
459
+ record says which measured.
460
+ - **An answer once, for a client that shows both.** A tool result carries its text (the
461
+ sentences, then the JSON) and its structured content (the JSON, led by the sentences)
462
+ because a client shows the model one of them — Claude Code the structured content,
463
+ OpenJarvis the text. Hermes puts both in the tool message, every answer twice, and a 26B
464
+ driving the loop in a 64k context spent it in twenty-two calls, the harness's compaction
465
+ unable to drop a turn of tool calls. `serve --mcp-results text` and `mcp --results text`
466
+ send the text alone (`mcp.server.RESULTS`); the default is unchanged. The same 26B then
467
+ sent an edit without its old text twenty-seven times running, reading each refusal as the
468
+ first: an edit missing a key is now told the shape and the skill's closest line to copy,
469
+ and the same refusal three times running carries the tool's description again, from the
470
+ sixth time with "if it cannot be done, say so and stop" (`ToolServer._repeated`).
471
+ - **Folders hidden from every launch** (`TUNINGFORK_SANDBOX_HIDE`): the sandbox hides a
472
+ launch's own recordings, runs and spec folder; the person can name more, where a
473
+ benchmark keeps the keys its checks read — an agent given the file system was seen to
474
+ find a task's check script. What a launch could not see is kept in its record.
475
+ - **What work cost** (`tuningfork spend`): model calls, tokens in and out, and time, counted
476
+ from the records for a run, an experiment, a job, a chain or a task — the same count for a
477
+ harness's runs and an experiment's samples.
478
+ - **What the checks said, counted, and where it was made.** `tuningfork tally` (MCP's
479
+ tally_failures) reads every failed run's check output at once: each line a check prints is one
480
+ finding, lines that differ only in their numbers or colours are one, each counted once a run
481
+ with its interval, the most common first; a screen run to the end is tallied by arm. An agent
482
+ that opened failed runs one at a time aimed at the first failure it read, not the most common.
483
+ `tuningfork check-along` (the `check-along` job kind) runs the check on a run's workspace as it
484
+ started, after each call that changed it, and at its end, so each finding comes with the call
485
+ that made it — a mistake every run made, and some repaired later, is named as one, with where
486
+ to refine it. `tuningfork where` (MCP's where_it_holds) asks one question of every run — a
487
+ target tried at every step, or a file's pattern at each state of its workspace — lined up with
488
+ the verdicts, with the call that made the difference: what a check that prints only its verdict
489
+ leaves to go on.
490
+ - **The step to refine, chosen by what the run did.** A step can be named by what the run did
491
+ there (`write_file`, `changed:<path>`, `made:<words>`) and is found in each run.
492
+ find_divergence names the tool whose use goes with passing (runs that called it against runs
493
+ that never did), and step_behaviours gives the model's own chance of each tool at the step,
494
+ from scored answers. A screen to the end draws its forks from several runs at the same step
495
+ (`--checkpoints`), passing and failing by turns, and says whether the effect held at each: one
496
+ that lived in a single prefix is one fresh runs draw anew. A refinement at a passing run's
497
+ step is told so, a step screen whose original is at the ceiling says no gain could show there,
498
+ and a refinement toward the check and a screen's plan warn when a call before the step changed
499
+ the workspace, which a fork replays as recorded.
500
+ - **Screens that cost less.** A refinement's original is forked once: a later screen to the end
501
+ counts the original's forks its earlier screens drew at the same checkpoints and draws only
502
+ what its n still needs (`--fresh-original` draws anew). A quick look (`--quick`, `quick`)
503
+ forks each proposal eight times and the original not at all: rates, never a verdict, and a
504
+ lead for the next edit.
505
+ - **Edits and long texts as an agent sends and reads them.** propose_edit takes a line by its
506
+ number — `{line, new}`, `{line, through, new}`, `{after, insert}` — beside `{old, new}`, each
507
+ resolved to an exact replacement, so several in one proposal do not shift each other, and text
508
+ whose line breaks were escaped twice is read as the lines it meant. show_files reads a file or
509
+ a diff from an offset, and show_step one message of a step whole, a page at a time: a file cut
510
+ with no way past the cut had hidden what the check failed. Refusals say more plainly what to
511
+ send: an edit that changes nothing is told what `new` is, and the sixth identical refusal says
512
+ to try another edit, step or tool rather than to stop. A running job says how long is left,
513
+ and `serve --mcp-max-wait` sets how long one MCP call may wait for it.
514
+ - **A cap on output for every call through the proxy** (`serve --max-output N`): a harness's
515
+ call asking for more than N output tokens, or naming no limit, is sent with N — a call a
516
+ person edits at a breakpoint, and a validation's held-out runs, included; the step records
517
+ what the model was sent and what the harness asked, and a fork's replay matches the
518
+ harness's own request. Hermes answers a reply cut at its limit by asking again for more, so
519
+ the cap has to be the proxy's: an agent's reply looped to Hermes's 65,536 tokens for 35
520
+ minutes of one GPU.
521
+ - **Every launch's clock can be pinned** (`TUNINGFORK_CLOCK`): the harness reads the same date
522
+ whichever day a run is made, and a fork starts where its source did.
523
+ - **Fixed:** a line of the original that a longer line of the variant contains is no longer taken
524
+ for a leak (it had voided twenty forks of one screen); a tally masks a hex colour whole; parsing
525
+ a recorded script for its digest no longer prints its warnings.
526
+ - **The guides on Gemma 4 31B.** `examples/README.md` was run again as written on one 32 GB card,
527
+ every output captured there, and every step of Parts 2 to 4 now has a "With Hermes Agent" block:
528
+ Hermes's own edit validated three ways and approved.
529
+ - **A benchmark driver** (`benchmark/driver/run.py`): Hermes's own self-improvement loop, as a
530
+ told chain, against Hermes with tuningfork's tools over MCP — the same task, model and server,
531
+ each arm's final skill judged by fresh runs — with one task, `benchmark/architecture-diagram/`.
532
+ - **A case study** (`examples/hermes-architecture-diagram/`): on Hermes's `architecture-diagram`
533
+ skill, its own loop reached 9/10 by growing the skill, templates of the training answer
534
+ included; Hermes with tuningfork's tools reached 8/10 with an 8-line edit, kept only after it
535
+ beat the original in forks. A demonstration of the tools, with an agent standing in for a
536
+ person.
537
+
538
+ ## 0.1.0 — the first public release
539
+
540
+ A step debugger and experiment lab for agent skills on local LLMs, pre-alpha. What it does:
541
+
542
+ - **Records** every model call of an agent harness, through a proxy that speaks OpenAI
543
+ chat-completions and Anthropic Messages, with no change to the harness. Hermes Agent and
544
+ Claude Code have adapters; any harness that takes a base URL can be recorded.
545
+ - **Finds where runs part**: a batch of runs of a task, judged by the task's check, and the
546
+ first step where they stopped agreeing.
547
+ - **Tests a step, not a whole run**: resamples a step with its history held fixed, swaps an
548
+ edited skill in at that step (an A/B with its target declared before sampling), and follows
549
+ either to the end of the run, where the task's check decides. Every rate comes with its
550
+ interval.
551
+ - **Stops a run where it matters**: live breakpoints before or after the model, at a call that
552
+ matches, with the call held to read, edit, step or drop.
553
+ - **Restarts a run from any step**: the workspace as each call found it, kept in snapshots;
554
+ forks replayed from the recording up to a step and live from there; every launch in a
555
+ bubblewrap sandbox that looks the same each time.
556
+ - **Reads inside a step**, on llama.cpp: how sure the model was of each token of an answer,
557
+ and which token decided the step.
558
+ - **Refines a skill at the step that fails**: a target declared first; edits proposed by a
559
+ person, an agent or the built-in proposer; each screened against the original; a survivor
560
+ validated with full runs, held-out tasks included; and written to the skill only when a
561
+ person approves, typing a code.
562
+ - **All of it from a web UI**, or as tools for an agent over MCP.
563
+
564
+ Two guides walk through it on real runs: `examples/README.md`, a toy skill made to fail one
565
+ way, and `examples/hermes-design-md/`, Hermes Agent's own design-md skill fixed from its
566
+ failing step to an approved edit.