flyloft 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. {flyloft-0.2.0 → flyloft-0.2.2}/.github/workflows/ci.yml +5 -2
  2. {flyloft-0.2.0 → flyloft-0.2.2}/PKG-INFO +2 -2
  3. {flyloft-0.2.0 → flyloft-0.2.2}/docs/ARCHITECTURE.md +8 -3
  4. {flyloft-0.2.0 → flyloft-0.2.2}/docs/NEW_PROJECT.md +6 -6
  5. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/agent-interface.md +8 -8
  6. flyloft-0.2.2/docs/design/cpu-partition.md +122 -0
  7. flyloft-0.2.2/docs/design/tasks.md +77 -0
  8. flyloft-0.2.2/docs/plans/cpu-partition.md +319 -0
  9. flyloft-0.2.2/docs/plans/tasks.md +702 -0
  10. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/clusters/slinky.toml +2 -1
  11. {flyloft-0.2.0 → flyloft-0.2.2}/pyproject.toml +1 -1
  12. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/_version.py +2 -2
  13. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/client/main.py +67 -30
  14. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/client/render.py +3 -1
  15. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/config/load.py +21 -10
  16. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/config/models.py +21 -1
  17. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/knowledge/memory.py +1 -1
  18. flyloft-0.2.2/src/flyloft/migrations/0028_cpu_runs.sql +9 -0
  19. flyloft-0.2.0/src/flyloft/publication/_findings_investigation.md.j2 → flyloft-0.2.2/src/flyloft/publication/_findings_critic_judged.md.j2 +2 -2
  20. flyloft-0.2.2/src/flyloft/publication/_head_critic_judged.md.j2 +37 -0
  21. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/publication/pr_body.md.j2 +3 -3
  22. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/publication/record.md.j2 +3 -3
  23. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/actions.py +47 -14
  24. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/launch.py +5 -3
  25. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/models.py +14 -0
  26. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/store.py +12 -4
  27. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/submit.py +4 -2
  28. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/views.py +2 -1
  29. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/runs/staging.py +22 -5
  30. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/service/tick.py +10 -7
  31. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/skill/SKILL.md +5 -5
  32. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/skill/contract.md +1 -1
  33. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/skill/handoff.md +27 -0
  34. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/tools/server.py +34 -15
  35. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/assembly.py +9 -5
  36. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/launch.py +1 -1
  37. flyloft-0.2.2/src/flyloft/turns/roles/_cj_critic_runs.md.j2 +22 -0
  38. flyloft-0.2.2/src/flyloft/turns/roles/_cj_implementer_rules.md.j2 +11 -0
  39. flyloft-0.2.2/src/flyloft/turns/roles/_cj_record.md.j2 +51 -0
  40. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/critic.investigate.md.j2 +2 -22
  41. flyloft-0.2.2/src/flyloft/turns/roles/critic.task.md.j2 +37 -0
  42. flyloft-0.2.2/src/flyloft/turns/roles/implementer.investigate.md.j2 +32 -0
  43. flyloft-0.2.2/src/flyloft/turns/roles/implementer.task.md.j2 +33 -0
  44. flyloft-0.2.2/src/flyloft/turns/roles/reporter.task.md.j2 +36 -0
  45. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/wake.py +1 -1
  46. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/web/app.py +4 -1
  47. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/web/compute.py +3 -3
  48. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/web/read.py +1 -1
  49. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/web/schemas.py +4 -0
  50. flyloft-0.2.2/src/flyloft/web/static/assets/index-CEib_UXZ.js +244 -0
  51. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/web/static/index.html +1 -1
  52. {flyloft-0.2.0 → flyloft-0.2.2}/tests/conftest.py +21 -3
  53. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_agent_interface.py +1 -1
  54. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_investigate.py +93 -1
  55. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_live.py +12 -1
  56. flyloft-0.2.2/tests/e2e/test_tasks.py +147 -0
  57. flyloft-0.2.2/tests/scripts/cpu_investigator.py +36 -0
  58. flyloft-0.2.2/tests/scripts/cpu_tasker.py +26 -0
  59. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/crew.py +6 -1
  60. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/integrator.py +13 -17
  61. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/integrator_spoil.py +3 -2
  62. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/investigator.py +13 -1
  63. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/live_investigator.py +7 -1
  64. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/reporter.py +16 -0
  65. flyloft-0.2.2/tests/scripts/task_critic.py +27 -0
  66. flyloft-0.2.2/tests/scripts/tasker.py +95 -0
  67. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_assembly.py +56 -3
  68. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_client.py +19 -0
  69. flyloft-0.2.2/tests/unit/test_compute_view.py +58 -0
  70. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_config.py +64 -1
  71. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_investigation.py +5 -5
  72. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_migrate.py +1 -0
  73. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_publication.py +26 -0
  74. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_runs.py +44 -2
  75. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_skill.py +50 -11
  76. flyloft-0.2.2/tests/unit/test_tasks.py +104 -0
  77. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_tools.py +10 -0
  78. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_web_app.py +3 -2
  79. {flyloft-0.2.0 → flyloft-0.2.2}/uv.lock +128 -3
  80. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/api/types.ts +13 -1
  81. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/FinishedRow.tsx +3 -3
  82. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/InvestigationCard.tsx +1 -1
  83. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/StartFromAgent.tsx +2 -2
  84. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/campaign.ts +4 -1
  85. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/crew.test.ts +13 -2
  86. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/crew.ts +8 -6
  87. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/launch.test.ts +4 -1
  88. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/launch.ts +6 -6
  89. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/roles.test.ts +3 -0
  90. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/roles.ts +11 -2
  91. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/Home.tsx +3 -3
  92. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/Launch.tsx +5 -5
  93. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/Run.tsx +4 -1
  94. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/campaign/Frame.tsx +4 -4
  95. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/campaign/Overview.tsx +7 -5
  96. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/campaign/Runs.tsx +3 -2
  97. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/launch/steps.tsx +39 -17
  98. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/project/NewProject.tsx +1 -1
  99. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/project/ValidationPanel.tsx +1 -1
  100. flyloft-0.2.0/src/flyloft/publication/_head_investigation.md.j2 +0 -35
  101. flyloft-0.2.0/src/flyloft/turns/roles/implementer.investigate.md.j2 +0 -90
  102. flyloft-0.2.0/src/flyloft/web/static/assets/index-DLv8ShXP.js +0 -244
  103. {flyloft-0.2.0 → flyloft-0.2.2}/.github/workflows/python-publish.yaml +0 -0
  104. {flyloft-0.2.0 → flyloft-0.2.2}/.gitignore +0 -0
  105. {flyloft-0.2.0 → flyloft-0.2.2}/.python-version +0 -0
  106. {flyloft-0.2.0 → flyloft-0.2.2}/AGENTS.md +0 -0
  107. {flyloft-0.2.0 → flyloft-0.2.2}/CLAUDE.md +0 -0
  108. {flyloft-0.2.0 → flyloft-0.2.2}/README.md +0 -0
  109. {flyloft-0.2.0 → flyloft-0.2.2}/docs/NEW_TEMPLATE.md +0 -0
  110. {flyloft-0.2.0 → flyloft-0.2.2}/docs/VISION.md +0 -0
  111. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/board-1.md +0 -0
  112. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/board-2.md +0 -0
  113. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/board-3.md +0 -0
  114. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/board-4.md +0 -0
  115. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/board-5/campaign.html +0 -0
  116. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/board-5/campaigns.html +0 -0
  117. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/board-5/launch.html +0 -0
  118. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/board-5/pages.html +0 -0
  119. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/board-5.md +0 -0
  120. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/budget-billing.md +0 -0
  121. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/campaign-compute.md +0 -0
  122. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/compute/compute.html +0 -0
  123. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/compute.md +0 -0
  124. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/criterion-removal.md +0 -0
  125. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/flyloft-up.md +0 -0
  126. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/investigate.md +0 -0
  127. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/multi-user.md +0 -0
  128. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/new-project.md +0 -0
  129. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/projects.md +0 -0
  130. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/replication.md +0 -0
  131. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-1.md +0 -0
  132. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-2.md +0 -0
  133. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-3a.md +0 -0
  134. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-3b.md +0 -0
  135. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-4a.md +0 -0
  136. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-4b.md +0 -0
  137. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-5a.md +0 -0
  138. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-5b.md +0 -0
  139. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-5c.md +0 -0
  140. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-5d.md +0 -0
  141. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-6a.md +0 -0
  142. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-6b.md +0 -0
  143. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/stage-7.md +0 -0
  144. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/tier-one-lockstep.md +0 -0
  145. {flyloft-0.2.0 → flyloft-0.2.2}/docs/design/validation.md +0 -0
  146. {flyloft-0.2.0 → flyloft-0.2.2}/docs/environments/deeplearning_v2026-09-15.txt +0 -0
  147. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/agent-interface.md +0 -0
  148. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/board-1.md +0 -0
  149. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/board-2.md +0 -0
  150. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/board-3.md +0 -0
  151. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/board-4.md +0 -0
  152. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/board-5.md +0 -0
  153. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/budget-billing.md +0 -0
  154. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/campaign-compute.md +0 -0
  155. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/compute.md +0 -0
  156. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/criterion-removal.md +0 -0
  157. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/flyloft-up.md +0 -0
  158. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/investigate.md +0 -0
  159. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/multi-user-a.md +0 -0
  160. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/multi-user-b.md +0 -0
  161. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/new-project.md +0 -0
  162. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/projects.md +0 -0
  163. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/replication-a.md +0 -0
  164. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/replication-b.md +0 -0
  165. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/replication-c.md +0 -0
  166. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-1.md +0 -0
  167. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-2.md +0 -0
  168. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-3a.md +0 -0
  169. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-3b.md +0 -0
  170. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-4a.md +0 -0
  171. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-4b.md +0 -0
  172. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-5a.md +0 -0
  173. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-5b.md +0 -0
  174. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-5c.md +0 -0
  175. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-5d.md +0 -0
  176. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-6a.md +0 -0
  177. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-6b.md +0 -0
  178. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/stage-7.md +0 -0
  179. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/tier-one-lockstep.md +0 -0
  180. {flyloft-0.2.0 → flyloft-0.2.2}/docs/plans/validation.md +0 -0
  181. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/flyloft.toml +0 -0
  182. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/builder.toml +0 -0
  183. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/claude.toml +0 -0
  184. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/coordinator.toml +0 -0
  185. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/first_campaign.toml +0 -0
  186. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/generator.toml +0 -0
  187. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/happy_path.toml +0 -0
  188. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/integrator.toml +0 -0
  189. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/lenient_critic.toml +0 -0
  190. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/librarian.toml +0 -0
  191. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/opencode.toml +0 -0
  192. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/orchestrator.toml +0 -0
  193. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/proximity.toml +0 -0
  194. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/reporter.toml +0 -0
  195. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/harnesses/strict_critic.toml +0 -0
  196. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/projects/toy-live-4b.toml +0 -0
  197. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/projects/toy-live-5a.toml +0 -0
  198. {flyloft-0.2.0 → flyloft-0.2.2}/examples/live-config/projects/toy-live.toml +0 -0
  199. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/__init__.py +0 -0
  200. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/budget/__init__.py +0 -0
  201. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/budget/compare.py +0 -0
  202. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/budget/ledger.py +0 -0
  203. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/cli/__init__.py +0 -0
  204. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/cli/main.py +0 -0
  205. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/client/__init__.py +0 -0
  206. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/client/api.py +0 -0
  207. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/client/handoff.py +0 -0
  208. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/config/__init__.py +0 -0
  209. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/config/projects.py +0 -0
  210. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/config/toml.py +0 -0
  211. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/github.py +0 -0
  212. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/host/__init__.py +0 -0
  213. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/host/doctor.py +0 -0
  214. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/host/harnesses/claude.toml +0 -0
  215. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/host/harnesses/opencode.toml +0 -0
  216. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/host/init.py +0 -0
  217. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/host/units.py +0 -0
  218. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/host/up.py +0 -0
  219. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/knowledge/__init__.py +0 -0
  220. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0001_initial.sql +0 -0
  221. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0002_finding_state.sql +0 -0
  222. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0003_several_implementers.sql +0 -0
  223. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0004_entry_order_within_a_transaction.sql +0 -0
  224. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0005_population.sql +0 -0
  225. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0006_memory.sql +0 -0
  226. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0007_islands.sql +0 -0
  227. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0008_blocks.sql +0 -0
  228. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0009_export.sql +0 -0
  229. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0010_failures.sql +0 -0
  230. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0011_unblock.sql +0 -0
  231. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0012_indefinite.sql +0 -0
  232. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0013_pull_requests.sql +0 -0
  233. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0014_run_elapsed.sql +0 -0
  234. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0015_campaign_types.sql +0 -0
  235. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0016_island_aim.sql +0 -0
  236. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0017_campaign_project_config.sql +0 -0
  237. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0018_fresh_record.sql +0 -0
  238. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0019_run_tree.sql +0 -0
  239. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0020_users.sql +0 -0
  240. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0021_token_refused.sql +0 -0
  241. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0022_drop_criterion.sql +0 -0
  242. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0023_campaign_compute.sql +0 -0
  243. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0024_optional_cpus_mem.sql +0 -0
  244. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0025_validation.sql +0 -0
  245. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0026_measurement.sql +0 -0
  246. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/migrations/0027_investigation.sql +0 -0
  247. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/publication/__init__.py +0 -0
  248. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/publication/_findings.md.j2 +0 -0
  249. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/publication/_head.md.j2 +0 -0
  250. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/publication/_macros.md.j2 +0 -0
  251. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/publication/body.py +0 -0
  252. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/publication/export.py +0 -0
  253. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/publication/gh.py +0 -0
  254. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/publication/promotion_pr.md.j2 +0 -0
  255. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/publication/publisher.py +0 -0
  256. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/publication/record_pr.md.j2 +0 -0
  257. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/__init__.py +0 -0
  258. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/consistency.py +0 -0
  259. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/db.py +0 -0
  260. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/islands.py +0 -0
  261. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/migrate.py +0 -0
  262. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/researcher.py +0 -0
  263. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/templates.py +0 -0
  264. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/users.py +0 -0
  265. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/record/validation.py +0 -0
  266. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/runs/__init__.py +0 -0
  267. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/runs/askpass.sh +0 -0
  268. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/runs/capacity.py +0 -0
  269. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/runs/client.py +0 -0
  270. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/runs/collect.py +0 -0
  271. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/runs/observe.py +0 -0
  272. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/runs/ssh.py +0 -0
  273. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/service/__init__.py +0 -0
  274. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/service/factory.py +0 -0
  275. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/tools/__init__.py +0 -0
  276. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/tools/__main__.py +0 -0
  277. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/__init__.py +0 -0
  278. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/__init__.py +0 -0
  279. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/_plan.md.j2 +0 -0
  280. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/bounded.md.j2 +0 -0
  281. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/coordinator.md.j2 +0 -0
  282. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/critic.md.j2 +0 -0
  283. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/direction.md.j2 +0 -0
  284. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/generator.md.j2 +0 -0
  285. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/implementer.md.j2 +0 -0
  286. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/integrator.md.j2 +0 -0
  287. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/librarian.md.j2 +0 -0
  288. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/memory.md.j2 +0 -0
  289. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/orchestrator.md.j2 +0 -0
  290. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/proximity.md.j2 +0 -0
  291. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/reporter.investigate.md.j2 +0 -0
  292. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/roles/reporter.md.j2 +0 -0
  293. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/substrates/__init__.py +0 -0
  294. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/substrates/claude_code.py +0 -0
  295. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/substrates/opencode.py +0 -0
  296. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/turns/substrates/scripted.py +0 -0
  297. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/version.py +0 -0
  298. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/web/__init__.py +0 -0
  299. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/web/artifacts.py +0 -0
  300. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/web/auth.py +0 -0
  301. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/web/events.py +0 -0
  302. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/web/openapi.py +0 -0
  303. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/web/static/assets/index-y1dUR_aG.css +0 -0
  304. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/workspace/__init__.py +0 -0
  305. {flyloft-0.2.0 → flyloft-0.2.2}/src/flyloft/workspace/git.py +0 -0
  306. {flyloft-0.2.0 → flyloft-0.2.2}/tests/__init__.py +0 -0
  307. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/__init__.py +0 -0
  308. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/fixtures/opencode-turn.jsonl +0 -0
  309. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_acting_as_owner.py +0 -0
  310. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_board1.py +0 -0
  311. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_board2.py +0 -0
  312. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_board3.py +0 -0
  313. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_board4.py +0 -0
  314. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_board5.py +0 -0
  315. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_campaign_compute.py +0 -0
  316. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_compute.py +0 -0
  317. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_projects.py +0 -0
  318. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_replication.py +0 -0
  319. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_replication_board.py +0 -0
  320. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage1.py +0 -0
  321. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage2.py +0 -0
  322. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage3.py +0 -0
  323. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage3b.py +0 -0
  324. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage4a.py +0 -0
  325. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage4b.py +0 -0
  326. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage5a.py +0 -0
  327. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage5b.py +0 -0
  328. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage5c.py +0 -0
  329. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage5d.py +0 -0
  330. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage6a.py +0 -0
  331. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage6b.py +0 -0
  332. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_stage7.py +0 -0
  333. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_users.py +0 -0
  334. {flyloft-0.2.0 → flyloft-0.2.2}/tests/e2e/test_validation.py +0 -0
  335. {flyloft-0.2.0 → flyloft-0.2.2}/tests/fakes/__init__.py +0 -0
  336. {flyloft-0.2.0 → flyloft-0.2.2}/tests/fakes/cluster.py +0 -0
  337. {flyloft-0.2.0 → flyloft-0.2.2}/tests/fakes/github.py +0 -0
  338. {flyloft-0.2.0 → flyloft-0.2.2}/tests/fakes/publisher.py +0 -0
  339. {flyloft-0.2.0 → flyloft-0.2.2}/tests/fixtures/slinky/capacity.txt +0 -0
  340. {flyloft-0.2.0 → flyloft-0.2.2}/tests/fixtures/slinky/cluster.toml +0 -0
  341. {flyloft-0.2.0 → flyloft-0.2.2}/tests/fixtures/toyproject/BRIEF.md +0 -0
  342. {flyloft-0.2.0 → flyloft-0.2.2}/tests/fixtures/toyproject/config.json +0 -0
  343. {flyloft-0.2.0 → flyloft-0.2.2}/tests/fixtures/toyproject/train.py +0 -0
  344. {flyloft-0.2.0 → flyloft-0.2.2}/tests/host/__init__.py +0 -0
  345. {flyloft-0.2.0 → flyloft-0.2.2}/tests/host/test_host.py +0 -0
  346. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/__init__.py +0 -0
  347. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/builder.py +0 -0
  348. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/common.py +0 -0
  349. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/coordinator.py +0 -0
  350. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/crash.py +0 -0
  351. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/failing_job.py +0 -0
  352. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/first_campaign.py +0 -0
  353. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/flaky_builder.py +0 -0
  354. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/generator.py +0 -0
  355. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/happy_path.py +0 -0
  356. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/investigator_quiet.py +0 -0
  357. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/lenient_critic.py +0 -0
  358. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/librarian.py +0 -0
  359. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/missing_results.py +0 -0
  360. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/near_far.py +0 -0
  361. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/orchestrator.py +0 -0
  362. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/plateau.py +0 -0
  363. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/post_then_crash.py +0 -0
  364. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/proximity.py +0 -0
  365. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/replication.py +0 -0
  366. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/retracting.py +0 -0
  367. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/silent_exit.py +0 -0
  368. {flyloft-0.2.0 → flyloft-0.2.2}/tests/scripts/strict_critic.py +0 -0
  369. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/__init__.py +0 -0
  370. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/conftest.py +0 -0
  371. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/island_helpers.py +0 -0
  372. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/memory_helpers.py +0 -0
  373. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_actions.py +0 -0
  374. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_budget.py +0 -0
  375. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_capacity.py +0 -0
  376. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_claude_code.py +0 -0
  377. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_cli.py +0 -0
  378. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_client_main.py +0 -0
  379. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_consistency.py +0 -0
  380. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_format_metrics.py +0 -0
  381. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_gh.py +0 -0
  382. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_github.py +0 -0
  383. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_host_doctor.py +0 -0
  384. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_host_init.py +0 -0
  385. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_host_units.py +0 -0
  386. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_host_up.py +0 -0
  387. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_islands.py +0 -0
  388. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_knowledge.py +0 -0
  389. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_launch.py +0 -0
  390. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_measurement.py +0 -0
  391. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_opencode.py +0 -0
  392. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_population.py +0 -0
  393. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_projects.py +0 -0
  394. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_scripted.py +0 -0
  395. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_service.py +0 -0
  396. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_ssh.py +0 -0
  397. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_store.py +0 -0
  398. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_submit.py +0 -0
  399. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_templates.py +0 -0
  400. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_toyproject.py +0 -0
  401. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_transcript.py +0 -0
  402. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_users.py +0 -0
  403. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_wake.py +0 -0
  404. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_web_artifacts.py +0 -0
  405. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_web_compare.py +0 -0
  406. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_web_events.py +0 -0
  407. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_web_read.py +0 -0
  408. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/test_workspace.py +0 -0
  409. {flyloft-0.2.0 → flyloft-0.2.2}/tests/unit/web_helpers.py +0 -0
  410. {flyloft-0.2.0 → flyloft-0.2.2}/web/components.json +0 -0
  411. {flyloft-0.2.0 → flyloft-0.2.2}/web/index.html +0 -0
  412. {flyloft-0.2.0 → flyloft-0.2.2}/web/package-lock.json +0 -0
  413. {flyloft-0.2.0 → flyloft-0.2.2}/web/package.json +0 -0
  414. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/App.tsx +0 -0
  415. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/api/client.test.ts +0 -0
  416. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/api/client.ts +0 -0
  417. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/api/live.ts +0 -0
  418. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/api/queries.ts +0 -0
  419. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/Actions.tsx +0 -0
  420. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/AnswerValue.tsx +0 -0
  421. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/Breadcrumbs.tsx +0 -0
  422. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/CampaignCard.tsx +0 -0
  423. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/CampaignFilter.tsx +0 -0
  424. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/ChangeMarkers.tsx +0 -0
  425. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/DotBanner.tsx +0 -0
  426. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/EntityLink.tsx +0 -0
  427. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/ErrorNote.tsx +0 -0
  428. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/Freshness.tsx +0 -0
  429. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/LogList.tsx +0 -0
  430. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/ProgressChart.tsx +0 -0
  431. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/RoleChips.tsx +0 -0
  432. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/RoleNode.tsx +0 -0
  433. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/Selectable.tsx +0 -0
  434. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/Sidebar.tsx +0 -0
  435. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/Step.tsx +0 -0
  436. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/Story.tsx +0 -0
  437. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/SwarmMap.tsx +0 -0
  438. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/Timeline.tsx +0 -0
  439. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/Timestamp.tsx +0 -0
  440. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/Transcript.tsx +0 -0
  441. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/ui/alert-dialog.tsx +0 -0
  442. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/ui/button.tsx +0 -0
  443. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/ui/popover.tsx +0 -0
  444. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/ui/sheet.tsx +0 -0
  445. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/ui/tabs.tsx +0 -0
  446. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/components/ui/tooltip.tsx +0 -0
  447. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/campaign.test.ts +0 -0
  448. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/chart.test.ts +0 -0
  449. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/chart.ts +0 -0
  450. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/compute.test.ts +0 -0
  451. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/compute.ts +0 -0
  452. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/format.test.ts +0 -0
  453. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/format.ts +0 -0
  454. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/projects.ts +0 -0
  455. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/runs.test.ts +0 -0
  456. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/runs.ts +0 -0
  457. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/seen.test.ts +0 -0
  458. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/seen.ts +0 -0
  459. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/story.test.ts +0 -0
  460. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/story.ts +0 -0
  461. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/swarm.test.ts +0 -0
  462. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/swarm.ts +0 -0
  463. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/lib/utils.ts +0 -0
  464. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/main.tsx +0 -0
  465. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/Admin.tsx +0 -0
  466. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/Compare.tsx +0 -0
  467. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/Compute.tsx +0 -0
  468. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/Login.tsx +0 -0
  469. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/Projects.tsx +0 -0
  470. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/Turn.tsx +0 -0
  471. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/admin/Host.tsx +0 -0
  472. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/admin/Users.tsx +0 -0
  473. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/campaign/Activity.tsx +0 -0
  474. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/campaign/Hypotheses.tsx +0 -0
  475. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/campaign/context.ts +0 -0
  476. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/pages/project/ProjectPage.tsx +0 -0
  477. {flyloft-0.2.0 → flyloft-0.2.2}/web/src/styles.css +0 -0
  478. {flyloft-0.2.0 → flyloft-0.2.2}/web/tsconfig.json +0 -0
  479. {flyloft-0.2.0 → flyloft-0.2.2}/web/vite.config.ts +0 -0
@@ -76,11 +76,14 @@ jobs:
76
76
 
77
77
  client:
78
78
  runs-on: ubuntu-latest
79
+ strategy:
80
+ matrix:
81
+ python: ["3.11", "3.12"]
79
82
  steps:
80
83
  - uses: actions/checkout@v4
81
84
  with:
82
85
  fetch-depth: 0
83
86
  - uses: astral-sh/setup-uv@v6
84
- - run: uv venv client-venv && uv pip install --python client-venv/bin/python .
87
+ - run: uv venv client-venv --python ${{ matrix.python }} && uv pip install --python client-venv/bin/python .
85
88
  - run: client-venv/bin/flyloft --help
86
- - run: client-venv/bin/flyloft install-skill
89
+ - run: client-venv/bin/flyloft install --harness claude
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: flyloft
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Agent swarms for empirical research on Slurm clusters
5
- Requires-Python: >=3.12
5
+ Requires-Python: >=3.11
6
6
  Requires-Dist: httpx>=0.27
7
7
  Requires-Dist: typer>=0.12
8
8
  Provides-Extra: server
@@ -1,6 +1,6 @@
1
1
  # flyloft Architecture
2
2
 
3
- Status: approved 2026-09-21. Stage 1 complete on 2026-09-23 (see [design/stage-1.md](design/stage-1.md)); stage 2 complete on 2026-09-23 (tiers one, two, and three passed; see [design/stage-2.md](design/stage-2.md)); stage 3a complete (see [design/stage-3a.md](design/stage-3a.md)). Stage 3b complete (see [design/stage-3b.md](design/stage-3b.md)). Stage 4a complete on 2026-09-25 (tiers one, two, and three passed; see [design/stage-4a.md](design/stage-4a.md)). Stage 4b complete on 2026-09-25 (tiers one, two, and three passed; see [design/stage-4b.md](design/stage-4b.md)). Stage 5a complete on 2026-09-27 (tiers one, two, and three passed; see [design/stage-5a.md](design/stage-5a.md)). Stage 5b complete on 2026-09-27 (tiers one and three passed; see [design/stage-5b.md](design/stage-5b.md)). Stage 5c complete on 2026-09-28 (tiers one and three passed; see [design/stage-5c.md](design/stage-5c.md)). Stage 5d complete on 2026-09-28 (tiers one and three passed; see [design/stage-5d.md](design/stage-5d.md)). Stage 6a complete on 2026-09-29 (tiers one and three passed; see [design/stage-6a.md](design/stage-6a.md)). Stage 6b complete on 2026-09-30 (tiers one and three passed; see [design/stage-6b.md](design/stage-6b.md)). Stage 7 complete on 2026-09-30 (tiers one, two, and three passed; see [design/stage-7.md](design/stage-7.md)). Every staged part is built. The vision's revision of 2026-10-13 widens flyloft to two modes, investigate and optimize, with a lab-wide toolshed; what comes next is in §6, *What comes next*. The Projects rework's board flow and template repositories are built and closed on 2026-10-19 (tiers one, two, and three passed; see [design/projects.md](design/projects.md)); the investigate mode is next, in four parts, the first built (see [design/investigate.md](design/investigate.md)); then the agent interface, where the researcher's own coding agent starts work and the board is where they come back to (see [design/agent-interface.md](design/agent-interface.md)).
3
+ Status: approved 2026-09-21. Stage 1 complete on 2026-09-23 (see [design/stage-1.md](design/stage-1.md)); stage 2 complete on 2026-09-23 (tiers one, two, and three passed; see [design/stage-2.md](design/stage-2.md)); stage 3a complete (see [design/stage-3a.md](design/stage-3a.md)). Stage 3b complete (see [design/stage-3b.md](design/stage-3b.md)). Stage 4a complete on 2026-09-25 (tiers one, two, and three passed; see [design/stage-4a.md](design/stage-4a.md)). Stage 4b complete on 2026-09-25 (tiers one, two, and three passed; see [design/stage-4b.md](design/stage-4b.md)). Stage 5a complete on 2026-09-27 (tiers one, two, and three passed; see [design/stage-5a.md](design/stage-5a.md)). Stage 5b complete on 2026-09-27 (tiers one and three passed; see [design/stage-5b.md](design/stage-5b.md)). Stage 5c complete on 2026-09-28 (tiers one and three passed; see [design/stage-5c.md](design/stage-5c.md)). Stage 5d complete on 2026-09-28 (tiers one and three passed; see [design/stage-5d.md](design/stage-5d.md)). Stage 6a complete on 2026-09-29 (tiers one and three passed; see [design/stage-6a.md](design/stage-6a.md)). Stage 6b complete on 2026-09-30 (tiers one and three passed; see [design/stage-6b.md](design/stage-6b.md)). Stage 7 complete on 2026-09-30 (tiers one, two, and three passed; see [design/stage-7.md](design/stage-7.md)). Every staged part is built. The vision's revision of 2026-10-13 widens flyloft to two modes, investigate and optimize, with a lab-wide toolshed; what comes next is in §6, *What comes next*. The Projects rework's board flow and template repositories are built and closed on 2026-10-19 (tiers one, two, and three passed; see [design/projects.md](design/projects.md)); the investigate mode is next, in four parts, the first built (see [design/investigate.md](design/investigate.md)); then the agent interface, where the researcher's own coding agent starts work and the board is where they come back to (see [design/agent-interface.md](design/agent-interface.md)). A critic-judged campaign's 0-GPU runs go to its cluster's CPU partition, sized within a CPU-run limit (see [design/cpu-partition.md](design/cpu-partition.md)).
4
4
 
5
5
  This document settles the shape of flyloft: the record that holds its state, the lifecycle and authority rules, the agent turn, the subsystem boundaries, and the stages by which the system grows. It fixes what is expensive to change later and leaves everything else to the design document of the stage that needs it. [VISION.md](VISION.md) says what flyloft is for; [AGENTS.md](../AGENTS.md) says how to work on it. This document is the bridge between them.
6
6
 
@@ -197,7 +197,7 @@ Two processes write to the record from the first turn, the service and the tool
197
197
 
198
198
  **The Compute page.** After campaign-level compute, its own change ([design/compute.md](design/compute.md)): a sidebar page where each cluster says what each GPU type its file lists has (free GPUs, flyloft's running and queued jobs, nodes, CPUs, memory, longest run, and most free on a node), cached for 30 seconds and asked per cluster over the runs module's SSH client; the launch page's GPU type select shows each type's free count.
199
199
 
200
- **What comes next.** The vision's revision of 2026-10-13 widens flyloft from optimization to two modes, investigate and optimize, and adds a toolshed shared across the lab. Stages 1 through 7 built the optimize mode. What follows is staged like everything before it, each part with its own scenario and a design document written when it begins: the Projects rework, which makes bringing a project one flow on the board and turns templates into GitHub template repositories registered with flyloft (see [design/projects.md](design/projects.md)); then the investigate mode, in four parts: a solo, exploratory investigation whose project may be only a brief ([design/investigate.md](design/investigate.md)); a test fixed in advance, making findings confirmed; the toolshed; and investigation at swarm size. Then the agent interface ([design/agent-interface.md](design/agent-interface.md)): the researcher's coding agent is flyloft's front door (sign-in from a laptop, `submit` of a brief that flyloft turns into a repository and a campaign, a `flyloft` client with every initiating action, a skill), the host's commands move under `flyloft host` and admin actions under `flyloft admin`, and the board becomes where people monitor and steer; its second part adds tasks, a third label on the investigation engine. Their mechanics are decided in their design documents, not here.
200
+ **What comes next.** The vision's revision of 2026-10-13 widens flyloft from optimization to two modes, investigate and optimize, and adds a toolshed shared across the lab. Stages 1 through 7 built the optimize mode. What follows is staged like everything before it, each part with its own scenario and a design document written when it begins: the Projects rework, which makes bringing a project one flow on the board and turns templates into GitHub template repositories registered with flyloft (see [design/projects.md](design/projects.md)); then the investigate mode, in four parts: a solo, exploratory investigation whose project may be only a brief ([design/investigate.md](design/investigate.md)); a test fixed in advance, making findings confirmed; the toolshed; and investigation at swarm size. Then the agent interface ([design/agent-interface.md](design/agent-interface.md)): the researcher's coding agent is flyloft's front door (sign-in from a laptop, `submit` of a brief that flyloft turns into a repository and a campaign, a `flyloft` client with every initiating action, a skill), the host's commands move under `flyloft host` and admin actions under `flyloft admin`, and the board becomes where people monitor and steer; its second part adds tasks, a third label on the investigation engine ([design/tasks.md](design/tasks.md)); both are built and closed (2026-10-22). Their mechanics are decided in their design documents, not here.
201
201
 
202
202
  Recovery is built when a stage loses work, most likely stage three, when a service restart first interrupts many turns at once. It is not scheduled.
203
203
 
@@ -377,16 +377,21 @@ Ideas that have arrived before their stage. Adding a line here is how an impulse
377
377
  | Searching or filtering the template list in New project | When a lab has more templates than fit on a screen |
378
378
  | Memory reading investigations: an earlier investigation's account and findings in a later campaign's assignment (today memory skips investigations both ways) | When an optimize campaign follows an investigation on the same project |
379
379
  | A size cap on an investigation run's collected `out/` | When a run is seen copying something large back to the host |
380
+ | Computed completion for tasks: a checklist the ask is parsed into | When critics are seen accepting incomplete outputs |
380
381
  | Two campaigns writing a project's work directory at once | When two are seen colliding there |
381
382
  | An "Empty project" choice on New project that writes the brief on the board and creates the repository | Superseded by `flyloft submit`: see [design/agent-interface.md](design/agent-interface.md) |
382
383
  | The toolshed (part 3 of investigate) and a data equivalent: where the lab's datasets live, how they are versioned, what has been fetched and prepared | Near-term, after the agent interface; each its own design |
383
384
  | A Claude Code plugin and marketplace for the flyloft skill (the skill's directory plus a manifest) | When a lab member wants it from a marketplace, or a hook or second component is needed |
384
- | The flyloft skill for Codex and OpenCode | When a lab member uses one |
385
+ | The flyloft skill for Codex and OpenCode | Done 2026-10-22: `flyloft install` serves every harness found |
385
386
  | `flyloft logout`, several hosts per laptop, refreshing a session before it expires | When one is missed |
386
387
  | Deleting the board's initiation flows (New project, registration, the launch wizard) and the database-direct `flyloft host campaign` and `host project` commands | When the agent interface is seen to cover what people and operators use them for |
387
388
  | flyloft as a monitor and troubleshooter of the lab's compute, from what it already observes (node and job states, failure diagnostics, capacity) | When the agent interface and tasks are in use |
388
389
  | HTTPS for the board and the client (a 30-day session crosses the intranet in the clear: an accepted risk for the first version, see [design/agent-interface.md](design/agent-interface.md) §3) | When the host leaves netbird or a lab policy asks |
389
390
  | An MCP catalogue of knowledge sources (bioRxiv, PubMed, ChEMBL) for flyloft's own agents: per-role permissions, credentials, and what a source returned on the record; with the toolshed, in one lab-owned repository | With the toolshed, when the first investigation needs a source its agents cannot reach |
391
+ | A CPU row on the Compute page and in `flyloft options` ([design/cpu-partition.md](design/cpu-partition.md) §6) | When a launch goes wrong for want of it |
392
+ | A CPU budget ([design/cpu-partition.md](design/cpu-partition.md) §6) | When CPU work on the shared node needs a bound beyond run slots and the CPU-run limit |
393
+ | Choosing a GPU type per run within a campaign; budgeting must then weigh the types ([design/cpu-partition.md](design/cpu-partition.md) §6) | When a campaign needs two GPU types |
394
+ | The request and the render of a CPU run disagreeing when `cpu_partition` changes while a run is preparing; the fix is to key `_is_cpu_run` on `run.cpus is not None` | When it is observed |
390
395
 
391
396
  ## References
392
397
 
@@ -70,9 +70,9 @@ What follows from it:
70
70
  training loop, evaluation, and output), only smaller: a smoke path that skips the evaluation hides the bugs
71
71
  validation exists to find.
72
72
 
73
- ## 2a. A project for investigation
73
+ ## 2a. A project for investigation or a task
74
74
 
75
- A researcher who wants a question investigated, not a metric optimized, needs no run command. The repository may hold
75
+ A researcher who wants a question investigated or a task done, not a metric optimized, needs no run command. The repository may hold
76
76
  only its brief: the agents write everything else on the campaign branch. The brief says:
77
77
 
78
78
  - what the project is and the question's background, in a paragraph;
@@ -81,14 +81,14 @@ only its brief: the agents write everything else on the campaign branch. The bri
81
81
  - what agents must not do (no training on a held-out set, no more than one GPU per run).
82
82
 
83
83
  The usual path is `flyloft submit` from the researcher's own agent: on the researcher's laptop, `pip install flyloft`,
84
- `flyloft login --host <board URL>`, and `flyloft install-skill --harness claude-code` give the agent the flyloft skill,
85
- whose `handoff.md` holds the brief template. The agent writes the brief and an investigation file, runs
84
+ `flyloft login --host <board URL>`, and `flyloft install` give the agent (Claude Code, Codex, or OpenCode) the flyloft skill,
85
+ whose `handoff.md` holds the brief template. The agent writes the brief and an investigation or task file, runs
86
86
  `flyloft submit <directory> --name <name> --campaign <file> --check` until it reports no problem, and submits: flyloft
87
- creates the repository under the lab's organization, registers the project, and launches the investigation.
87
+ creates the repository under the lab's organization, registers the project, and launches the campaign. A task is submitted the same way: `task.toml` with `type = "task"`, and the brief says what needs doing and what done looks like.
88
88
 
89
89
  To register a repository by hand instead, use **My own repository** on **New project**, or `flyloft register <url>
90
90
  --name <name>`, leaving the run command and results path blank; there is nothing to validate. Launch it with
91
- **Investigate a question**. Every job an investigation runs has
91
+ **Investigate a question** or **Do a task**. Every job a critic-judged campaign runs has
92
92
  `$FLYLOFT_PROJECT_WORK`, the project's directory on the cluster, where environments, weights, and data stay for later
93
93
  campaigns, and `$FLYLOFT_OUT`, whose contents come back to the board. When the account recommends an optimization, add
94
94
  a run command that meets section 2, validate it, and launch **Optimize a metric** on the same project: what the
@@ -24,7 +24,7 @@ VISION.md was revised with the human section by section on 2026-10-20: the openi
24
24
 
25
25
  ## 2. Scenario
26
26
 
27
- *Once per researcher.* `pip install flyloft` on the laptop; `flyloft install-skill --harness claude-code`; `flyloft login --host http://<board>`: a device code appears, they approve on GitHub, and the client prints "signed in to flyloft as briney". Someone not on the allowlist is refused with who to ask.
27
+ *Once per researcher.* `pip install flyloft` on the laptop; `flyloft install` (the skill, into every harness found: Claude Code, Codex, OpenCode); `flyloft login --host http://<board>`: a device code appears, they approve on GitHub, and the client prints "signed in to flyloft as briney". Someone not on the allowlist is refused with who to ask.
28
28
 
29
29
  *The slow cooker.*
30
30
 
@@ -57,7 +57,7 @@ The laptop holds only a flyloft session, never a GitHub token. The GitHub token
57
57
 
58
58
  **Transport.** The host's network is the lab's intranet, and the board stays plain HTTP: an accepted risk for the first version, since a 30-day session crosses that network in the clear. HTTPS is on the deferred list, to be picked up when the host leaves netbird or a lab policy asks.
59
59
 
60
- **Versions.** Every request carries `X-Flyloft-Client: <version>` and every reply `X-Flyloft-Host: <version>`, both the one package's version. The host holds `MIN_CLIENT`, bumped whenever the API changes incompatibly; an older client gets 426 with "your flyloft client is 1.4.0; this host needs 1.6.0 or newer: `pip install -U flyloft`", which the skill treats as an instruction. A client newer than the host prints one warning line ("this host runs 1.5.0; some commands may not work") and continues; the API rejects unknown request fields rather than dropping them, so a new field never vanishes quietly. `install-skill` writes the client's version into `SKILL.md`, and the client warns once per command when the installed skill's version differs ("run `flyloft install-skill`"). No compatibility framework beyond this: one supported pairing, the newest of each.
60
+ **Versions.** Every request carries `X-Flyloft-Client: <version>` and every reply `X-Flyloft-Host: <version>`, both the one package's version. The host holds `MIN_CLIENT`, bumped whenever the API changes incompatibly; an older client gets 426 with "your flyloft client is 1.4.0; this host needs 1.6.0 or newer: `pip install -U flyloft`", which the skill treats as an instruction. A client newer than the host prints one warning line ("this host runs 1.5.0; some commands may not work") and continues; the API rejects unknown request fields rather than dropping them, so a new field never vanishes quietly. `install` writes the client's version into `SKILL.md`, and the client warns once per command when the installed skill's version differs ("run `flyloft install`"). No compatibility framework beyond this: one supported pairing, the newest of each.
61
61
 
62
62
  ## 4. `submit` on the host
63
63
 
@@ -101,7 +101,7 @@ The reply carries the project, the repository URL, the campaign id, and the boar
101
101
  | `stop ID` | `POST /api/campaigns/{id}/stop` |
102
102
  | `admin user list / add LOGIN [--admin] / remove LOGIN / promote LOGIN / demote LOGIN` | `GET/POST/PATCH/DELETE /api/users…`, 403 for a non-admin |
103
103
  | `admin user logout LOGIN` | `DELETE /api/users/{login}/sessions` |
104
- | `install-skill --harness claude-code` | copies the packaged skill to `~/.claude/skills/flyloft/`, overwriting, with the client's version written in |
104
+ | `install [--harness claude\|codex\|opencode]` | copies the packaged skill, with the client's version written in, to `~/.claude/skills/flyloft/` for Claude Code and `~/.agents/skills/flyloft/` for Codex and OpenCode (both read it); without `--harness`, every harness whose CLI is on `PATH`; overwriting |
105
105
 
106
106
  `status` with no id lists the researcher's campaigns, one line each (state, kind, project, what is running or the latest finding, budget); with an id it shows the experiments or runs, the findings, the budget, the notes, and the account once written.
107
107
 
@@ -120,12 +120,12 @@ The client ships in the existing `flyloft` package. The researcher's commands ar
120
120
  5. **Checking in**: `status`, then `status ID`, leading with the account when there is one. `steer` and `stop` only when asked.
121
121
  6. **Graduating**, when an account recommends an optimization and the researcher wants it: the investigation's code is on its branch behind a draft pull request, so have the researcher review and merge it first; then clone, wire the contract, push, `project set`, `validate` (read the report; fix what it names), `launch` with the metric and target the account named.
122
122
  7. **Bringing an existing repository**: the contract, `register`, `validate`, `launch`.
123
- 8. **Refusals** are the host's own sentences: fix what they name and retry; never work around one, such as by creating a repository by hand. A 426 means the client is too old: run `pip install -U flyloft` and retry; a skill-version warning means `flyloft install-skill`.
123
+ 8. **Refusals** are the host's own sentences: fix what they name and retry; never work around one, such as by creating a repository by hand. A 426 means the client is too old: run `pip install -U flyloft` and retry; a skill-version warning means `flyloft install`.
124
124
  9. **Administering**, only when the researcher is an admin and asks: `flyloft admin user …`; a 403 means they are not.
125
125
 
126
126
  ## 7. The board, `flyloft host`, and `flyloft admin`
127
127
 
128
- The board shows every project and campaign and lets people steer and stop. This part adds a "Start new work from your agent" panel under the home page's campaign list (also its empty state): `pip install flyloft`, `flyloft login --host <this board's URL>`, `flyloft install-skill --harness claude-code`, with the URL filled in; the launch page's first line points to it. The Admin page gains "end every session" per user. New project, registration, and the launch wizard stay as they are.
128
+ The board shows every project and campaign and lets people steer and stop. This part adds a "Start new work from your agent" panel under the home page's campaign list (also its empty state): `pip install flyloft`, `flyloft login --host <this board's URL>`, `flyloft install`, with the URL filled in; the launch page's first line points to it. The Admin page gains "end every session" per user. New project, registration, and the launch wizard stay as they are.
129
129
 
130
130
  `flyloft host` holds every host-side operator command, unchanged in behavior: `serve`, `web`, `up`, `init`, `doctor`, `db migrate`, host `status`, the operator's `login`, the database-direct `campaign` and `project` groups, and `user`, which is the bootstrap: `flyloft host user add --admin LOGIN` makes the first admin on a fresh host, before anyone can sign in; afterwards admins use `flyloft admin user …` over the API, from anywhere. `flyloft admin` holds only what needs the admin role.
131
131
 
@@ -135,7 +135,7 @@ The board shows every project and campaign and lets people steer and stop. This
135
135
 
136
136
  **Tier one** (`tests/e2e/test_agent_interface.py`: the fake cluster, the stand-in GitHub with device endpoints and organization repositories, scripted agents; the client through `CliRunner` against the board app served over HTTP, as the browser tests serve it):
137
137
 
138
- - `login` (the stand-in approves; a `slow_down` is honored), `options`, `install-skill` into a temporary home, `submit --check` (every problem at once, the manifest), `submit` of a brief and plan: the stand-in holds `brineylab/<name>` created with the researcher's token, the implementer's assignment carries the plan, and the scripted investigator runs it to an account that `status` shows; `steer` and `stop` on a second submission; `logout`, after which the next command is a 401.
138
+ - `login` (the stand-in approves; a `slow_down` is honored), `options`, `install` into a temporary home, `submit --check` (every problem at once, the manifest), `submit` of a brief and plan: the stand-in holds `brineylab/<name>` created with the researcher's token, the implementer's assignment carries the plan, and the scripted investigator runs it to an account that `status` shows; `steer` and `stop` on a second submission; `logout`, after which the next command is a 401.
139
139
  - Graduation: merge the investigation's pull request on the stand-in, clone, add the toy project's run contract, push; `project set`, `validate`, `launch` an optimization run by the scripted crew.
140
140
  - `register` of the toy repository, `validate`, `launch`.
141
141
  - Refusals, each leaving nothing on the stand-in GitHub: no brief; a refused campaign file; a name taken (the refusal names the existing project and campaign); a researcher not on the allowlist; an expired session; an optimization on a brief-only project; a `.env` in the directory (not sent), a tracked file holding a `ghp_` token (refused with its line), a symlink (not followed), a 25 MB file; a client below `MIN_CLIENT` (426 with the upgrade line); a client newer than the host (one warning line, then the output).
@@ -143,11 +143,11 @@ The board shows every project and campaign and lets people steer and stop. This
143
143
  - The board's panel shows this board's URL, and the Admin page ends a user's sessions (browser).
144
144
  - The skill: every command and flag it names exists in the client, and its text states the budget's overshoot and names `submit --check` and the merge-before-graduation step.
145
145
  - The host's commands under `host`: the existing suite passes with its invocations moved; a unit test writes units with the old names, upgrades, and finds them rewritten and the aliases answering.
146
- - CI installs the package without extras and runs `flyloft --help`, `flyloft install-skill`, and the client's unit tests, so a server import in the client fails there, not on a researcher's laptop.
146
+ - CI installs the package without extras and runs `flyloft --help`, `flyloft install`, and the client's unit tests, so a server import in the client fails there, not on a researcher's laptop.
147
147
 
148
148
  **Tier two** (live, no model tokens): `submit` against real GitHub creates a private `brineylab/flyloft-tier2-<date>` (the human deletes it afterward: flyloft's token has no `delete_repo`); a scripted investigator runs one job on slinky; `status` reads it back. The session is created on the host, since `login` needs a person at GitHub.
149
149
 
150
- **Tier three:** the human, with Claude Code: `pip install flyloft`, `install-skill`, `login`, the ESM-C conversation, `submit`, and `status` from the harness days later. It is investigate part 1's tier three as well.
150
+ **Tier three:** the human, with Claude Code: `pip install flyloft`, `install`, `login`, the ESM-C conversation, `submit`, and `status` from the harness days later. It is investigate part 1's tier three as well.
151
151
 
152
152
  ## 9. Part 2: tasks
153
153
 
@@ -0,0 +1,122 @@
1
+ # The CPU Partition: Design
2
+
3
+ Status: approved 2026-10-22; widened the same day to tasks after [tasks.md](tasks.md) merged; built on branch `feature/cpu-partition`. Tier one passed (625 passed, 9 skipped; two `test_client` failures are environmental on the build host). Tier two passed on bcr: the setup run went to partition `cpu` with `cpu=8, mem=16G` and no GPU; the GPU run went to `l40s` with its share of the node (`cpu=48, gres/gpu=1, mem=179G`) and imported the environment the CPU run built. Follows investigate, part 1 ([investigate.md](investigate.md)) and tasks ([tasks.md](tasks.md)): it sends the 0-GPU runs of both critic-judged types to a cluster's CPU nodes.
4
+
5
+ Throughout, *critic-judged* means an investigation or a task (`Campaign.critic_judged`), the two types whose implementer sizes its own runs. Optimize campaigns give every run the campaign's resources and at least one GPU, so they have no 0-GPU runs and nothing here touches them.
6
+
7
+ A critic-judged campaign's implementer already sizes each run: its command, GPUs from zero to the campaign's ceiling, and time limit. A run that asks for no GPUs is a setup run, but it still goes to the partition of the campaign's GPU type and gets the campaign's CPUs and memory. On a GPU partition with no CPUs set, that is one CPU. That is useless for real data work, and it takes CPUs and memory from GPU nodes.
8
+
9
+ This change sends a no-GPU run to the cluster's CPU partition, when the cluster names one. It gives such a run its own CPUs and memory, within a CPU-run limit the researcher sets at launch. GPU runs are untouched. Their CPUs and memory stay what the campaign names, or their share of the node, so that a GPU job never takes the CPUs its node's other GPUs need.
10
+
11
+ ## 1. Scenario
12
+
13
+ The researcher retests curriculum learning for antibody language models on held-out donors. The paired data is an existing release. The unpaired data must be rebuilt first: a fresh download of OAS unpaired sequences, filtered, held-out donors removed, and clustered at 95% with mmseqs `linclust`. That is a CPU job of hours that needs on the order of 100 CPUs and several hundred GB of memory.
14
+
15
+ 1. **The cluster names its CPU partition.** bcr's file has `cpu_partition = "cpu"`: one node, 256 CPUs, about 1 TB, shared by the lab.
16
+ 2. **The researcher launches an investigation** on bcr:
17
+ - GPU type `l40s`, each run up to 4 GPUs for 48 hours;
18
+ - each CPU run up to 128 CPUs and 384G, half the CPU node.
19
+ 3. **The implementer builds the data first.** It commits the build code and requests a setup run with 0 GPUs, 96 CPUs, 256G and 24 hours.
20
+ - The job goes to `cpu` with those numbers.
21
+ - It writes the release under `$FLYLOFT_PROJECT_WORK/data/` and a summary to `$FLYLOFT_OUT`.
22
+ - It is charged nothing against the GPU-hour budget, and it holds one run slot while it runs.
23
+ 4. **Then it trains.** Its training runs ask for GPUs and go to `l40s` exactly as today, with the campaign's CPUs and memory or the cluster's share for each GPU.
24
+ 5. **Refusals:**
25
+ - a CPU run asking for 200 CPUs, or for 512G, is refused with the limit it exceeded;
26
+ - so is a CPU run that names no CPUs or memory;
27
+ - so is a GPU run that names CPUs or memory.
28
+
29
+ ## 2. What changes
30
+
31
+ **The cluster file.** `ClusterConfig` gains `cpu_partition: str | None`.
32
+ - It differs from `partition`, which is the fallback for every job whose GPU type names none. `cpu_partition` is only for jobs that ask for no GPUs.
33
+ - A cluster without one behaves as today.
34
+ - bcr's file is `clusters/bcr.toml` in the lab's `brineylab/flyloft-config` repository. It gains `cpu_partition = "cpu"` in a pull request there once this change is deployed, not in this repository.
35
+ - `examples/live-config/clusters/slinky.toml` gains a commented `cpu_partition` line, and the misleading comment on its `partition` line is corrected.
36
+
37
+ **The CPU node runs the same jobs.** On bcr the `cpu` node is x86_64 like the GPU nodes. It mounts the same `/mnt/data`, where `remote_work_dir` and the project work directory live, and every partition has the same images under `/mnt/data/containers/`. So:
38
+ - a CPU run takes the cluster's one `container_image` and `container_mounts`;
39
+ - an environment a setup run builds on `cpu` serves a GPU run after it.
40
+
41
+ The existing tier-two test `test_live_an_investigation_sizes_its_runs_and_keeps_its_environment` checks exactly that, and once the live config names `cpu_partition` it exercises this path. Per-partition containers are not needed.
42
+
43
+ **The CPU-run limit.**
44
+ - **Where it is set:** the campaign file's `resources` gains `cpu_run_cpus: int | None` and `cpu_run_mem: str | None`, stored on the campaign by migration `0028_cpu_runs.sql`. They are the most one CPU run may ask for. They are separate from `cpus` and `mem`, which keep their meaning for every GPU run.
45
+ - **`validate_campaign`:**
46
+ - refuses either field on a campaign that is not critic-judged ("only an investigation or a task takes a CPU-run limit");
47
+ - refuses either field on a cluster with no `cpu_partition`;
48
+ - refuses a `cpu_run_cpus` below 1;
49
+ - refuses a `cpu_run_mem` that does not parse.
50
+ - **Left empty:** both are optional. Left empty, a CPU run may ask for whatever Slurm will grant on the node.
51
+
52
+ **A CPU run's request.** `RunRequestBody` gains `cpus: int | None` and `mem: str | None`, and the migration adds nullable `run.cpus`, `run.mem` and `run.partition`. In `_request_investigation_run`:
53
+ - **A run with 0 GPUs on a cluster with a `cpu_partition` is a CPU run.**
54
+ - It must name `cpus` (at least 1) and `mem`.
55
+ - `mem` is in Slurm's form: a whole number with an optional `K`, `M`, `G` or `T`, megabytes when bare.
56
+ - Each is at most the campaign's CPU-run limit when one is set. Memory is compared in megabytes.
57
+ - **A run with GPUs, or a 0-GPU run on a cluster without a `cpu_partition`, may not name `cpus` or `mem`.** It gets the campaign's, as every run does today.
58
+
59
+ **Where a run goes.** `render_job_script` sends a CPU run to `cpu_partition` with its own `--cpus-per-task` and `--mem`. Every other job is rendered exactly as today. Only a critic-judged campaign's runs can ask for no GPUs, so optimize campaigns, validations and measurements are unchanged. The partition a run was submitted to is stored on the run (`run.partition`, written by the service when it submits). The board then reports where the run went, even if the cluster's file changes later.
60
+
61
+ **The agent's tool.** `investigation_request_run` gains optional `cpus` and `mem`. Its description says:
62
+ - a run with 0 GPUs goes to the cluster's CPU nodes when it has them, and then names its CPUs and memory;
63
+ - a GPU run names neither.
64
+
65
+ The implementer's assignment, through the shared `_cj_implementer_rules.md.j2` and `_cj_record.md.j2` partials used by both types, shows the CPU-run limit beside the GPU and time ceilings, when the cluster has a CPU partition. It says that data work belongs on a CPU run, sized to what it needs.
66
+
67
+ **The budget.** Unchanged. A run is charged its own GPUs times its time, so a CPU run costs nothing. Run slots and the CPU-run limit are the only bounds on CPU work. As today, no run, a CPU run included, is accepted once the GPU-hour budget is spent: the investigation ends at its budget anyway.
68
+
69
+ ## 3. The board
70
+
71
+ - **The host's API.** `ClusterView` gains `cpu_partition`, so the launch page knows whether to offer a CPU-run limit.
72
+ - **Launch.** When the chosen cluster has a CPU partition, an investigation's or a task's Compute step adds "each CPU run may ask for up to" CPUs and memory, both optional. The existing CPUs and memory fields keep their meaning for GPU runs.
73
+ - **The run and its page.** A CPU run shows its request as CPUs, memory and time, "on cpu". A GPU run is shown as today. `npm run types` regenerates `web/src/api/types.ts`.
74
+
75
+ ## 4. Tests
76
+
77
+ **Tier one** (`tests/e2e/test_investigate.py`, extended). The fake cluster's file names a `cpu_partition`, and the campaign sets a CPU-run limit.
78
+ - **A CPU run:** the scripted investigator requests a 0-GPU setup run with its own CPUs and memory. Its job script has the CPU partition, `--cpus-per-task` and `--mem`, and no `--gres`.
79
+ - **A GPU run in the same campaign** renders exactly as before: the GPU type's partition and the campaign's CPUs and memory.
80
+ - **Refusals, each with its reason:**
81
+ - a CPU run over the CPU limit, and one over the memory limit;
82
+ - a CPU run that names no CPUs or memory;
83
+ - a GPU run that names CPUs.
84
+ - **Campaign files that are refused:** a CPU-run limit on an optimize campaign, and one on a cluster without a `cpu_partition`.
85
+ - **A cluster without a `cpu_partition`:** a 0-GPU run renders as today.
86
+ - **A task with a 0-GPU ceiling** on the cluster with a CPU partition: its run names its CPUs and memory, goes to the CPU partition, and is charged nothing. The existing task scenario (`tests/e2e/test_tasks.py`, cluster `local`) passes unchanged.
87
+ - **In the browser:** launching on that cluster offers the CPU-run fields, and the run page shows the CPU run's request.
88
+
89
+ **A unit test** for the memory parser and comparison: `512`, `8G`, `384G`, `1T`, and refusals of `8GB`, `1.5G` and `-1G`.
90
+
91
+ **Tier two** (live on slinky, scripted, no model tokens). An investigation on bcr with a CPU-run limit of 16 CPUs and 32G requests a 0-GPU setup run with 8 CPUs and 16G that writes `nproc` and the job's memory limit to `$FLYLOFT_OUT`.
92
+ - Checked: `sacct` shows it ran on `cpu` with 8 CPUs and 16G, and its `out/` came back.
93
+ - The test skips with a reason when the live config's cluster names no `cpu_partition`. The tester's live config gains `cpu_partition = "cpu"`.
94
+
95
+ **Tier three** is the curriculum retest's data build.
96
+
97
+ ## 5. Code layout
98
+
99
+ ```
100
+ src/flyloft/migrations/0028_cpu_runs.sql campaign.cpu_run_cpus, cpu_run_mem; run.cpus, mem, partition
101
+ src/flyloft/config/models.py ClusterConfig.cpu_partition; ResourceProfile cpu_run_cpus,
102
+ cpu_run_mem; Slurm memory parsing
103
+ src/flyloft/config/load.py the CPU-run limit's rules
104
+ src/flyloft/record/models.py RunRequestBody and Run cpus, mem; Campaign's CPU-run limit
105
+ src/flyloft/record/launch.py, record/store.py store the CPU-run limit; insert and read run.cpus, mem, partition
106
+ src/flyloft/record/actions.py a CPU run's request and its limit
107
+ src/flyloft/runs/staging.py a CPU run to cpu_partition with its own cpus, mem
108
+ src/flyloft/service/tick.py record the partition a run was submitted to
109
+ src/flyloft/tools/server.py investigation_request_run takes cpus, mem
110
+ src/flyloft/turns/roles/implementer.investigate.md.j2 the CPU-run limit; data work on a CPU run
111
+ src/flyloft/web/schemas.py, record/views.py ClusterView.cpu_partition; a CPU run's request and partition
112
+ web/src/... the launch's CPU-run fields and the run's request
113
+ examples/live-config/clusters/slinky.toml cpu_partition, and the partition comment corrected
114
+ ```
115
+
116
+ ## 6. Out of scope
117
+
118
+ - **Choosing a GPU type per run** within a campaign. It would change budgeting (a B200-hour is not an L40S-hour), and this scenario trains on one type.
119
+ - **Resizing a GPU run's CPUs or memory.** A GPU job keeps the campaign's values or its share of the node, so it never takes CPUs its node's other GPUs need.
120
+ - **A separate CPU-run limit per type.** Investigations and tasks share one rule.
121
+ - **A CPU row on the Compute page and in `flyloft options`.** The researcher sets the limit from what they know of the node; added when a launch goes wrong for want of it.
122
+ - **A CPU budget.** Run slots and the CPU-run limit bound CPU work, at the researcher's call.
@@ -0,0 +1,77 @@
1
+ # Tasks: Design
2
+
3
+ Status: approved 2026-10-21; built the same day on branch `tasks`. Tier one passed (599 passed, 9 skipped; the task scenario is `tests/e2e/test_tasks.py`). No tier two (§6). Tier three passed on 2026-10-22: `v0.2.1` released and deployed, a task talked through in Claude Code with the flyloft skill, submitted, and run. Closed. Part 2 of the agent interface ([agent-interface.md](agent-interface.md) §9 decided its shape); the third kind of work in [VISION.md](../VISION.md).
4
+
5
+ A *task* is work that needs doing, whose product is its output: benchmark these five models on this suite, rerun the analysis a reviewer asked for, process this dataset into that format. There is no hypothesis and nothing to judge worth pursuing. The agents do all of what was asked and nothing beyond it, and the product is the output, reported faithfully with the runs that produced it. A task runs on the investigation engine built in part 1 of investigate: one implementer that sizes its own runs in the project work directory, one critic, one reporter, a brief-only project, and `submit` from the researcher's agent. What differs is the framing the three roles are given, how a task completes, and the words the board and the pull request use.
6
+
7
+ Shipped alongside, because they are small and the human asked during tier three: the package's Python floor drops to 3.11 (two one-line rewrites in the server; the client needs only `tomllib`), and `flyloft login` shows its address and code in a panel on a terminal.
8
+
9
+ ## 1. Scenario
10
+
11
+ The researcher wants a reviewer's request done: the lab's model and four published ones, each run on a standard benchmark suite, as one table for the response to reviewers.
12
+
13
+ 1. **Conversation.** With Claude Code and the flyloft skill: *What do you want to do?* (the table), *Why is this important?* (the manuscript's revision), *How will you know it is done?* (every model on every benchmark, one table, each number traceable to a run). The ask is not exploratory, so the fourth question is not asked. The skill proposes a **task** and says why: the product is the output, not a finding. The agent writes `BRIEF.md` (the models and where they are published, the benchmark suite and where it lives, the table's shape, what agents must not do) and `task.toml` (`type = "task"`), checks, and submits on the researcher's "submit".
14
+ 2. **The work.** The implementer brings up the models in `$FLYLOFT_PROJECT_WORK`, runs every model on every benchmark, and posts the output: one finding whose claim is the table, with every run it rests on cited. It does nothing the ask did not call for.
15
+ 3. **The judgment.** The critic checks the output against the ask: every model on every benchmark the brief named, no number in the table that the runs do not show, nothing left out. The first output lacks one model on one benchmark; the critic blocks it naming the gap; the implementer runs it and posts a superseding output; the critic accepts.
16
+ 4. **The end.** The implementer concludes: a task concludes only once an output has been accepted and nothing is in flight. The reporter writes the account: what was done and where the outputs are, what was tried, what could not be done; "what to ask next" only if there is something to say.
17
+ 5. **The researcher** reads it in the harness (`flyloft status <id>`) or on the board, where the campaign is labeled a task and its outputs are not called exploratory, and drops the table into the response to reviewers.
18
+
19
+ ## 2. One engine, two labels
20
+
21
+ `investigation` and `task` are campaign types; both are **critic-judged**: `Campaign.critic_judged` (true for either) replaces every `type == "investigation"` test in the server (35 of them) and `criticJudged(c)` the board's. Everything part 1 built serves both: the run sizing and slots, the project work directory, `out/`, findings that cite runs, the three roles, `conclude`, the account, publication after the first accepted finding, memory skipping them.
22
+
23
+ | | Investigation | Task |
24
+ |---|---|---|
25
+ | What the researcher fixes | what they want to know | work that needs doing |
26
+ | The implementer | the fewest runs that show whether there is anything there; one seed | all of what was asked, nothing beyond it |
27
+ | A finding is | what the experiments suggest, with its limits (*exploratory*) | an **output**: the deliverable, with the runs that produced it |
28
+ | The critic asks | does the claim say no more than its evidence shows; are all arms reported | is the output complete against the ask; is every number in it one the runs show; is anything the ask named missing |
29
+ | Concluding | nothing in flight, no finding under review | the same, **and at least one accepted output** |
30
+ | The account | `answer` and `next` required | `answer` (what was done, where the outputs are) required; `next` optional |
31
+ | Words | "investigation"; findings tagged exploratory; "The question" | "task"; outputs untagged; "The ask" |
32
+
33
+ **Completion.** A task's completion is judged by the critic, not computed: what was asked is prose in the brief, so nothing but a reader can tell whether the table has every row. The rule that makes this binding is in `conclude`: a task is refused while no finding is accepted ("no output has been accepted yet; post the output and wait for the critic"). An implementer that wants to end a task therefore has to produce the deliverable and get it past the critic. The critic's block names what is missing, and the superseding-finding path already carries the corrected output.
34
+
35
+ **CPU-only work.** Many tasks need no GPU. `validate_campaign`'s rule that a bounded campaign asks for at least one GPU per run exempts critic-judged campaigns: their implementer sizes each run, and the ceiling may be `gpus = 0`. `gpu_hours` stays required for both types (one rule, one field); with a 0-GPU ceiling no run can charge against it, and the task ends at its conclusion or a stall. The launch page and the skill say so.
36
+
37
+ ## 3. What is recorded
38
+
39
+ Nothing new in the schema. `CampaignFile.type` and the campaign's `type` column accept `"task"`; `validate_campaign` applies the investigation rules (no metric, direction, baseline, target, dry window, islands, or replication; `gpu_hours`; exactly implementer, critic, reporter) to both critic-judged types. `submit` accepts both. `report`'s rule: an investigation needs `answer` and `next`; a task needs `answer`.
40
+
41
+ ## 4. Roles
42
+
43
+ Each investigation template gains its task variant, chosen by type: `implementer.task.md.j2`, `critic.task.md.j2`, `reporter.task.md.j2`. The shared blocks (the project work directory and `out/` rules, run sizing, the record, refusals) move into includes used by both variants, so the two differ only in their framing paragraphs:
44
+
45
+ - **Implementer.** Do all of what was asked and nothing beyond it. Setup runs, then the work, in as many runs as it takes, in parallel up to the slots. The deliverable is one finding, its claim the output as the brief asked for it (a table, a file in `out/`, a converted dataset in the project work directory), citing every run it rests on. Post it when it is complete; the critic checks completeness. After a block, do what it names and post a superseding output. Conclude once the output is accepted.
46
+ - **Critic.** Judge completeness and faithfulness against the ask: every item the brief named is there; every number is one the cited runs' outputs show; nothing is reported that was not run. Never ask for more than the ask; never judge whether the result is interesting. A block names exactly what is missing or wrong.
47
+ - **Reporter.** Wakes once at the end. `answer`: what was done and where the outputs are (paths in `out/` or the project work directory, the table itself when it is short). `found`: the outputs; `tried`: the runs; `ruled_out` and `open`: what could not be done and why; `next` only when the work showed something worth asking.
48
+
49
+ The heading over the campaign's question reads "The ask" in every critic-judged assignment; an investigation's reads the same.
50
+
51
+ ## 5. The board, the pull request, the client, the skill
52
+
53
+ - **Launch:** the Question step offers three choices: **Investigate a question**, **Do a task**, **Optimize a metric** (the last disabled without a run command). A task's steps are an investigation's; its Compute step allows 0 GPUs per run, with the line "with no GPU, the budget never charges; the task ends when its implementer concludes".
54
+ - **Labels:** the card, the campaign page, the finished row, the PR head, and `flyloft status` say "task"; an output is shown without the *exploratory* tag; the account's "Worth asking next" section appears only when `next` is non-empty.
55
+ - **`submit` and `launch`** take `type = "task"`.
56
+ - **The skill:** proposes a task when the ask has no hypothesis and nothing to judge worth pursuing (the benchmark, the reviewer's analysis, the conversion), an investigation otherwise; `handoff.md` gains an annotated `task.toml`.
57
+
58
+ ## 6. Tests
59
+
60
+ **Tier one** (`tests/e2e/test_tasks.py`, fake cluster, stand-in GitHub, scripted agents):
61
+ - A scripted task implementer runs two "benchmark" jobs, posts an output whose table lacks one row; a scripted critic blocks it naming the row; the implementer runs the missing job and posts a superseding output; the critic accepts; `conclude` is refused before the acceptance ("no output has been accepted yet") and succeeds after; the reporter's account has an `answer` and an empty `next`.
62
+ - A task with a 0-GPU ceiling launches, its runs are charged nothing, and it ends at its conclusion.
63
+ - `submit` of a `task.toml` from the client, and `status` labeling it a task.
64
+ - Browser: the card and page say "task" and show no "exploratory" tag; the launch page's three choices.
65
+ - The investigation scenarios pass unchanged (the predicate did not change their behavior).
66
+ - Unit: `conclude`'s accepted-output rule, `report`'s task rule, `validate_campaign` accepting a 0-GPU task and refusing a 0-GPU optimization.
67
+
68
+ **Tier two:** none of its own. The engine's live path was exercised by investigate part 1 and the agent interface; a task runs the same code with different prose.
69
+
70
+ **Tier three:** the human's walk from Claude Code: a task submitted by their agent, judged, and read back.
71
+
72
+ ## 7. Out of scope
73
+
74
+ - Computed completion (a checklist the ask is parsed into). The critic judges; if critics are seen accepting incomplete outputs, that is the trigger.
75
+ - A task with no budget at all (`gpu_hours` optional). One rule for both types until a researcher asks.
76
+ - Outputs larger than `out/` carries (a converted dataset lives in the project work directory; the account names its path).
77
+ - Tasks at swarm size.