outerloop-science 0.1.1__tar.gz → 0.1.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 (181) hide show
  1. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/CHANGELOG.md +6 -0
  2. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/CITATION.cff +1 -1
  3. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/PKG-INFO +1 -1
  4. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/__init__.py +1 -1
  5. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/measure.py +4 -4
  6. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_measure.py +19 -0
  7. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/.gitignore +0 -0
  8. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/.pre-commit-config.yaml +0 -0
  9. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/.python-version +0 -0
  10. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/CLAUDE.md +0 -0
  11. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/CONTRIBUTING.md +0 -0
  12. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/LICENSE +0 -0
  13. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/NOTICE +0 -0
  14. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/README.md +0 -0
  15. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/RELEASING.md +0 -0
  16. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/SECURITY.md +0 -0
  17. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/containers/README.md +0 -0
  18. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/containers/agent-py312.def +0 -0
  19. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/assets/icon-dark.svg +0 -0
  20. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/assets/icon-light.svg +0 -0
  21. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/assets/icon.svg +0 -0
  22. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/community.md +0 -0
  23. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/compute.md +0 -0
  24. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/contract.md +0 -0
  25. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/agent-substrate.md +0 -0
  26. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/architecture.md +0 -0
  27. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/base-reintegration.md +0 -0
  28. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/consolidation.md +0 -0
  29. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/dispatcher.md +0 -0
  30. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/eval-cache.md +0 -0
  31. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/external.md +0 -0
  32. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/github-app-auth.md +0 -0
  33. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/headline.md +0 -0
  34. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/judge-placement.md +0 -0
  35. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/meta.md +0 -0
  36. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/onboarding.md +0 -0
  37. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/orchestrator-verify.md +0 -0
  38. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/public-surface.md +0 -0
  39. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/research-lines.md +0 -0
  40. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/research-loop-buildout.md +0 -0
  41. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/research-loop.md +0 -0
  42. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/resident-tick.md +0 -0
  43. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/review-placement.md +0 -0
  44. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/reviewer-infra.md +0 -0
  45. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/role-cli.md +0 -0
  46. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/roles.md +0 -0
  47. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/scaling.md +0 -0
  48. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/design/session-watcher.md +0 -0
  49. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/install.md +0 -0
  50. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/reviewer.md +0 -0
  51. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/roadmap.md +0 -0
  52. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/docs/validation/author-syscalls.md +0 -0
  53. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/examples/review.yml +0 -0
  54. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/pyproject.toml +0 -0
  55. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/scripts/README.md +0 -0
  56. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/scripts/install_codex.sh +0 -0
  57. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/scripts/install_hermes.sh +0 -0
  58. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/scripts/requeue_moved_successors.sh +0 -0
  59. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/scripts/setup_branch_protection.sh +0 -0
  60. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/scripts/sweep_git_locks.sh +0 -0
  61. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/scripts/tick_chain.sbatch +0 -0
  62. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/scripts/tick_deploy.sh +0 -0
  63. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/scripts/tick_resident.sh +0 -0
  64. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/__main__.py +0 -0
  65. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/appauth.py +0 -0
  66. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/appmanifest.py +0 -0
  67. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/attempt.py +0 -0
  68. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/brief.py +0 -0
  69. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/cli.py +0 -0
  70. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/climbboard.py +0 -0
  71. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/compute.py +0 -0
  72. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/contract.py +0 -0
  73. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/contract_cli.py +0 -0
  74. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/disk.py +0 -0
  75. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/dispatch.py +0 -0
  76. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/evalcache.py +0 -0
  77. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/followup.py +0 -0
  78. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/github.py +0 -0
  79. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/harness.py +0 -0
  80. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/housekeeping.py +0 -0
  81. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/image.py +0 -0
  82. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/init.py +0 -0
  83. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/intake.py +0 -0
  84. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/launchlog.py +0 -0
  85. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/limits.py +0 -0
  86. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/maintain.py +0 -0
  87. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/maintain_agent_cli.py +0 -0
  88. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/maintain_post_cli.py +0 -0
  89. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/markers.py +0 -0
  90. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/orchestrator.py +0 -0
  91. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/panel.py +0 -0
  92. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/paths.py +0 -0
  93. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/posting.py +0 -0
  94. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/progress.py +0 -0
  95. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/py.typed +0 -0
  96. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/review.py +0 -0
  97. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/review_agent.py +0 -0
  98. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/review_agent_cli.py +0 -0
  99. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/review_post_cli.py +0 -0
  100. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/review_summarize_cli.py +0 -0
  101. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/role_runner.py +0 -0
  102. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/roles.py +0 -0
  103. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/rolespec.py +0 -0
  104. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/runstate.py +0 -0
  105. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/steward.py +0 -0
  106. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/style.py +0 -0
  107. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/syscall.py +0 -0
  108. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/syscall_cli.py +0 -0
  109. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/tick.py +0 -0
  110. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/verifier.py +0 -0
  111. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/verify_agent.py +0 -0
  112. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/verify_agent_cli.py +0 -0
  113. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/verify_post_cli.py +0 -0
  114. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/src/outerloop/watcher.py +0 -0
  115. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/conftest.py +0 -0
  116. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/fakes.py +0 -0
  117. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/helpers.py +0 -0
  118. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_appauth.py +0 -0
  119. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_appmanifest.py +0 -0
  120. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_attempt.py +0 -0
  121. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_bot_aliases.py +0 -0
  122. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_bot_login.py +0 -0
  123. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_brief.py +0 -0
  124. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_channel_dir.py +0 -0
  125. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_climbboard.py +0 -0
  126. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_codex_harness.py +0 -0
  127. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_compute.py +0 -0
  128. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_contract.py +0 -0
  129. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_contract_cli.py +0 -0
  130. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_contract_names.py +0 -0
  131. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_disk.py +0 -0
  132. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_dispatch.py +0 -0
  133. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_env_bridge.py +0 -0
  134. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_evalcache.py +0 -0
  135. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_followup.py +0 -0
  136. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_github.py +0 -0
  137. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_hardening.py +0 -0
  138. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_harness.py +0 -0
  139. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_hermes_harness.py +0 -0
  140. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_housekeeping.py +0 -0
  141. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_image.py +0 -0
  142. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_import.py +0 -0
  143. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_init.py +0 -0
  144. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_intake.py +0 -0
  145. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_launchlog.py +0 -0
  146. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_limits.py +0 -0
  147. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_local_compute.py +0 -0
  148. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_maintain.py +0 -0
  149. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_markers.py +0 -0
  150. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_measure_and_decide.py +0 -0
  151. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_orchestrator.py +0 -0
  152. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_packaging.py +0 -0
  153. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_panel.py +0 -0
  154. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_paths.py +0 -0
  155. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_posting.py +0 -0
  156. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_progress.py +0 -0
  157. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_requeue_moved_successors.py +0 -0
  158. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_review.py +0 -0
  159. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_review_agent.py +0 -0
  160. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_review_agent_cli.py +0 -0
  161. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_review_hardening.py +0 -0
  162. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_review_policy.py +0 -0
  163. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_review_summarize.py +0 -0
  164. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_role_runner.py +0 -0
  165. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_rolespec.py +0 -0
  166. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_runstate.py +0 -0
  167. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_start.py +0 -0
  168. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_steward.py +0 -0
  169. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_sweep_git_locks.py +0 -0
  170. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_syscall.py +0 -0
  171. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_syscall_cli.py +0 -0
  172. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_tick.py +0 -0
  173. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_tick_chain_successors.py +0 -0
  174. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_tick_resident.py +0 -0
  175. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_tiers.py +0 -0
  176. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_verifier.py +0 -0
  177. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_verify_agent.py +0 -0
  178. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_verify_agent_cli.py +0 -0
  179. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_version.py +0 -0
  180. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/tests/test_watcher.py +0 -0
  181. {outerloop_science-0.1.1 → outerloop_science-0.1.2}/uv.lock +0 -0
@@ -6,6 +6,12 @@ Versions follow [SemVer](https://semver.org).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.2] - 2026-09-11
10
+
11
+ ### Fixed
12
+
13
+ - A GPU benchmark on local compute aborted at its first gate measure with "no GPU lane is configured". The measure placement now follows the launch placement's rule: local compute has no lanes, the job runs on the machine's own GPUs.
14
+
9
15
  ## [0.1.1] - 2026-09-11
10
16
 
11
17
  ### Fixed
@@ -13,5 +13,5 @@ authors:
13
13
  repository-code: "https://github.com/outerloop-science/outerloop"
14
14
  url: "https://outerloop.science"
15
15
  license: Apache-2.0
16
- version: 0.1.1
16
+ version: 0.1.2
17
17
  date-released: "2026-09-11"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: outerloop-science
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Autonomous research agents that improve the benchmark you point them at, one verified pull request at a time
5
5
  Project-URL: Homepage, https://outerloop.science
6
6
  Project-URL: Repository, https://github.com/outerloop-science/outerloop
@@ -2,7 +2,7 @@
2
2
 
3
3
  import os
4
4
 
5
- __version__ = "0.1.1"
5
+ __version__ = "0.1.2"
6
6
 
7
7
 
8
8
  def _bridge_legacy_env() -> None:
@@ -24,7 +24,7 @@ from dataclasses import dataclass
24
24
  from pathlib import Path
25
25
  from typing import Any
26
26
 
27
- from outerloop.compute import GONE, Compute, JobSpec, is_terminal
27
+ from outerloop.compute import GONE, Compute, JobSpec, is_terminal, local_mode
28
28
  from outerloop.dispatch import (
29
29
  eval_job_spec,
30
30
  read_eval_result,
@@ -275,7 +275,9 @@ class DispatchedMeasurer:
275
275
  seed_cache: Path | None = None
276
276
 
277
277
  def _placement(self, m: Measure) -> tuple[str, str]:
278
- if m.gpus <= 0:
278
+ # local compute has no lanes: the job is a subprocess on whatever GPUs
279
+ # the machine has (same rule as DispatchSettings.placement)
280
+ if m.gpus <= 0 or local_mode():
279
281
  return self.account, self.partition
280
282
  if not self.gpu_partition:
281
283
  raise ValueError(
@@ -492,8 +494,6 @@ class DispatchSettings:
492
494
  GPU job has no lane — a queue that can never run is worse than a
493
495
  loud refusal. Local compute has no lanes: jobs are subprocesses on
494
496
  whatever GPUs the machine has, so placement is empty by design."""
495
- from outerloop.compute import local_mode
496
-
497
497
  if gpus <= 0 or local_mode():
498
498
  return self.account, self.partition
499
499
  if not self.gpu_partition:
@@ -399,6 +399,25 @@ def test_dispatch_settings_place_gpu_jobs_on_the_gpu_lane(tmp_path):
399
399
  assert own_account.placement(2) == ("gpu-acct", "h200")
400
400
 
401
401
 
402
+ def test_local_compute_places_a_gpu_measure_without_a_lane(tmp_path, monkeypatch):
403
+ """Local compute has no lanes: a GPU measure is a subprocess on the machine's
404
+ own GPUs, so the gate's placement must not demand OUTERLOOP_GPU_PARTITION.
405
+ The launch placement already followed that rule; the measure placement did
406
+ not, and a GPU benchmark on a local box aborted at its first baseline."""
407
+ from outerloop.compute import LocalCompute
408
+ from outerloop.measure import DispatchSettings, Measure
409
+
410
+ settings = DispatchSettings(compute=LocalCompute(), image="", account="", partition="")
411
+ m = settings.measurer(tmp_path, repo_root=tmp_path, eval_minutes=15, run_tag="r")
412
+ gpu = Measure(name="baseline", tree_sha="a" * 40, command="x", metric="loss", gpus=1)
413
+ monkeypatch.delenv("OUTERLOOP_COMPUTE", raising=False)
414
+ with pytest.raises(ValueError, match="OUTERLOOP_GPU_PARTITION"):
415
+ m._placement(gpu)
416
+ monkeypatch.setenv("OUTERLOOP_COMPUTE", "local")
417
+ assert m._placement(gpu) == ("", "")
418
+ assert settings.placement(1) == ("", "") # the launch side, same rule
419
+
420
+
402
421
  def test_mixed_suite_places_each_measure_on_its_own_lane(tmp_path):
403
422
  """A suite gate measures siblings through the ONE measurer: a GPU
404
423
  sibling of a CPU benchmark (or vice versa) must land on its own lane with