cli-agent-runner 0.3.11__tar.gz → 0.3.12__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 (406) hide show
  1. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/.vulture-whitelist.py +22 -0
  2. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/CHANGELOG.md +9 -0
  3. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/PKG-INFO +1 -1
  4. cli_agent_runner-0.3.12/agent_runner/_bounded.py +135 -0
  5. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_docgen.py +14 -1
  6. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_lifecycle.py +1 -1
  7. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_serve_policy.py +13 -5
  8. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_version.py +2 -2
  9. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/config/__init__.py +7 -1
  10. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/config/loader.py +107 -0
  11. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/config/models.py +71 -0
  12. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/config/parsers.py +59 -1
  13. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/events.py +2 -0
  14. cli_agent_runner-0.3.12/agent_runner/goal.py +428 -0
  15. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/runner.py +33 -1
  16. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/service_unit.py +3 -1
  17. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/vcs_state.py +12 -28
  18. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/architecture.md +2 -0
  19. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/configuration.md +113 -1
  20. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/memory-efficiency.md +89 -0
  21. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/pyproject.toml +2 -1
  22. cli_agent_runner-0.3.12/tests/integration/test_goal_steering_e2e.py +479 -0
  23. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_config_error_consistency.py +1 -0
  24. cli_agent_runner-0.3.12/tests/invariants/test_goal_firewall.py +166 -0
  25. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_import_footprint.py +5 -0
  26. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_module_boundaries.py +32 -5
  27. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_no_vacuous_invariants.py +1 -0
  28. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_timeout_budget_invariant.py +27 -0
  29. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_config.py +438 -1
  30. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_docgen.py +1 -1
  31. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_git_timeout.py +9 -4
  32. cli_agent_runner-0.3.12/tests/unit/test_goal_assessor.py +264 -0
  33. cli_agent_runner-0.3.12/tests/unit/test_goal_executor.py +222 -0
  34. cli_agent_runner-0.3.12/tests/unit/test_goal_firewall_behavioral.py +173 -0
  35. cli_agent_runner-0.3.12/tests/unit/test_goal_ledger.py +136 -0
  36. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_outer_round_ceiling.py +37 -0
  37. cli_agent_runner-0.3.12/tests/unit/test_run_bounded.py +133 -0
  38. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_runner.py +86 -0
  39. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_service_unit.py +35 -0
  40. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/.codecov.yml +0 -0
  41. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  42. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  43. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  44. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  45. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/.github/dependabot.yml +0 -0
  46. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/.github/workflows/ci.yml +0 -0
  47. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/.github/workflows/release.yml +0 -0
  48. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/.gitignore +0 -0
  49. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/BACKLOG.md +0 -0
  50. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/CODE_OF_CONDUCT.md +0 -0
  51. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/CONTRIBUTING.md +0 -0
  52. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/LICENSE +0 -0
  53. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/README.md +0 -0
  54. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/README.zh.md +0 -0
  55. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/SECURITY.md +0 -0
  56. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/__init__.py +0 -0
  57. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_emit/__init__.py +0 -0
  58. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_emit/agent.py +0 -0
  59. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_emit/memory.py +0 -0
  60. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_emit/rounds.py +0 -0
  61. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_emit/serve.py +0 -0
  62. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_install.py +0 -0
  63. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_monitor_detectors.py +0 -0
  64. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_monitor_registry.py +0 -0
  65. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_monitor_state.py +0 -0
  66. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_notify.py +0 -0
  67. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_observe.py +0 -0
  68. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_plugin_manifest.py +0 -0
  69. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_plugin_scan.py +0 -0
  70. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_procwait.py +0 -0
  71. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_redact.py +0 -0
  72. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_registry.py +0 -0
  73. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_resolve.py +0 -0
  74. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_round_outcome.py +0 -0
  75. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_round_support.py +0 -0
  76. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_substrate.py +0 -0
  77. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/_throttle.py +0 -0
  78. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/agent_runtime.py +0 -0
  79. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/api.py +0 -0
  80. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/api_types.py +0 -0
  81. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/__init__.py +0 -0
  82. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/_constants.py +0 -0
  83. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  84. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/codewhale.py +0 -0
  85. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/gemini.py +0 -0
  86. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/kimi.py +0 -0
  87. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/pi.py +0 -0
  88. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/__init__.py +0 -0
  89. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/__main__.py +0 -0
  90. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/_serve_cgroup.py +0 -0
  91. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/_serve_round.py +0 -0
  92. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/common.py +0 -0
  93. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/doctor_cmd.py +0 -0
  94. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/events_cmd.py +0 -0
  95. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/init_cmd.py +0 -0
  96. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/install_cmd.py +0 -0
  97. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/migrate_cmd.py +0 -0
  98. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/monitor_cmd.py +0 -0
  99. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/peek_cmd.py +0 -0
  100. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/round_cmd.py +0 -0
  101. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/serve_cmd.py +0 -0
  102. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/service_cmd.py +0 -0
  103. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/cli/upgrade_cmd.py +0 -0
  104. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/clock.py +0 -0
  105. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/config/errors.py +0 -0
  106. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/config/validators.py +0 -0
  107. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/context_store.py +0 -0
  108. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/defenses.py +0 -0
  109. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/event_log.py +0 -0
  110. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/hooks.py +0 -0
  111. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/host_health.py +0 -0
  112. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/http_progress.py +0 -0
  113. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/lifecycle.py +0 -0
  114. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/metrics.py +0 -0
  115. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/migrations.py +0 -0
  116. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/monitor.py +0 -0
  117. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/phase_select.py +0 -0
  118. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/presets/__init__.py +0 -0
  119. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/presets/aider.toml +0 -0
  120. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/presets/claude.toml +0 -0
  121. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/presets/codewhale.toml +0 -0
  122. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/presets/gemini.toml +0 -0
  123. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/presets/kimi.toml +0 -0
  124. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/presets/pi.toml +0 -0
  125. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/prompt_loader.py +0 -0
  126. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/remote_relay.py +0 -0
  127. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/round_log.py +0 -0
  128. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/round_view.py +0 -0
  129. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/scaffold.py +0 -0
  130. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/schedule.py +0 -0
  131. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/agent_runner/startup_check.py +0 -0
  132. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/build.sh +0 -0
  133. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/deploy/Dockerfile +0 -0
  134. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/deploy/docker-compose.yml +0 -0
  135. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/README.md +0 -0
  136. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/commands.md +0 -0
  137. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/events.md +0 -0
  138. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/long-running-agents.md +0 -0
  139. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/migrations/0.2.md +0 -0
  140. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/migrations/0.3.md +0 -0
  141. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/plugins.md +0 -0
  142. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/quickstart.md +0 -0
  143. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/recipes/aider.md +0 -0
  144. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/recipes/codewhale.md +0 -0
  145. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/recipes/container-pi.md +0 -0
  146. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/recipes/deploy-all-in-one.md +0 -0
  147. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/recipes/kimi.md +0 -0
  148. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/recipes/pi.md +0 -0
  149. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/runbook.md +0 -0
  150. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/docs/thesis.md +0 -0
  151. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/__init__.py +0 -0
  152. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/_clock.py +0 -0
  153. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/_test_helpers.py +0 -0
  154. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/conftest.py +0 -0
  155. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/contract/__init__.py +0 -0
  156. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/contract/test_public_api_surface.py +0 -0
  157. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/e2e/__init__.py +0 -0
  158. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/e2e/conftest.py +0 -0
  159. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  160. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/e2e/test_e2e_install_systemd.py +0 -0
  161. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  162. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/e2e/test_e2e_pi_resume.py +0 -0
  163. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/e2e/test_e2e_pre_oom.py +0 -0
  164. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  165. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  166. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  167. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  168. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/generate_vulture_whitelist.py +0 -0
  169. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/__init__.py +0 -0
  170. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_auto_stop_e2e.py +0 -0
  171. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_bounded_run.py +0 -0
  172. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_cooperative_grace_property.py +0 -0
  173. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_cooperative_stop_signal.py +0 -0
  174. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_fresh_eyes_signal.py +0 -0
  175. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_grace_kill_emission.py +0 -0
  176. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_install_dry_run.py +0 -0
  177. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_monitor_seeded.py +0 -0
  178. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_plugin_real_flow.py +0 -0
  179. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  180. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_reap_property.py +0 -0
  181. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  182. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_scaffold_presets.py +0 -0
  183. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_serve_loop.py +0 -0
  184. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_spawn_round_mem_floor.py +0 -0
  185. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_spawn_round_wedged.py +0 -0
  186. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_substrate_fingerprint.py +0 -0
  187. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/integration/test_transient_error_backoff.py +0 -0
  188. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/__init__.py +0 -0
  189. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/_docs.py +0 -0
  190. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/_event_scan.py +0 -0
  191. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_architecture.py +0 -0
  192. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_atomic_write_enforced.py +0 -0
  193. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_authored_facts_declared.py +0 -0
  194. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  195. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_bdd_test_conventions.py +0 -0
  196. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_brake_defaults_off.py +0 -0
  197. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_catalogs.py +0 -0
  198. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_classification_ssot.py +0 -0
  199. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_config_value_set_ssot.py +0 -0
  200. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_container_stop_bound.py +0 -0
  201. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
  202. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  203. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
  204. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_cli_claims.py +0 -0
  205. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_operator_surface.py +0 -0
  206. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
  207. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  208. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_usage_event_fields.py +0 -0
  209. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_docs_generated.py +0 -0
  210. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_docs_index_complete.py +0 -0
  211. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_entry_points_resolve.py +0 -0
  212. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_event_int_coercion.py +0 -0
  213. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_event_kind_registry.py +0 -0
  214. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_event_kinds_ssot.py +0 -0
  215. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_events_doc_contract.py +0 -0
  216. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
  217. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_hard_wall_signal.py +0 -0
  218. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_hook_contract_docs.py +0 -0
  219. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_layer_2_loop_size.py +0 -0
  220. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_migrations_registry_ssot.py +0 -0
  221. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_module_sizes.py +0 -0
  222. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_monitor_self_exclusion.py +0 -0
  223. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_no_ai_signatures.py +0 -0
  224. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_no_asyncio_select.py +0 -0
  225. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_no_direct_inspect_import.py +0 -0
  226. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_no_internal_refs.py +0 -0
  227. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_no_price_logic.py +0 -0
  228. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  229. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_no_raw_time.py +0 -0
  230. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_no_version_refs.py +0 -0
  231. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_peek_schema_version.py +0 -0
  232. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_phase_select_stateless.py +0 -0
  233. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_plugins_config_stable.py +0 -0
  234. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_procwait_shield.py +0 -0
  235. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  236. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  237. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_round_alloc_growth.py +0 -0
  238. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_round_log_decode_boundary.py +0 -0
  239. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_round_num_single_source.py +0 -0
  240. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_round_result_stable.py +0 -0
  241. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  242. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  243. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_throttle_stateless_imports.py +0 -0
  244. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_upstream_schema_canary.py +0 -0
  245. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  246. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/linux/__init__.py +0 -0
  247. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/linux/test_memory_high_nudge_e2e.py +0 -0
  248. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/literate/__init__.py +0 -0
  249. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/literate/parser.py +0 -0
  250. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/literate/test_parser.py +0 -0
  251. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/literate/test_quickstart.py +0 -0
  252. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/__init__.py +0 -0
  253. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime.py +0 -0
  254. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime_container.py +0 -0
  255. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime_grace.py +0 -0
  256. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
  257. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime_progress.py +0 -0
  258. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime_reap.py +0 -0
  259. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  260. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_api_assemble_prompt.py +0 -0
  261. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_api_events_stream.py +0 -0
  262. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_api_install.py +0 -0
  263. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_api_observation.py +0 -0
  264. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_api_read_round_num.py +0 -0
  265. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_api_resolve_phase.py +0 -0
  266. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_api_service.py +0 -0
  267. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_api_types.py +0 -0
  268. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_apply_reap_grace_env.py +0 -0
  269. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_apply_resume_env.py +0 -0
  270. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_auto_stop_gating.py +0 -0
  271. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_catalogs.py +0 -0
  272. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_cgroup_probe.py +0 -0
  273. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_claude_error_detector.py +0 -0
  274. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_claude_foreign_schema.py +0 -0
  275. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_cli.py +0 -0
  276. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_cli_common.py +0 -0
  277. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_cli_init_install.py +0 -0
  278. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_cli_main_boundary.py +0 -0
  279. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_cli_monitor_http.py +0 -0
  280. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  281. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_cli_upgrade.py +0 -0
  282. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_codewhale_code_precedence.py +0 -0
  283. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_codewhale_plugin.py +0 -0
  284. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_coerce_int_poison.py +0 -0
  285. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_config_fresh_eyes.py +0 -0
  286. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_config_max_rounds.py +0 -0
  287. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_config_nested_strictness.py +0 -0
  288. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_config_prompt_delivery.py +0 -0
  289. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_config_resume_guard.py +0 -0
  290. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_config_schedule.py +0 -0
  291. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_config_stop_file.py +0 -0
  292. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_config_strictness.py +0 -0
  293. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  294. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_config_transient_error_action.py +0 -0
  295. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_context_store.py +0 -0
  296. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_control_char_config.py +0 -0
  297. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_cooperative_signal_env.py +0 -0
  298. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_defenses.py +0 -0
  299. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_detect_hung_latch.py +0 -0
  300. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_detector_isolation.py +0 -0
  301. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_doctor_cmd.py +0 -0
  302. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_effective_throttle_view.py +0 -0
  303. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_emit_config_migrated.py +0 -0
  304. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_emit_schedule.py +0 -0
  305. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
  306. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_event_log.py +0 -0
  307. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_events.py +0 -0
  308. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_events_cmd.py +0 -0
  309. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_events_cmd_rollover.py +0 -0
  310. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_events_cmd_tail.py +0 -0
  311. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_events_jsonl_decode.py +0 -0
  312. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_example_plugin.py +0 -0
  313. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_exec_prefix.py +0 -0
  314. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_exit_cause.py +0 -0
  315. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_exit_classifier.py +0 -0
  316. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  317. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_gemini_plugin.py +0 -0
  318. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_hook_redact_order.py +0 -0
  319. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_hooks.py +0 -0
  320. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_host_health.py +0 -0
  321. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_http_progress.py +0 -0
  322. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_http_progress_epoch.py +0 -0
  323. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_init_entry_points.py +0 -0
  324. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
  325. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_kill_pgroup_shielding.py +0 -0
  326. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_kimi_plugin.py +0 -0
  327. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_lifecycle.py +0 -0
  328. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_loader_split.py +0 -0
  329. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_metrics.py +0 -0
  330. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_metrics_cgroup_usage.py +0 -0
  331. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_metrics_swap.py +0 -0
  332. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_migrate_cmd.py +0 -0
  333. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_migrations.py +0 -0
  334. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_assembly.py +0 -0
  335. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_cmd_severity.py +0 -0
  336. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_dedup_identity.py +0 -0
  337. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_dedup_rearm.py +0 -0
  338. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  339. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  340. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  341. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_detectors.py +0 -0
  342. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_event_tail.py +0 -0
  343. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_loop.py +0 -0
  344. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_on_alert.py +0 -0
  345. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_remote.py +0 -0
  346. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
  347. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_schedule_pause.py +0 -0
  348. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_network_blip_signal_guard.py +0 -0
  349. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_notify.py +0 -0
  350. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_open_round_log.py +0 -0
  351. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_orphan_chain_single_pass.py +0 -0
  352. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_peek_argparse.py +0 -0
  353. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_peek_project_isolation.py +0 -0
  354. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_peek_schedule.py +0 -0
  355. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_peek_select.py +0 -0
  356. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_phase_agent_launch.py +0 -0
  357. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_phase_profiles.py +0 -0
  358. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_phase_select.py +0 -0
  359. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_pi_plugin.py +0 -0
  360. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_plugin_constants.py +0 -0
  361. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_plugin_manifest.py +0 -0
  362. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_plugin_scan_parity.py +0 -0
  363. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_plugins_config_fields.py +0 -0
  364. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_presets.py +0 -0
  365. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_procwait.py +0 -0
  366. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_prompt_loader.py +0 -0
  367. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_read_json_hardening.py +0 -0
  368. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_redact.py +0 -0
  369. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_resolve.py +0 -0
  370. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_resolve_dirty_tree.py +0 -0
  371. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_round_cmd.py +0 -0
  372. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_round_cmd_sigterm.py +0 -0
  373. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_round_log_helpers.py +0 -0
  374. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_round_outcome.py +0 -0
  375. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_round_outcome_agent_axis.py +0 -0
  376. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_round_view.py +0 -0
  377. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_runner_sidecar_ordering.py +0 -0
  378. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_runner_throttle.py +0 -0
  379. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_scaffold.py +0 -0
  380. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_schedule.py +0 -0
  381. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_cmd_bounded.py +0 -0
  382. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_config_broken.py +0 -0
  383. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_crash_loop.py +0 -0
  384. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_mem_gate.py +0 -0
  385. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_phase_schedule.py +0 -0
  386. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_policy.py +0 -0
  387. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_round.py +0 -0
  388. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
  389. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_round_log.py +0 -0
  390. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_round_oom_killed.py +0 -0
  391. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_round_terminate.py +0 -0
  392. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_round_wait_exit.py +0 -0
  393. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_schedule_gate.py +0 -0
  394. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_sentinel.py +0 -0
  395. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_serve_wakeup.py +0 -0
  396. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_service_mode.py +0 -0
  397. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_startup_battery_exit.py +0 -0
  398. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_startup_check.py +0 -0
  399. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_substrate.py +0 -0
  400. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
  401. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_terminate_round_wait_exit.py +0 -0
  402. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_throttle_back_off_ladder.py +0 -0
  403. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_throttle_epoch_bound.py +0 -0
  404. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_throttle_plugin_classification.py +0 -0
  405. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_upgrade_migrate.py +0 -0
  406. {cli_agent_runner-0.3.11 → cli_agent_runner-0.3.12}/tests/unit/test_vcs_state.py +0 -0
@@ -6,6 +6,12 @@
6
6
  # bare name below marks one field "used". SSOT = the @dataclass definitions in
7
7
  # agent_runner/; a new field is covered by regenerating, never by editing here.
8
8
 
9
+ # agent_runner._bounded.BoundedResult
10
+ rc
11
+ stdout
12
+ stderr
13
+ timed_out
14
+
9
15
  # agent_runner._monitor_state.LocalSource
10
16
  log_dir
11
17
 
@@ -161,6 +167,11 @@ monitor
161
167
  phases
162
168
  plugins
163
169
  schedule
170
+ goal
171
+
172
+ # agent_runner.config.models.GoalConfig
173
+ ledger
174
+ checks
164
175
 
165
176
  # agent_runner.config.models.MonitorConfig
166
177
  auth_fail_patterns
@@ -232,6 +243,12 @@ pause_windows
232
243
  stash_idempotency_s
233
244
  dirty_action
234
245
 
246
+ # agent_runner.config.models._GoalCheckConfig
247
+ name
248
+ cmd
249
+ cwd
250
+ timeout_s
251
+
235
252
  # agent_runner.config.models._HostHealthBrakeConfig
236
253
  memory_high
237
254
  memory_high_step_pct
@@ -281,6 +298,11 @@ codifies
281
298
  guarded_by
282
299
  current_state
283
300
 
301
+ # agent_runner.goal.Advisory
302
+ observation
303
+ question
304
+ confidence
305
+
284
306
  # agent_runner.hooks.HookContext
285
307
  work_dir
286
308
  log_dir
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.12] - 2026-09-18
9
+
10
+ ### Added
11
+ - `[goal]` config table: a markdown lessons `ledger` (listed in `[prompt] files` at index >= 1, and — since it must survive the round's own `git stash` — required to sit under `log_dir` or outside `work_dir`) plus `[[goal.checks]]` (`name`, `cmd` argv, optional `timeout_s`) — objective, CLI-agnostic checks the round child runs after each round's agent exits, each emitting a `goal_check` event (`satisfied` bool, optional `value`).
12
+ - Treadmill assessor (advisory-only): across the last 3 completed rounds, if every round showed activity but at least one goal check stayed unsatisfied with an unchanged signature, serve writes one edge-triggered advisory to the ledger and emits a `goal_assessment` event (never on convergence). The ledger's `[prompt] files` membership carries it into the next round's prompt.
13
+ - Advisory/kill firewall: the goal loop never CAUSES a kill or give-up — the kill/give-up path reads events by kind and stays blind to `goal_check`/`goal_assessment`. A goal check's own wall time does count toward the round's duration, so a long check can DELAY (never trigger) the fast-spin `stalled_no_progress`/`crash_loop` breakers; config load warns if the total check budget reaches that window.
14
+
15
+ Check timeouts fold into the round's own timeout budget. No config migration required (v0.3.11 silently ignores an unrecognized `[goal]` table). `peek --json` schema stays `2.6`.
16
+
8
17
  ## [0.3.11] - 2026-09-17
9
18
 
10
19
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.3.11
3
+ Version: 0.3.12
4
4
  Summary: Restart-on-exit supervisor for autonomous CLI agents
5
5
  Project-URL: Homepage, https://github.com/wan9yu/cli-agent-runner
6
6
  Project-URL: Documentation, https://github.com/wan9yu/cli-agent-runner#readme
@@ -0,0 +1,135 @@
1
+ """Sanctioned timeout-bounded subprocess primitive.
2
+
3
+ Runs a command in its OWN session under a wall-clock timeout, escalating
4
+ TERM -> grace -> killpg(SIGKILL) on breach so a hung process leaves no
5
+ descendants (a backgrounded grandchild included, since killpg targets the
6
+ whole process group). Returns a ``BoundedResult`` with ``timed_out=True`` on
7
+ breach instead of raising -- the caller decides how to react. No ``clock``
8
+ parameter: ``subprocess.communicate(timeout=)`` self-times, there is no raw
9
+ wall-clock read here to abstract.
10
+
11
+ Two hardening properties beyond the plain TERM/KILL escalation:
12
+
13
+ - **Bounded even against a session-detached pipe holder.** A descendant that
14
+ ``setsid``-detaches out of the killed process group can still hold the
15
+ inherited stdout/stderr pipe open, so the final drain after ``killpg`` would
16
+ block forever waiting on EOF. Every drain is capped at ``_KILL_GRACE_S``; on
17
+ breach the read ends are closed and whatever was captured so far is returned.
18
+ The whole primitive therefore returns within ``timeout_s + 2*_KILL_GRACE_S``
19
+ (one grace for TERM->KILL, one for the post-kill drain) no matter what the
20
+ command spawns. A truly detached grandchild in its OWN session is NOT reaped
21
+ -- killpg cannot reach it; keep checks in a single process group.
22
+ - **Reap-on-interrupt.** A SIGTERM to the supervisor (see
23
+ ``cli.common.install_term_handler``, which raises ``KeyboardInterrupt``)
24
+ propagates out of ``communicate()`` while the command runs in its own
25
+ session and would otherwise never be signalled. Any ``BaseException`` out of
26
+ a wait therefore SIGKILLs the whole group, closes the pipes, and reaps under
27
+ one bounded ``_KILL_GRACE_S`` wait before re-raising, mirroring
28
+ ``agent_runtime._kill_pgroup``'s bounded-wait-after-SIGKILL.
29
+
30
+ Kept out of the startup import graph on purpose (see
31
+ tests/invariants/test_import_footprint.py): callers import it function-scope.
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import os
37
+ import signal
38
+ import subprocess # noqa: TID251 — agent_runner._bounded is the sanctioned timeout-bounded subprocess primitive
39
+ from dataclasses import dataclass
40
+ from pathlib import Path
41
+
42
+ _KILL_GRACE_S = 3 # grace between TERM and killpg(SIGKILL), and for the post-kill drain
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class BoundedResult:
47
+ rc: int
48
+ stdout: str
49
+ stderr: str
50
+ timed_out: bool
51
+
52
+
53
+ def _kill_group(proc: subprocess.Popen) -> None:
54
+ try:
55
+ os.killpg(proc.pid, signal.SIGKILL)
56
+ except OSError:
57
+ pass
58
+
59
+
60
+ def _close_pipes(proc: subprocess.Popen) -> None:
61
+ for pipe in (proc.stdout, proc.stderr):
62
+ if pipe is not None:
63
+ try:
64
+ pipe.close()
65
+ except OSError:
66
+ pass
67
+
68
+
69
+ def _drain_after_kill(proc: subprocess.Popen, exc: subprocess.TimeoutExpired) -> tuple[str, str]:
70
+ """Best-effort drain after ``killpg(SIGKILL)`` that never blocks unbounded.
71
+
72
+ A ``setsid``-detached descendant can still hold the stdout/stderr pipe open,
73
+ so stop waiting on it: close the read ends and reap the (already-SIGKILLed)
74
+ immediate child under one more ``_KILL_GRACE_S`` cap. ``TimeoutExpired.output``
75
+ is ``bytes`` even on this text-mode Popen (the partial read captured at
76
+ timeout bypasses the text-decoding wrapper), so decode it here with the
77
+ same ``errors="replace"`` policy; fall back to empty strings if the
78
+ attribute isn't bytes at all."""
79
+ _close_pipes(proc)
80
+ try:
81
+ proc.wait(timeout=_KILL_GRACE_S)
82
+ except subprocess.TimeoutExpired:
83
+ pass
84
+ out = (
85
+ exc.output.decode("utf-8", errors="replace")
86
+ if isinstance(exc.output, (bytes, bytearray))
87
+ else ""
88
+ )
89
+ err = (
90
+ exc.stderr.decode("utf-8", errors="replace")
91
+ if isinstance(exc.stderr, (bytes, bytearray))
92
+ else ""
93
+ )
94
+ return out, err
95
+
96
+
97
+ def run_bounded(argv: list[str], *, cwd: Path, timeout_s: int) -> BoundedResult:
98
+ """Run argv in its own session under a wall-clock timeout, escalating
99
+ TERM -> grace -> killpg on breach so a hung command leaves no reachable
100
+ descendants. Returns timed_out=True on breach -- never raises on breach.
101
+ A ``BaseException`` out of a wait (e.g. a SIGTERM-driven KeyboardInterrupt)
102
+ SIGKILLs the group, closes the pipes, and reaps under one bounded
103
+ ``_KILL_GRACE_S`` wait, then re-raises, so an interrupted check is torn
104
+ down (not left a zombie) rather than orphaned. ``errors="replace"`` keeps a
105
+ non-UTF-8 byte in the command's output from raising a UnicodeDecodeError
106
+ past the caller's fail-open guard."""
107
+ proc = subprocess.Popen(
108
+ argv,
109
+ cwd=cwd,
110
+ stdout=subprocess.PIPE,
111
+ stderr=subprocess.PIPE,
112
+ text=True,
113
+ errors="replace",
114
+ start_new_session=True,
115
+ )
116
+ try:
117
+ try:
118
+ out, err = proc.communicate(timeout=timeout_s)
119
+ return BoundedResult(rc=proc.returncode, stdout=out, stderr=err, timed_out=False)
120
+ except subprocess.TimeoutExpired:
121
+ proc.terminate()
122
+ try:
123
+ out, err = proc.communicate(timeout=_KILL_GRACE_S)
124
+ except subprocess.TimeoutExpired as exc:
125
+ _kill_group(proc)
126
+ out, err = _drain_after_kill(proc, exc)
127
+ return BoundedResult(rc=proc.returncode, stdout=out, stderr=err, timed_out=True)
128
+ except BaseException:
129
+ _kill_group(proc)
130
+ _close_pipes(proc)
131
+ try:
132
+ proc.wait(timeout=_KILL_GRACE_S)
133
+ except subprocess.TimeoutExpired:
134
+ pass
135
+ raise
@@ -21,6 +21,7 @@ from agent_runner.cli.init_cmd import _preset_names
21
21
  from agent_runner.config import (
22
22
  AgentConfig,
23
23
  Config,
24
+ GoalConfig,
24
25
  MonitorConfig,
25
26
  MonitorHostHealthConfig,
26
27
  PhasesConfig,
@@ -29,6 +30,7 @@ from agent_runner.config import (
29
30
  RuntimeConfig,
30
31
  ScheduleConfig,
31
32
  VcsConfig,
33
+ _GoalCheckConfig,
32
34
  _HostHealthBrakeConfig,
33
35
  _HostHealthDiskConfig,
34
36
  _HostHealthMemoryConfig,
@@ -72,6 +74,7 @@ _SECTIONS: list[tuple[str, type, list[tuple[str, type]]]] = [
72
74
  ("phases", PhasesConfig, []),
73
75
  ("plugins", PluginsConfig, []),
74
76
  ("schedule", ScheduleConfig, []),
77
+ ("goal", GoalConfig, [("goal.checks", _GoalCheckConfig)]),
75
78
  ]
76
79
 
77
80
 
@@ -102,13 +105,23 @@ def _field_table(dc: type) -> list[str]:
102
105
  return rows
103
106
 
104
107
 
108
+ # Nested sub-sections that are TOML arrays-of-tables (`[[name]]`), not single
109
+ # sub-tables (`[name]`): the parent field is a list/tuple of the sub-dataclass,
110
+ # so the header must double-bracket or a copy-pasted single `[goal.checks]` is
111
+ # rejected by the parser (_parse_goal expects a list).
112
+ _ARRAY_OF_TABLES_SECTIONS = frozenset({"goal.checks"})
113
+
114
+
105
115
  def render_config_schema_table() -> str:
106
116
  """Markdown sub-sections per Config dataclass with field/type/default."""
107
117
  parts: list[str] = []
108
118
  for name, dc, nested in _SECTIONS:
109
119
  parts += [f"### `[{name}]`", "", *_field_table(dc), ""]
110
120
  for sub_name, sub_dc in nested:
111
- parts += [f"#### `[{sub_name}]`", "", *_field_table(sub_dc), ""]
121
+ brackets = (
122
+ f"[[{sub_name}]]" if sub_name in _ARRAY_OF_TABLES_SECTIONS else f"[{sub_name}]"
123
+ )
124
+ parts += [f"#### `{brackets}`", "", *_field_table(sub_dc), ""]
112
125
  return "\n".join(parts).rstrip()
113
126
 
114
127
 
@@ -56,7 +56,7 @@ def outer_round_ceiling_s(cfg: Config, phase_arg: str | None) -> int:
56
56
  (cfg.profile_for(p).runtime.round_budget_s for p in (cfg.phases.list or [])),
57
57
  default=cfg.runtime.round_budget_s,
58
58
  )
59
- _, outer_ceiling = timeout_budget(inner)
59
+ _, outer_ceiling = timeout_budget(inner, goal_checks_allowance_s=cfg.goal_checks_allowance_s)
60
60
  return outer_ceiling
61
61
 
62
62
 
@@ -303,17 +303,18 @@ _STOP_GRACE_MARGIN_S = 10 # pad above _ROUND_TERM_GRACE_S for systemd stop-requ
303
303
  _ROUND_UNREAPED_RC = 137 # 128 + SIGKILL(9): reads as a kill in the crash-loop path
304
304
 
305
305
 
306
- def timeout_budget(round_budget_s: int) -> tuple[int, int]:
306
+ def timeout_budget(round_budget_s: int, *, goal_checks_allowance_s: int = 0) -> tuple[int, int]:
307
307
  """Single source for the round-timeout safety budget.
308
308
 
309
309
  Returns ``(timeout_stop_sec, outer_ceiling_s)``:
310
310
 
311
311
  - ``outer_ceiling_s`` — the in-process outer wall-clock ceiling for the
312
312
  round subprocess (``api.outer_round_ceiling_s``): ``round_budget_s``
313
- plus reap grace + git-commit ceiling + hook allowance, so it only trips
314
- when the round supervisor itself is wedged, never during its own
315
- bounded post-round cleanup. The reap margin must cover the widest grace
316
- ANY round can be configured with -- a cooperative agent's
313
+ plus reap grace + git-commit ceiling + hook allowance + the goal-check
314
+ executor's own allowance, so it only trips when the round supervisor
315
+ itself is wedged, never during its own bounded post-round cleanup (nor
316
+ a slow-but-still-bounded goal check). The reap margin must cover the
317
+ widest grace ANY round can be configured with -- a cooperative agent's
317
318
  ``sigterm_grace_s`` is boot-capped at ``_ROUND_TERM_GRACE_S``
318
319
  (``config/models.py``'s mirror of this module's single source), not
319
320
  the non-cooperative default ``_REAP_GRACE_S``, so the margin is
@@ -324,12 +325,19 @@ def timeout_budget(round_budget_s: int) -> tuple[int, int]:
324
325
  SIGTERM to reach and drain the round (``_ROUND_TERM_GRACE_S``) plus a
325
326
  stop-request overhead pad, so `systemctl stop` never SIGKILLs a round
326
327
  that is draining normally.
328
+
329
+ ``goal_checks_allowance_s`` (default 0, byte-identical to the old
330
+ signature when omitted) is the caller's ``GoalConfig.checks_allowance_s``
331
+ when ``[goal]`` is configured -- folded into this ONE ceiling rather than
332
+ a second, separate one, so the goal-check path can never itself trip
333
+ ``round_supervisor_wedged``.
327
334
  """
328
335
  outer_ceiling_s = (
329
336
  round_budget_s
330
337
  + max(_REAP_GRACE_S, _ROUND_TERM_GRACE_S)
331
338
  + _GIT_COMMIT_TIMEOUT_S
332
339
  + _HOOK_ALLOWANCE_S
340
+ + goal_checks_allowance_s
333
341
  )
334
342
  timeout_stop_sec = outer_ceiling_s + _ROUND_TERM_GRACE_S + _STOP_GRACE_MARGIN_S
335
343
  return timeout_stop_sec, outer_ceiling_s
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.3.11'
22
- __version_tuple__ = version_tuple = (0, 3, 11)
21
+ __version__ = version = '0.3.12'
22
+ __version_tuple__ = version_tuple = (0, 3, 12)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -40,6 +40,7 @@ from agent_runner.config.models import ( # noqa: F401 — public re-export
40
40
  _VCS_ALLOWED_FIELDS,
41
41
  AgentConfig,
42
42
  Config,
43
+ GoalConfig,
43
44
  MonitorConfig,
44
45
  MonitorHostHealthConfig,
45
46
  PhaseOverride,
@@ -50,17 +51,22 @@ from agent_runner.config.models import ( # noqa: F401 — public re-export
50
51
  RuntimeConfig,
51
52
  ScheduleConfig,
52
53
  VcsConfig,
54
+ _GoalCheckConfig,
53
55
  _HostHealthBrakeConfig,
54
56
  _HostHealthDiskConfig,
55
57
  _HostHealthMemoryConfig,
56
58
  _HostHealthPressureConfig,
57
59
  )
58
- from agent_runner.config.validators import _reject_control_chars # noqa: F401
60
+ from agent_runner.config.validators import ( # noqa: F401 — public re-export
61
+ _reject_control_chars,
62
+ _resolve_against_work_dir,
63
+ )
59
64
 
60
65
  __all__ = [
61
66
  "AgentConfig",
62
67
  "Config",
63
68
  "ConfigError",
69
+ "GoalConfig",
64
70
  "MonitorConfig",
65
71
  "MonitorHostHealthConfig",
66
72
  "PhaseOverride",
@@ -7,12 +7,14 @@ Thin glue by design — every table's own validation lives in ``parsers.py``.
7
7
  from __future__ import annotations
8
8
 
9
9
  import tomllib
10
+ import warnings
10
11
  from pathlib import Path
11
12
 
12
13
  from agent_runner.config.errors import ConfigError
13
14
  from agent_runner.config.models import Config, PhasesConfig
14
15
  from agent_runner.config.parsers import (
15
16
  _parse_agent,
17
+ _parse_goal,
16
18
  _parse_monitor,
17
19
  _parse_phase_overrides,
18
20
  _parse_plugins,
@@ -56,6 +58,102 @@ def _reject_static_resume_flag(cfg) -> None:
56
58
  )
57
59
 
58
60
 
61
+ def _reject_goal_ledger_not_listed(cfg: Config) -> None:
62
+ """A [goal].ledger doesn't exist at cold start -- it's written by the agent
63
+ mid-run -- so it must ride in [prompt] files at index >= 1: index 0 is a
64
+ fatal-on-missing read (prompt_loader.py), and the single `file=` form can
65
+ never carry a second entry at all. Checks every configured phase's own
66
+ effective files list, mirroring Config.profile_for's override-or-base
67
+ resolution rule for prompt_files (no override -> the base [prompt] table)."""
68
+ if cfg.goal is None:
69
+ return
70
+ ledger = cfg.goal.ledger
71
+ phase_names: list[str | None] = list(cfg.phases.list) if cfg.phases.list else [None]
72
+ for phase_name in phase_names:
73
+ where = "[prompt]" if phase_name is None else f"[phases.{phase_name}.prompt]"
74
+ prompt_files = cfg.profile_for(phase_name).prompt_files
75
+ # No override -> [] via cfg.prompt.files, which is already [] when the
76
+ # single `file=` form is in play (parser-enforced): never carries a
77
+ # second entry.
78
+ files = (
79
+ [str(p) for p in prompt_files]
80
+ if prompt_files is not None
81
+ else [str(p) for p in cfg.prompt.files]
82
+ )
83
+ if ledger not in files or files.index(ledger) == 0:
84
+ raise ConfigError(
85
+ f"{where}: [goal] ledger {ledger!r} is not listed in the resolved "
86
+ "prompt files at index >= 1 (the single `file=` form can't carry it, "
87
+ f"and index 0 doesn't exist at cold start); list {ledger!r} in "
88
+ "[prompt] files at index >= 1"
89
+ )
90
+
91
+
92
+ def _reject_goal_ledger_in_stash_swept_tree(cfg: Config) -> None:
93
+ """The ``[goal].ledger`` is supervisor-written and must PERSIST across
94
+ rounds, but the round's own vcs step sweeps the work tree: under the
95
+ DEFAULT ``[vcs] dirty_action="stash"`` a ``git stash push -u`` stashes
96
+ every dirty path EXCEPT ``log_dir`` (see ``vcs_state``'s exclude pathspec).
97
+ A ledger resolved INSIDE ``work_dir`` but OUTSIDE ``log_dir`` would be
98
+ stashed away as orphan work right after the round it fired for -- the steer
99
+ would silently last one round.
100
+
101
+ Pure path arithmetic (no git): require the resolved ledger to sit OUTSIDE
102
+ ``work_dir``, OR UNDER ``log_dir`` (the one work-tree path the stash
103
+ pathspec excludes). Composes with -- and runs after --
104
+ ``_reject_goal_ledger_not_listed``."""
105
+ if cfg.goal is None:
106
+ return
107
+ # .resolve() all three: an absolute log_dir (or ledger) is NOT canonicalized
108
+ # by _expand_and_resolve (only a relative path is), so a symlinked absolute
109
+ # log_dir (e.g. /tmp -> /private/tmp on macOS) would otherwise FALSE-REJECT
110
+ # a ledger genuinely under it -- work_dir is always canonical already
111
+ # (loader.load_config resolves it unconditionally), so this is a no-op there.
112
+ ledger = Path(cfg.goal.ledger).resolve()
113
+ work_dir = cfg.runtime.work_dir.resolve()
114
+ log_dir = cfg.runtime.log_dir.resolve()
115
+ if ledger.is_relative_to(work_dir) and not ledger.is_relative_to(log_dir):
116
+ raise ConfigError(
117
+ f"[goal] ledger {cfg.goal.ledger!r} resolves inside runtime.work_dir "
118
+ f"({work_dir}) but outside runtime.log_dir ({log_dir}): the default "
119
+ '[vcs] dirty_action="stash" would stash it away as orphan work after '
120
+ "the round it fired for, so the steer would last only one round. Put "
121
+ 'the ledger under log_dir (e.g. ledger = "logs/lessons.md") or at an '
122
+ "absolute path outside work_dir."
123
+ )
124
+
125
+
126
+ def _warn_goal_checks_budget_disarms_fast_spin(cfg: Config) -> None:
127
+ """A goal check's wall time counts inside ``round_duration_s``, the
128
+ discriminator for serve's fast-spin give-up breakers (``crash_loop`` counts
129
+ a crash only under ``CRASH_LOOP_SHORT_EXIT_S``, ``stalled_no_progress`` only
130
+ under ``_NO_PROGRESS_SHORT_S``). A goal-check budget at or above that short
131
+ window can push an otherwise fast-crashing round past the threshold and
132
+ DISARM the breaker. The goal loop still never CAUSES a give-up -- the
133
+ firewall keeps the kill/give-up path blind to ``goal_*`` events -- so this
134
+ is the one place a long check can only DELAY (never trigger) one. A
135
+ legitimate ``pytest`` goal-check may well exceed the window, so this WARNS
136
+ (via the same load-time ``warnings`` channel plugin-load failures use)
137
+ rather than rejecting."""
138
+ if cfg.goal is None:
139
+ return
140
+ # Function-scope import: config/ must not import _serve_policy at module
141
+ # scope (_serve_policy imports agent_runner.config for ConfigError -- a
142
+ # cycle). By load_config-call time the module is importable.
143
+ from agent_runner._serve_policy import _NO_PROGRESS_SHORT_S
144
+
145
+ allowance = cfg.goal.checks_allowance_s
146
+ if allowance >= _NO_PROGRESS_SHORT_S:
147
+ warnings.warn(
148
+ f"[goal] total goal-check budget is {allowance}s, at or above the "
149
+ f"{_NO_PROGRESS_SHORT_S}s fast-spin give-up window: a goal check's wall "
150
+ "time counts toward the round's own duration, so a long check can delay "
151
+ "(never trigger) the stalled_no_progress/crash_loop breakers. Keep total "
152
+ "check time under that window if you rely on them.",
153
+ stacklevel=2,
154
+ )
155
+
156
+
59
157
  def _check_schema_version(raw: dict) -> None:
60
158
  """Boot gate: a config's ``schema_version`` decouples the on-disk config
61
159
  shape from the installed package version, so a config can be migrated
@@ -136,6 +234,11 @@ def load_config(toml_path: Path) -> Config:
136
234
  monitor = _parse_monitor(_require_table(raw, "monitor"))
137
235
  plugins = _parse_plugins(_require_table(raw, "plugins"))
138
236
  schedule_cfg = _parse_schedule(_require_table(raw, "schedule"))
237
+ goal = (
238
+ _parse_goal(_require_table(raw, "goal"), project_name=project_name, work_dir=work_dir)
239
+ if "goal" in raw
240
+ else None
241
+ )
139
242
 
140
243
  cfg = Config(
141
244
  agent=agent,
@@ -146,6 +249,7 @@ def load_config(toml_path: Path) -> Config:
146
249
  phases=phases_cfg,
147
250
  plugins=plugins,
148
251
  schedule=schedule_cfg,
252
+ goal=goal,
149
253
  )
150
254
 
151
255
  # Verify + import discovered agent_runner.plugins entry points now that
@@ -158,5 +262,8 @@ def load_config(toml_path: Path) -> Config:
158
262
  apply_plugin_disable(plugins.disable)
159
263
 
160
264
  _reject_static_resume_flag(cfg)
265
+ _reject_goal_ledger_not_listed(cfg)
266
+ _reject_goal_ledger_in_stash_swept_tree(cfg)
267
+ _warn_goal_checks_budget_disarms_fast_spin(cfg)
161
268
 
162
269
  return cfg
@@ -32,6 +32,15 @@ DEFAULT_TERMINAL_MARKER = '"type":"result"'
32
32
  # agent's preset gets from the round leader (see AgentConfig.sigterm_grace_s).
33
33
  DEFAULT_SIGTERM_GRACE_S = 10
34
34
 
35
+ # Default [[goal.checks]] timeout_s: an objective goal-check is a lightweight
36
+ # scriptable sanity command (e.g. a lint/test run), not another agent round.
37
+ DEFAULT_GOAL_CHECK_TIMEOUT_S = 10
38
+
39
+ # Boot cap for [[goal.checks]] timeout_s: a value above this is rejected
40
+ # outright, not clamped, so one slow check can't silently eat the round's
41
+ # own time budget.
42
+ _MAX_GOAL_CHECK_TIMEOUT_S = 30
43
+
35
44
  # Ceiling for [agent] sigterm_grace_s: sits a STRICT ~3s BELOW _serve_policy's
36
45
  # _ROUND_TERM_GRACE_S (15, the supervisor's own wait for the round leader) --
37
46
  # NOT equal to it. The margin buys the round LEADER's own cooperative
@@ -306,6 +315,56 @@ class ScheduleConfig:
306
315
  return bool(self.run_windows or self.pause_windows)
307
316
 
308
317
 
318
+ @dataclass(frozen=True)
319
+ class _GoalCheckConfig:
320
+ """One ``[[goal.checks]]`` entry: an objective, scriptable check (e.g. a
321
+ lint or test run) the operator trusts over the agent's own self-report.
322
+ ``cwd`` is a raw operator string, resolved by the check runner, not here."""
323
+
324
+ name: str
325
+ cmd: list[str]
326
+ cwd: str | None = None
327
+ timeout_s: int = DEFAULT_GOAL_CHECK_TIMEOUT_S
328
+
329
+
330
+ @dataclass(frozen=True)
331
+ class GoalConfig:
332
+ """The ``[goal]`` table: objective goal-checks plus the lessons-ledger
333
+ path threaded into the prompt across rounds. ``Config.goal`` stays
334
+ ``None`` when ``[goal]`` is absent -- the steering loop is opt-in.
335
+
336
+ ``ledger`` is required; ``checks`` defaults to an empty tuple (a ledger-only
337
+ ``[goal]`` runs the treadmill assessor with nothing objective to converge
338
+ on -- valid, if degenerate), so it is declared last with a default to match
339
+ the parser's ``goal.checks`` default."""
340
+
341
+ ledger: str
342
+ checks: tuple[_GoalCheckConfig, ...] = ()
343
+
344
+ @property
345
+ def checks_allowance_s(self) -> int:
346
+ """Worst-case wall-clock the goal-check executor can consume in one
347
+ round, so the round-timeout budget (``_serve_policy.timeout_budget``)
348
+ can fold it into the single outer ceiling -- a slow (or hung) check
349
+ must never trip ``round_supervisor_wedged`` itself.
350
+
351
+ The executor runs every check SEQUENTIALLY with no short-circuit on
352
+ failure/timeout, and each breach costs TWO ``_KILL_GRACE_S`` windows,
353
+ not one: ``run_bounded`` spends up to ``timeout_s + 2*_KILL_GRACE_S``
354
+ on a breach (one grace for TERM->killpg, one for the bounded post-kill
355
+ drain that never blocks on a session-detached pipe holder). So the
356
+ all-hang worst case is every check's own timeout PLUS two kill graces
357
+ EACH.
358
+
359
+ Local import: this module sits on the frozen startup graph
360
+ (``test_import_footprint.py``), and ``_bounded`` does not -- a
361
+ module-scope import here would pull it onto that graph.
362
+ """
363
+ from agent_runner._bounded import _KILL_GRACE_S
364
+
365
+ return sum(c.timeout_s for c in self.checks) + len(self.checks) * 2 * _KILL_GRACE_S
366
+
367
+
309
368
  @dataclass(frozen=True)
310
369
  class Profile:
311
370
  """Fully-resolved per-phase execution profile.
@@ -333,6 +392,14 @@ class Config:
333
392
  phases: PhasesConfig = field(default_factory=PhasesConfig)
334
393
  plugins: PluginsConfig = field(default_factory=PluginsConfig)
335
394
  schedule: ScheduleConfig = field(default_factory=ScheduleConfig)
395
+ goal: GoalConfig | None = None
396
+
397
+ @property
398
+ def goal_checks_allowance_s(self) -> int:
399
+ """``goal.checks_allowance_s``, or 0 when ``[goal]`` is absent -- the
400
+ one-liner both ``_lifecycle.outer_round_ceiling_s`` and
401
+ ``service_unit``'s ``TimeoutStopSec`` derivation need, deduped here."""
402
+ return self.goal.checks_allowance_s if self.goal is not None else 0
336
403
 
337
404
  def profile_for(self, phase: str | None) -> Profile:
338
405
  """Resolve the effective execution profile for a phase.
@@ -429,3 +496,7 @@ _PROMPT_ALLOWED_FIELDS = frozenset(
429
496
 
430
497
  # Keys allowed under a [schedule] table (top-level or per-phase).
431
498
  _SCHEDULE_ALLOWED_FIELDS = frozenset({"timezone", "run_windows", "pause_windows"})
499
+
500
+ # Keys allowed under the [goal] table and each [[goal.checks]] entry.
501
+ _GOAL_ALLOWED_FIELDS = frozenset(f.name for f in dataclasses.fields(GoalConfig))
502
+ _GOAL_CHECK_ALLOWED_FIELDS = frozenset(f.name for f in dataclasses.fields(_GoalCheckConfig))