outerloop-science 0.1.0.dev4__tar.gz → 0.1.1__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.0.dev4 → outerloop_science-0.1.1}/CHANGELOG.md +54 -0
  2. outerloop_science-0.1.1/CITATION.cff +17 -0
  3. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/PKG-INFO +7 -2
  4. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/README.md +1 -1
  5. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/RELEASING.md +2 -2
  6. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/pyproject.toml +7 -0
  7. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/__init__.py +1 -1
  8. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/attempt.py +34 -7
  9. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_attempt.py +95 -0
  10. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/.gitignore +0 -0
  11. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/.pre-commit-config.yaml +0 -0
  12. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/.python-version +0 -0
  13. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/CLAUDE.md +0 -0
  14. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/CONTRIBUTING.md +0 -0
  15. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/LICENSE +0 -0
  16. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/NOTICE +0 -0
  17. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/SECURITY.md +0 -0
  18. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/containers/README.md +0 -0
  19. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/containers/agent-py312.def +0 -0
  20. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/assets/icon-dark.svg +0 -0
  21. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/assets/icon-light.svg +0 -0
  22. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/assets/icon.svg +0 -0
  23. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/community.md +0 -0
  24. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/compute.md +0 -0
  25. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/contract.md +0 -0
  26. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/agent-substrate.md +0 -0
  27. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/architecture.md +0 -0
  28. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/base-reintegration.md +0 -0
  29. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/consolidation.md +0 -0
  30. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/dispatcher.md +0 -0
  31. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/eval-cache.md +0 -0
  32. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/external.md +0 -0
  33. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/github-app-auth.md +0 -0
  34. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/headline.md +0 -0
  35. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/judge-placement.md +0 -0
  36. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/meta.md +0 -0
  37. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/onboarding.md +0 -0
  38. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/orchestrator-verify.md +0 -0
  39. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/public-surface.md +0 -0
  40. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/research-lines.md +0 -0
  41. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/research-loop-buildout.md +0 -0
  42. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/research-loop.md +0 -0
  43. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/resident-tick.md +0 -0
  44. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/review-placement.md +0 -0
  45. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/reviewer-infra.md +0 -0
  46. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/role-cli.md +0 -0
  47. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/roles.md +0 -0
  48. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/scaling.md +0 -0
  49. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/design/session-watcher.md +0 -0
  50. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/install.md +0 -0
  51. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/reviewer.md +0 -0
  52. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/roadmap.md +0 -0
  53. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/docs/validation/author-syscalls.md +0 -0
  54. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/examples/review.yml +0 -0
  55. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/scripts/README.md +0 -0
  56. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/scripts/install_codex.sh +0 -0
  57. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/scripts/install_hermes.sh +0 -0
  58. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/scripts/requeue_moved_successors.sh +0 -0
  59. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/scripts/setup_branch_protection.sh +0 -0
  60. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/scripts/sweep_git_locks.sh +0 -0
  61. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/scripts/tick_chain.sbatch +0 -0
  62. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/scripts/tick_deploy.sh +0 -0
  63. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/scripts/tick_resident.sh +0 -0
  64. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/__main__.py +0 -0
  65. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/appauth.py +0 -0
  66. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/appmanifest.py +0 -0
  67. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/brief.py +0 -0
  68. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/cli.py +0 -0
  69. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/climbboard.py +0 -0
  70. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/compute.py +0 -0
  71. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/contract.py +0 -0
  72. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/contract_cli.py +0 -0
  73. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/disk.py +0 -0
  74. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/dispatch.py +0 -0
  75. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/evalcache.py +0 -0
  76. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/followup.py +0 -0
  77. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/github.py +0 -0
  78. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/harness.py +0 -0
  79. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/housekeeping.py +0 -0
  80. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/image.py +0 -0
  81. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/init.py +0 -0
  82. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/intake.py +0 -0
  83. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/launchlog.py +0 -0
  84. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/limits.py +0 -0
  85. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/maintain.py +0 -0
  86. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/maintain_agent_cli.py +0 -0
  87. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/maintain_post_cli.py +0 -0
  88. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/markers.py +0 -0
  89. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/measure.py +0 -0
  90. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/orchestrator.py +0 -0
  91. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/panel.py +0 -0
  92. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/paths.py +0 -0
  93. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/posting.py +0 -0
  94. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/progress.py +0 -0
  95. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/py.typed +0 -0
  96. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/review.py +0 -0
  97. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/review_agent.py +0 -0
  98. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/review_agent_cli.py +0 -0
  99. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/review_post_cli.py +0 -0
  100. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/review_summarize_cli.py +0 -0
  101. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/role_runner.py +0 -0
  102. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/roles.py +0 -0
  103. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/rolespec.py +0 -0
  104. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/runstate.py +0 -0
  105. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/steward.py +0 -0
  106. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/style.py +0 -0
  107. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/syscall.py +0 -0
  108. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/syscall_cli.py +0 -0
  109. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/tick.py +0 -0
  110. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/verifier.py +0 -0
  111. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/verify_agent.py +0 -0
  112. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/verify_agent_cli.py +0 -0
  113. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/verify_post_cli.py +0 -0
  114. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/src/outerloop/watcher.py +0 -0
  115. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/conftest.py +0 -0
  116. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/fakes.py +0 -0
  117. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/helpers.py +0 -0
  118. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_appauth.py +0 -0
  119. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_appmanifest.py +0 -0
  120. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_bot_aliases.py +0 -0
  121. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_bot_login.py +0 -0
  122. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_brief.py +0 -0
  123. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_channel_dir.py +0 -0
  124. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_climbboard.py +0 -0
  125. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_codex_harness.py +0 -0
  126. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_compute.py +0 -0
  127. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_contract.py +0 -0
  128. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_contract_cli.py +0 -0
  129. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_contract_names.py +0 -0
  130. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_disk.py +0 -0
  131. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_dispatch.py +0 -0
  132. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_env_bridge.py +0 -0
  133. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_evalcache.py +0 -0
  134. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_followup.py +0 -0
  135. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_github.py +0 -0
  136. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_hardening.py +0 -0
  137. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_harness.py +0 -0
  138. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_hermes_harness.py +0 -0
  139. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_housekeeping.py +0 -0
  140. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_image.py +0 -0
  141. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_import.py +0 -0
  142. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_init.py +0 -0
  143. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_intake.py +0 -0
  144. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_launchlog.py +0 -0
  145. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_limits.py +0 -0
  146. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_local_compute.py +0 -0
  147. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_maintain.py +0 -0
  148. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_markers.py +0 -0
  149. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_measure.py +0 -0
  150. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_measure_and_decide.py +0 -0
  151. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_orchestrator.py +0 -0
  152. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_packaging.py +0 -0
  153. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_panel.py +0 -0
  154. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_paths.py +0 -0
  155. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_posting.py +0 -0
  156. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_progress.py +0 -0
  157. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_requeue_moved_successors.py +0 -0
  158. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_review.py +0 -0
  159. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_review_agent.py +0 -0
  160. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_review_agent_cli.py +0 -0
  161. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_review_hardening.py +0 -0
  162. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_review_policy.py +0 -0
  163. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_review_summarize.py +0 -0
  164. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_role_runner.py +0 -0
  165. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_rolespec.py +0 -0
  166. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_runstate.py +0 -0
  167. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_start.py +0 -0
  168. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_steward.py +0 -0
  169. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_sweep_git_locks.py +0 -0
  170. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_syscall.py +0 -0
  171. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_syscall_cli.py +0 -0
  172. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_tick.py +0 -0
  173. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_tick_chain_successors.py +0 -0
  174. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_tick_resident.py +0 -0
  175. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_tiers.py +0 -0
  176. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_verifier.py +0 -0
  177. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_verify_agent.py +0 -0
  178. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_verify_agent_cli.py +0 -0
  179. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_version.py +0 -0
  180. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/tests/test_watcher.py +0 -0
  181. {outerloop_science-0.1.0.dev4 → outerloop_science-0.1.1}/uv.lock +0 -0
@@ -6,6 +6,60 @@ Versions follow [SemVer](https://semver.org).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.1] - 2026-09-11
10
+
11
+ ### Fixed
12
+
13
+ - Wakes never recorded a launch as ended. A nested `import time` in the wake function shadowed the module import, so the ledger step failed on every wake, and the failure was only logged to a stderr the scheduler discards. The PR's experiments table stayed empty and `history` showed every launch as not back. The nested imports are gone, the best-effort wrapper now logs the redacted traceback, and a wake writes its kernel log to `runs/<run>/kernel.log`.
14
+
15
+ ### Changed
16
+
17
+ - The PyPI project page and the Python-version badge now read correctly: the package metadata lists the supported Python version (3.12) and the project's audience and topic. The README's Python badge is a static `3.12+` so it renders regardless of the release's metadata.
18
+
19
+ ### Deprecated
20
+
21
+ - The `AUTORESEARCH_*` environment names and `.autoresearch` paths are still accepted. The 0.1.0 notes said they would go in 0.1.1; a bug-fix release should not also break a deployment's environment, so their removal moves to 0.2.0.
22
+
23
+ ## [0.1.0] - 2026-09-10
24
+
25
+ The first release of Outerloop — autoresearch agents that improve your benchmark
26
+ on your own code, your keys, and your compute. Built and used daily by the
27
+ Agentic Learning AI Lab at NYU.
28
+
29
+ ### Highlights
30
+
31
+ - **The loop.** An agent proposes a change, runs the experiment on your cluster,
32
+ measures it against the base tree at the same seed, and opens a pull request
33
+ only when the benchmark actually improves. Every attempt gets a short report,
34
+ negative results included.
35
+ - **Verification first.** Outerloop re-measures every claim against the
36
+ repository's contract — it never trusts a number the agent reports — and a
37
+ reviewer panel reads the change and the claim before a pull request stands.
38
+ - **Your guardrails apply.** Agents cannot touch the benchmark, the budgets, or
39
+ your CI; your branch protection and required checks apply to them as to any
40
+ contributor. A pull request waits for a human by default; a repo can also let
41
+ clean ones merge themselves.
42
+ - **Runs where you do.** Slurm or a single GPU machine, behind one compute seam.
43
+ Sessions run in a scrubbed sandbox, and your model key and bot credentials
44
+ never leave the job.
45
+ - **Onboarding in a few commands.** `outerloop init` writes the config and sets
46
+ up auth (a per-adopter GitHub App, or a token), `outerloop start` runs the
47
+ loop, and `outerloop upgrade` moves a local install to the newest release. The
48
+ container image is pulled, not built.
49
+ - **Contracts and the board.** A repo declares its benchmark, scope, and budgets
50
+ in `.outerloop.yaml`; the climb board publishes every attempt and its outcome,
51
+ and the ledger keeps the honest record — refusals and negatives included.
52
+ - **Research lines and depth.** Each agent works its own line and reintegrates
53
+ the moving base; a run hibernates through long experiments and wakes to read
54
+ its results.
55
+ - **Advisory review and a weekly digest.** The reviewer posts structured,
56
+ advisory findings on every pull request, and a weekly maintainer scan files a
57
+ codebase-health digest.
58
+
59
+ The `AUTORESEARCH_*` environment names and `.autoresearch` paths from before the
60
+ rename are still accepted in 0.1; 0.1.1 kept them (see its notes) and 0.2.0
61
+ removes them. The full history since the first pre-release follows.
62
+
9
63
  ### Changed
10
64
 
11
65
  - Install guide: a lab member who is not an org owner now has documented steps
@@ -0,0 +1,17 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use Outerloop in your research, please cite it."
3
+ title: Outerloop
4
+ abstract: >-
5
+ Outerloop runs AI agents on your own research code: an agent proposes a
6
+ change, runs the experiment on your cluster, and opens a pull request only when
7
+ your benchmark improves. Every claim is re-measured against the repository's
8
+ contract, every attempt is recorded, and it runs on your keys, your compute,
9
+ and your repositories.
10
+ type: software
11
+ authors:
12
+ - name: "Agentic Learning AI Lab, New York University"
13
+ repository-code: "https://github.com/outerloop-science/outerloop"
14
+ url: "https://outerloop.science"
15
+ license: Apache-2.0
16
+ version: 0.1.1
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.0.dev4
3
+ Version: 0.1.1
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
@@ -10,6 +10,11 @@ Author: Agentic Learning AI Lab, New York University
10
10
  License-Expression: Apache-2.0
11
11
  License-File: LICENSE
12
12
  License-File: NOTICE
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
13
18
  Requires-Python: >=3.12
14
19
  Requires-Dist: cryptography>=42
15
20
  Requires-Dist: pydantic>=2
@@ -26,7 +31,7 @@ Description-Content-Type: text/markdown
26
31
 
27
32
  [![ci](https://github.com/outerloop-science/outerloop/actions/workflows/ci.yml/badge.svg)](https://github.com/outerloop-science/outerloop/actions/workflows/ci.yml)
28
33
  [![PyPI](https://img.shields.io/pypi/v/outerloop-science.svg)](https://pypi.org/project/outerloop-science/)
29
- [![Python](https://img.shields.io/pypi/pyversions/outerloop-science.svg)](https://pypi.org/project/outerloop-science/)
34
+ [![Python](https://img.shields.io/badge/python-3.12+-blue.svg)](https://pypi.org/project/outerloop-science/)
30
35
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
31
36
 
32
37
  **Autoresearch agents that improve your benchmark.**
@@ -7,7 +7,7 @@
7
7
 
8
8
  [![ci](https://github.com/outerloop-science/outerloop/actions/workflows/ci.yml/badge.svg)](https://github.com/outerloop-science/outerloop/actions/workflows/ci.yml)
9
9
  [![PyPI](https://img.shields.io/pypi/v/outerloop-science.svg)](https://pypi.org/project/outerloop-science/)
10
- [![Python](https://img.shields.io/pypi/pyversions/outerloop-science.svg)](https://pypi.org/project/outerloop-science/)
10
+ [![Python](https://img.shields.io/badge/python-3.12+-blue.svg)](https://pypi.org/project/outerloop-science/)
11
11
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
12
12
 
13
13
  **Autoresearch agents that improve your benchmark.**
@@ -8,8 +8,8 @@
8
8
 
9
9
  ## Cutting a release
10
10
 
11
- 1. PR: bump `__version__` and move the `[Unreleased]` entries under the new
12
- version. A dev or rc pre-release still bumps the version (PyPI never
11
+ 1. PR: bump `__version__`, set `CITATION.cff`'s `version` and `date-released`,
12
+ and move the `[Unreleased]` entries under the new version. A dev or rc pre-release still bumps the version (PyPI never
13
13
  accepts a version twice, so the next one is `.dev1`, `rc2`, ...) but leaves
14
14
  `[Unreleased]` in place until the final release.
15
15
  2. `git tag vX.Y.Z && git push origin vX.Y.Z`. The `release` workflow builds
@@ -11,6 +11,13 @@ license = "Apache-2.0"
11
11
  license-files = ["LICENSE", "NOTICE"]
12
12
  requires-python = ">=3.12"
13
13
  authors = [{ name = "Agentic Learning AI Lab, New York University" }]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Science/Research",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
20
+ ]
14
21
  dependencies = [
15
22
  "cryptography>=42", # GitHub App auth (RS256 JWTs), the recommended identity
16
23
  "pydantic>=2",
@@ -2,7 +2,7 @@
2
2
 
3
3
  import os
4
4
 
5
- __version__ = "0.1.0.dev4"
5
+ __version__ = "0.1.1"
6
6
 
7
7
 
8
8
  def _bridge_legacy_env() -> None:
@@ -19,6 +19,7 @@ import os
19
19
  import re
20
20
  import shutil
21
21
  import time
22
+ import traceback
22
23
  from collections.abc import Callable, Iterable
23
24
  from dataclasses import dataclass
24
25
  from dataclasses import replace as dc_replace
@@ -319,18 +320,35 @@ class AttemptOutcome:
319
320
  report_path: str = ""
320
321
 
321
322
 
323
+ def _attach_run_log(directory: Path) -> None:
324
+ """Also write the kernel's log lines to `<run dir>/kernel.log`: the scheduler
325
+ discards a wake job's own stderr, so this is the only trace a swallowed
326
+ step leaves. Only for a run that exists — never a stray directory."""
327
+ if not directory.is_dir():
328
+ return
329
+ try:
330
+ handler = logging.FileHandler(directory / "kernel.log", encoding="utf-8")
331
+ except OSError as exc:
332
+ log.warning("run log unavailable: %s", exc)
333
+ return
334
+ handler.setFormatter(logging.Formatter("%(asctime)s %(message)s"))
335
+ logging.getLogger().addHandler(handler)
336
+
337
+
322
338
  def _best_effort(what: str, fn: Callable[[], object], secrets: tuple[str, ...] = ()) -> bool:
323
- """One ending step; a failure is logged, never raised.
339
+ """One ending step; a failure is logged with its traceback, never raised.
324
340
 
325
341
  The terminal sequence (record, report, issue post) must degrade
326
342
  independently: a full disk must not block the GitHub post, and a network
327
- failure must not block the record.
343
+ failure must not block the record. The traceback is the only trace a
344
+ swallowed step leaves, so it is logged whole (redacted).
328
345
  """
329
346
  try:
330
347
  fn()
331
348
  return True
332
349
  except Exception as exc:
333
- log.warning("%s failed: %s", what, redact(f"{type(exc).__name__}: {exc}", secrets))
350
+ detail = "".join(traceback.format_exception(exc)).rstrip()
351
+ log.warning("%s failed: %s", what, redact(detail, secrets))
334
352
  return False
335
353
 
336
354
 
@@ -1025,8 +1043,6 @@ def _wake_author_sleep(
1025
1043
  # existing wake path decides). Keep the NEW park's snapshot ref; the OLD
1026
1044
  # sleep ref is superseded once the new park persists.
1027
1045
  kept_ref = next((s.ref for s in snapshots if s.commit == p.candidate_sha), "")
1028
- import time
1029
-
1030
1046
  try:
1031
1047
  _park_run(
1032
1048
  run_root,
@@ -3017,8 +3033,6 @@ def live_attempt(
3017
3033
  # commit); record and keep that same ref, drop the rest. An
3018
3034
  # author-sleep's snapshot is the tree the wake re-delivers.
3019
3035
  kept_ref = next((s.ref for s in snapshots if s.commit == p.candidate_sha), "")
3020
- import time
3021
-
3022
3036
  # anchor the deadline to the PARK (when the evals were submitted),
3023
3037
  # not the run's start `now` — a session lasting hours would otherwise
3024
3038
  # eat the queue budget and let the sweep cancel a still-queued eval.
@@ -3419,11 +3433,22 @@ def main() -> int:
3419
3433
  )
3420
3434
  return value
3421
3435
 
3436
+ def _run_id(value: str) -> str:
3437
+ # the id names the run directory under runs/: one path segment, so a
3438
+ # value cannot reach outside the run root before the record is read
3439
+ # ("" is the default: a fresh climb, not a resume)
3440
+ if value and not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_.@-]{0,127}", value):
3441
+ raise argparse.ArgumentTypeError(
3442
+ f"run id {value!r} is not a run directory name (want [A-Za-z0-9][A-Za-z0-9_.@-]*)"
3443
+ )
3444
+ return value
3445
+
3422
3446
  parser.add_argument("--agent-id", default="agent-01", type=_agent_id)
3423
3447
  parser.add_argument("--run-root", required=True, type=Path)
3424
3448
  parser.add_argument(
3425
3449
  "--resume",
3426
3450
  default="",
3451
+ type=_run_id,
3427
3452
  metavar="RUN_ID",
3428
3453
  help="wake a parked dispatched run instead of starting a fresh climb",
3429
3454
  )
@@ -3530,6 +3555,8 @@ def main() -> int:
3530
3555
  )
3531
3556
  args = parser.parse_args()
3532
3557
  logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
3558
+ if args.resume:
3559
+ _attach_run_log(run_dir_of(args.run_root, args.resume))
3533
3560
  if not args.image and not args.uncontained:
3534
3561
  parser.error("--image is required (or pass --uncontained explicitly, dev only)")
3535
3562
  # NOTE: the codex author is validated on the EFFECTIVE author per path — the
@@ -3304,6 +3304,81 @@ def test_author_sleep_wake_delivers_results_and_flows_to_a_candidate_park(
3304
3304
  assert str(record.stage["candidate_ref"]) in refs[0]
3305
3305
 
3306
3306
 
3307
+ def test_author_sleep_wake_records_each_launch_as_ended_in_the_ledger(
3308
+ tmp_path, monkeypatch
3309
+ ) -> None:
3310
+ """The wake writes one `ended` record per launch job under the sleep that
3311
+ submitted it, so `history` and the PR's experiments table see the job come
3312
+ back. A nested `import time` once made this step raise inside its
3313
+ best-effort wrapper on every wake: no ended record was ever written."""
3314
+ from outerloop.launchlog import append_submitted, history, read_ledger
3315
+ from outerloop.measure import MeasurementPending
3316
+ from outerloop.roles import author_spec
3317
+ from outerloop.syscall import Launch
3318
+
3319
+ state, run_id, _wsroot, _ = _write_parked_author_sleep(
3320
+ tmp_path, monkeypatch, raise_exc=MeasurementPending(("701", "702"))
3321
+ )
3322
+ run_dir = state / "runs" / run_id
3323
+ append_submitted(
3324
+ run_dir,
3325
+ sleep=1,
3326
+ launches=(Launch(name="probe", command="uv run probe.py", minutes=45, why="tails"),),
3327
+ job_ids=["501"],
3328
+ at=1_000_000.0,
3329
+ )
3330
+ resume_run(
3331
+ state,
3332
+ run_id,
3333
+ dispatch=_fake_dispatch(),
3334
+ github=CommentingGitHub(), # type: ignore[arg-type]
3335
+ bot_auth=NoAuth(),
3336
+ now=1_000_100.0,
3337
+ harness=ScriptedHarness(edits={"src/pilot/solvers/tsp.py": "def solve(): return 'p'\n"}),
3338
+ spec=author_spec(),
3339
+ )
3340
+ ended = [r for r in read_ledger(run_dir) if r["event"] == "ended"]
3341
+ assert [(r["sleep"], r["name"], r["exit_code"], r["last_line"]) for r in ended] == [
3342
+ (1, "probe", 0, "tail improvement: 0.7")
3343
+ ]
3344
+ (entry,) = history(run_dir)
3345
+ assert entry["jobs"] and entry["jobs"][0]["exit_code"] == 0
3346
+
3347
+
3348
+ def test_best_effort_logs_the_traceback_of_a_swallowed_step(caplog) -> None:
3349
+ import logging
3350
+
3351
+ from outerloop.attempt import _best_effort
3352
+
3353
+ def boom() -> None:
3354
+ raise NameError("cannot access free variable 'time' (token hunter2)")
3355
+
3356
+ with caplog.at_level(logging.WARNING):
3357
+ assert _best_effort("launch ledger", boom, secrets=("hunter2",)) is False
3358
+ assert "launch ledger failed" in caplog.text
3359
+ assert "Traceback" in caplog.text and "in boom" in caplog.text
3360
+ assert "hunter2" not in caplog.text
3361
+
3362
+
3363
+ def test_attach_run_log_persists_kernel_lines_beside_the_run(tmp_path) -> None:
3364
+ import logging
3365
+
3366
+ from outerloop.attempt import _attach_run_log, log
3367
+
3368
+ _attach_run_log(tmp_path / "runs" / "nope") # no such run: nothing created
3369
+ assert not (tmp_path / "runs").exists()
3370
+ (tmp_path / "runs" / "r1").mkdir(parents=True)
3371
+ _attach_run_log(tmp_path / "runs" / "r1")
3372
+ try:
3373
+ log.warning("launch ledger failed: NameError")
3374
+ finally:
3375
+ for h in list(logging.getLogger().handlers):
3376
+ if isinstance(h, logging.FileHandler) and h.baseFilename.endswith("kernel.log"):
3377
+ logging.getLogger().removeHandler(h)
3378
+ h.close()
3379
+ assert "launch ledger failed" in (tmp_path / "runs" / "r1" / "kernel.log").read_text()
3380
+
3381
+
3307
3382
  def test_author_sleep_wake_can_sleep_again(tmp_path, monkeypatch) -> None:
3308
3383
  # the woken author launches more work and sleeps again: a fresh author-sleep
3309
3384
  # park with the counts advanced.
@@ -4205,6 +4280,26 @@ def test_cli_rejects_ref_shaping_agent_ids(capsys, monkeypatch) -> None:
4205
4280
  assert "cannot shape a git ref" in capsys.readouterr().err
4206
4281
 
4207
4282
 
4283
+ def test_resume_rejects_a_run_id_that_is_not_one_path_segment(
4284
+ tmp_path, monkeypatch, capsys
4285
+ ) -> None:
4286
+ """`--resume` names the run directory under runs/. A traversal value is
4287
+ refused by the parser, before the lease or the kernel log could be created
4288
+ outside the run root."""
4289
+ from outerloop.attempt import main as climb_main
4290
+
4291
+ (tmp_path / "elsewhere").mkdir()
4292
+ for bad in ("../elsewhere", "a/b", ".."):
4293
+ monkeypatch.setattr(
4294
+ "sys.argv",
4295
+ ["climb", "--resume", bad, "--run-root", str(tmp_path / "state"), "--image", "x.sif"],
4296
+ )
4297
+ with pytest.raises(SystemExit):
4298
+ climb_main()
4299
+ assert "is not a run directory name" in capsys.readouterr().err
4300
+ assert not (tmp_path / "elsewhere" / "kernel.log").exists()
4301
+
4302
+
4208
4303
  def test_wake_refreshes_origin_refs(tmp_path, monkeypatch) -> None:
4209
4304
  """The clone's refs freeze at run start; a wake fetches origin so the
4210
4305
  resumed session reads current base and sibling refs locally."""