cli-agent-runner 0.3.4__tar.gz → 0.3.5__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 (415) hide show
  1. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.vulture-whitelist.py +2 -0
  2. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/CHANGELOG.md +5 -0
  3. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/PKG-INFO +1 -1
  4. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_plugin_manifest.py +20 -0
  5. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_serve_policy.py +13 -2
  6. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_version.py +2 -2
  7. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/agent_runtime.py +16 -4
  8. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/_serve_round.py +6 -4
  9. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/common.py +8 -2
  10. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/doctor_cmd.py +15 -0
  11. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/serve_cmd.py +49 -0
  12. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/config/models.py +26 -0
  13. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/config/parsers.py +24 -0
  14. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/runner.py +24 -0
  15. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/commands.md +1 -1
  16. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/configuration.md +15 -0
  17. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/memory-efficiency.md +23 -0
  18. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/plugins.md +2 -2
  19. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_auto_stop_e2e.py +9 -1
  20. cli_agent_runner-0.3.5/tests/integration/test_cooperative_grace_property.py +142 -0
  21. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_run_one_round_with_fake_agent.py +26 -0
  22. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_spawn_round_wedged.py +14 -0
  23. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_architecture.py +1 -0
  24. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_peek_schema_version.py +1 -1
  25. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_timeout_budget_invariant.py +32 -1
  26. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime_grace.py +82 -0
  27. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime_reap.py +53 -0
  28. cli_agent_runner-0.3.5/tests/unit/test_apply_reap_grace_env.py +92 -0
  29. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config.py +44 -0
  30. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_doctor_cmd.py +9 -0
  31. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_kill_pgroup_shielding.py +24 -0
  32. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_peek_sandbox_block.py +26 -0
  33. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugin_manifest.py +44 -0
  34. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_runner.py +35 -0
  35. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_service_unit.py +4 -3
  36. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.codecov.yml +0 -0
  37. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  38. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  39. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  40. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  41. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/dependabot.yml +0 -0
  42. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/workflows/ci.yml +0 -0
  43. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/workflows/release.yml +0 -0
  44. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.gitignore +0 -0
  45. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/BACKLOG.md +0 -0
  46. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/CODE_OF_CONDUCT.md +0 -0
  47. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/CONTRIBUTING.md +0 -0
  48. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/LICENSE +0 -0
  49. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/README.md +0 -0
  50. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/README.zh.md +0 -0
  51. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/SECURITY.md +0 -0
  52. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/__init__.py +0 -0
  53. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_docgen.py +0 -0
  54. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_emit/__init__.py +0 -0
  55. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_emit/agent.py +0 -0
  56. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_emit/memory.py +0 -0
  57. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_emit/plugins.py +0 -0
  58. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_emit/rounds.py +0 -0
  59. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_emit/serve.py +0 -0
  60. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_install.py +0 -0
  61. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_lifecycle.py +0 -0
  62. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_monitor_detectors.py +0 -0
  63. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_monitor_registry.py +0 -0
  64. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_monitor_state.py +0 -0
  65. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_notify.py +0 -0
  66. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_observe.py +0 -0
  67. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_plugin_checksum.py +0 -0
  68. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_plugin_sandbox.py +0 -0
  69. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_plugin_scan.py +0 -0
  70. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_procwait.py +0 -0
  71. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_redact.py +0 -0
  72. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_registry.py +0 -0
  73. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_resolve.py +0 -0
  74. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_round_outcome.py +0 -0
  75. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_round_support.py +0 -0
  76. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_sandbox_probe.py +0 -0
  77. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_substrate.py +0 -0
  78. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_throttle.py +0 -0
  79. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/api.py +0 -0
  80. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/api_types.py +0 -0
  81. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/__init__.py +0 -0
  82. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/_constants.py +0 -0
  83. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  84. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/codewhale.py +0 -0
  85. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
  86. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/gemini.py +0 -0
  87. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/kimi.py +0 -0
  88. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/pi.py +0 -0
  89. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/__init__.py +0 -0
  90. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/__main__.py +0 -0
  91. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/_serve_cgroup.py +0 -0
  92. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/events_cmd.py +0 -0
  93. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/init_cmd.py +0 -0
  94. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/install_cmd.py +0 -0
  95. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/migrate_cmd.py +0 -0
  96. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/monitor_cmd.py +0 -0
  97. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/peek_cmd.py +0 -0
  98. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/round_cmd.py +0 -0
  99. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/service_cmd.py +0 -0
  100. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/upgrade_cmd.py +0 -0
  101. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/clock.py +0 -0
  102. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/config/__init__.py +0 -0
  103. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/config/errors.py +0 -0
  104. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/config/loader.py +0 -0
  105. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/config/validators.py +0 -0
  106. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/context_store.py +0 -0
  107. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/defenses.py +0 -0
  108. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/events.py +0 -0
  109. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/hooks.py +0 -0
  110. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/host_health.py +0 -0
  111. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/http_progress.py +0 -0
  112. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/lifecycle.py +0 -0
  113. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/metrics.py +0 -0
  114. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/migrations.py +0 -0
  115. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/monitor.py +0 -0
  116. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/phase_select.py +0 -0
  117. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/__init__.py +0 -0
  118. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/aider.toml +0 -0
  119. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/claude.toml +0 -0
  120. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/codewhale.toml +0 -0
  121. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/gemini.toml +0 -0
  122. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/kimi.toml +0 -0
  123. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/pi.toml +0 -0
  124. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/prompt_loader.py +0 -0
  125. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/remote_relay.py +0 -0
  126. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/round_log.py +0 -0
  127. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/round_view.py +0 -0
  128. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/scaffold.py +0 -0
  129. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/schedule.py +0 -0
  130. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/service_unit.py +0 -0
  131. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/startup_check.py +0 -0
  132. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/vcs_state.py +0 -0
  133. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/build.sh +0 -0
  134. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/deploy/Dockerfile +0 -0
  135. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/deploy/docker-compose.yml +0 -0
  136. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/README.md +0 -0
  137. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/architecture.md +0 -0
  138. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/events.md +0 -0
  139. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/long-running-agents.md +0 -0
  140. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/migrations/0.2.md +0 -0
  141. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/migrations/0.3.md +0 -0
  142. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/quickstart.md +0 -0
  143. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/recipes/aider.md +0 -0
  144. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/recipes/codewhale.md +0 -0
  145. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/recipes/container-pi.md +0 -0
  146. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/recipes/deploy-all-in-one.md +0 -0
  147. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/recipes/kimi.md +0 -0
  148. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/recipes/pi.md +0 -0
  149. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/runbook.md +0 -0
  150. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/thesis.md +0 -0
  151. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/pyproject.toml +0 -0
  152. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/__init__.py +0 -0
  153. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/_clock.py +0 -0
  154. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/_test_helpers.py +0 -0
  155. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/conftest.py +0 -0
  156. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/contract/__init__.py +0 -0
  157. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/contract/test_public_api_surface.py +0 -0
  158. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/e2e/__init__.py +0 -0
  159. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/e2e/conftest.py +0 -0
  160. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  161. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/e2e/test_e2e_install_systemd.py +0 -0
  162. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  163. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  164. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  165. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  166. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  167. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/generate_vulture_whitelist.py +0 -0
  168. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/__init__.py +0 -0
  169. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_bounded_run.py +0 -0
  170. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_context_enricher_namespacing.py +0 -0
  171. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_dirty_handler_seam.py +0 -0
  172. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_fresh_eyes_signal.py +0 -0
  173. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_grace_kill_emission.py +0 -0
  174. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_install_dry_run.py +0 -0
  175. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_monitor_seeded.py +0 -0
  176. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_plugin_detector_loaded.py +0 -0
  177. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_plugin_owned_paths.py +0 -0
  178. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_plugin_real_flow.py +0 -0
  179. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  180. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_reap_property.py +0 -0
  181. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_scaffold_presets.py +0 -0
  182. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_serve_loop.py +0 -0
  183. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_spawn_round_mem_floor.py +0 -0
  184. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_substrate_fingerprint.py +0 -0
  185. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_transient_error_backoff.py +0 -0
  186. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/__init__.py +0 -0
  187. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/_docs.py +0 -0
  188. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/_event_scan.py +0 -0
  189. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_atomic_write_enforced.py +0 -0
  190. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_authored_facts_declared.py +0 -0
  191. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  192. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_bdd_test_conventions.py +0 -0
  193. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_builtin_plugin_names_sync.py +0 -0
  194. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_catalogs.py +0 -0
  195. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_classification_ssot.py +0 -0
  196. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_config_error_consistency.py +0 -0
  197. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_config_value_set_ssot.py +0 -0
  198. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_container_stop_bound.py +0 -0
  199. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
  200. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  201. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
  202. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_cli_claims.py +0 -0
  203. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_operator_surface.py +0 -0
  204. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
  205. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  206. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_usage_event_fields.py +0 -0
  207. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_docs_generated.py +0 -0
  208. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_docs_index_complete.py +0 -0
  209. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_entry_points_resolve.py +0 -0
  210. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_event_int_coercion.py +0 -0
  211. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_event_kind_registry.py +0 -0
  212. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_event_kinds_ssot.py +0 -0
  213. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_events_doc_contract.py +0 -0
  214. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
  215. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_hook_contract_docs.py +0 -0
  216. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_import_footprint.py +0 -0
  217. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_kill_tests_not_skipped.py +0 -0
  218. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_layer_2_loop_size.py +0 -0
  219. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_migrations_registry_ssot.py +0 -0
  220. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_module_boundaries.py +0 -0
  221. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_module_sizes.py +0 -0
  222. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_monitor_self_exclusion.py +0 -0
  223. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_ai_signatures.py +0 -0
  224. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_asyncio_select.py +0 -0
  225. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_internal_refs.py +0 -0
  226. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_price_logic.py +0 -0
  227. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  228. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_raw_time.py +0 -0
  229. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_vacuous_invariants.py +0 -0
  230. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_version_refs.py +0 -0
  231. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_phase_select_stateless.py +0 -0
  232. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_plugins_config_stable.py +0 -0
  233. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_procwait_shield.py +0 -0
  234. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  235. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  236. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_round_alloc_growth.py +0 -0
  237. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_round_log_decode_boundary.py +0 -0
  238. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_round_num_single_source.py +0 -0
  239. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_round_result_stable.py +0 -0
  240. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  241. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  242. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_upstream_schema_canary.py +0 -0
  243. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  244. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/linux/__init__.py +0 -0
  245. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/linux/test_plugin_sandbox_kill.py +0 -0
  246. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/literate/__init__.py +0 -0
  247. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/literate/parser.py +0 -0
  248. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/literate/test_parser.py +0 -0
  249. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/literate/test_quickstart.py +0 -0
  250. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/__init__.py +0 -0
  251. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime.py +0 -0
  252. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime_container.py +0 -0
  253. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
  254. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime_progress.py +0 -0
  255. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  256. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_assemble_prompt.py +0 -0
  257. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_events_stream.py +0 -0
  258. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_install.py +0 -0
  259. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_observation.py +0 -0
  260. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_read_round_num.py +0 -0
  261. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_resolve_phase.py +0 -0
  262. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_service.py +0 -0
  263. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_types.py +0 -0
  264. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_auto_stop_gating.py +0 -0
  265. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_builtin_provenance.py +0 -0
  266. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_catalogs.py +0 -0
  267. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cgroup_probe.py +0 -0
  268. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_claude_error_detector.py +0 -0
  269. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_claude_foreign_schema.py +0 -0
  270. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli.py +0 -0
  271. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli_common.py +0 -0
  272. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli_init_install.py +0 -0
  273. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli_main_boundary.py +0 -0
  274. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli_monitor_http.py +0 -0
  275. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  276. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli_upgrade.py +0 -0
  277. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_codewhale_code_precedence.py +0 -0
  278. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_codewhale_plugin.py +0 -0
  279. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_coerce_int_poison.py +0 -0
  280. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_collapse_spawn_decisions.py +0 -0
  281. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_fresh_eyes.py +0 -0
  282. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_max_rounds.py +0 -0
  283. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_nested_strictness.py +0 -0
  284. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_prompt_delivery.py +0 -0
  285. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_schedule.py +0 -0
  286. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_stop_file.py +0 -0
  287. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_strictness.py +0 -0
  288. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  289. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_transient_error_action.py +0 -0
  290. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_context_store.py +0 -0
  291. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_control_char_config.py +0 -0
  292. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_default_dirty_handler.py +0 -0
  293. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_defenses.py +0 -0
  294. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_detect_hung_latch.py +0 -0
  295. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_detector_isolation.py +0 -0
  296. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_detector_protocol.py +0 -0
  297. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_dirty_handlers.py +0 -0
  298. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_dispatch_dirty_owner_branch.py +0 -0
  299. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_docgen.py +0 -0
  300. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_doctor_sandbox.py +0 -0
  301. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_effective_throttle_view.py +0 -0
  302. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_emit_config_migrated.py +0 -0
  303. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_emit_schedule.py +0 -0
  304. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
  305. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_events.py +0 -0
  306. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_events_cmd.py +0 -0
  307. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_events_cmd_rollover.py +0 -0
  308. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_events_cmd_tail.py +0 -0
  309. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_events_jsonl_decode.py +0 -0
  310. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_example_dirty_handler.py +0 -0
  311. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_example_plugin.py +0 -0
  312. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_exec_prefix.py +0 -0
  313. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_exit_cause.py +0 -0
  314. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_exit_classifier.py +0 -0
  315. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  316. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_gate_serve_boot.py +0 -0
  317. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_gemini_plugin.py +0 -0
  318. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_git_timeout.py +0 -0
  319. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_hook_failure_isolation.py +0 -0
  320. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_hook_redact_order.py +0 -0
  321. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_hooks.py +0 -0
  322. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_host_health.py +0 -0
  323. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_http_progress.py +0 -0
  324. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_http_progress_epoch.py +0 -0
  325. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_init_entry_points.py +0 -0
  326. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
  327. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_kimi_plugin.py +0 -0
  328. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_lifecycle.py +0 -0
  329. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_load_fail_closed.py +0 -0
  330. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_loader_split.py +0 -0
  331. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_manifest_spawn_hooks.py +0 -0
  332. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_metrics.py +0 -0
  333. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_metrics_cgroup_usage.py +0 -0
  334. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_metrics_swap.py +0 -0
  335. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_migrate_cmd.py +0 -0
  336. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_migrations.py +0 -0
  337. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_assembly.py +0 -0
  338. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_cmd_severity.py +0 -0
  339. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_dedup_identity.py +0 -0
  340. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_dedup_rearm.py +0 -0
  341. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  342. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  343. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  344. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_detectors.py +0 -0
  345. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_event_tail.py +0 -0
  346. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_loop.py +0 -0
  347. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_on_alert.py +0 -0
  348. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_remote.py +0 -0
  349. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
  350. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_schedule_pause.py +0 -0
  351. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_network_blip_signal_guard.py +0 -0
  352. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_notify.py +0 -0
  353. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_open_round_log.py +0 -0
  354. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_orphan_chain_single_pass.py +0 -0
  355. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_outer_round_ceiling.py +0 -0
  356. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_peek_argparse.py +0 -0
  357. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_peek_project_isolation.py +0 -0
  358. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_peek_schedule.py +0 -0
  359. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_peek_select.py +0 -0
  360. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_phase_agent_launch.py +0 -0
  361. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_phase_profiles.py +0 -0
  362. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_phase_select.py +0 -0
  363. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_pi_plugin.py +0 -0
  364. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugin_checksum.py +0 -0
  365. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugin_constants.py +0 -0
  366. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugin_name_mismatch.py +0 -0
  367. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugin_sandbox_wire.py +0 -0
  368. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugin_scan_parity.py +0 -0
  369. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugins_config_fields.py +0 -0
  370. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_presets.py +0 -0
  371. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_primary_prompt_file_home.py +0 -0
  372. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_procwait.py +0 -0
  373. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_prompt_loader.py +0 -0
  374. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_read_json_hardening.py +0 -0
  375. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_redact.py +0 -0
  376. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_resolve.py +0 -0
  377. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_round_cmd.py +0 -0
  378. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_round_cmd_sigterm.py +0 -0
  379. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_round_log_helpers.py +0 -0
  380. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_round_outcome.py +0 -0
  381. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_round_outcome_agent_axis.py +0 -0
  382. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_round_view.py +0 -0
  383. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_runner_sidecar_ordering.py +0 -0
  384. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_runner_throttle.py +0 -0
  385. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_sandbox_probe.py +0 -0
  386. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_scaffold.py +0 -0
  387. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_schedule.py +0 -0
  388. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_cmd_bounded.py +0 -0
  389. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_config_broken.py +0 -0
  390. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_crash_loop.py +0 -0
  391. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_mem_gate.py +0 -0
  392. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_phase_schedule.py +0 -0
  393. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_policy.py +0 -0
  394. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_round.py +0 -0
  395. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
  396. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_round_log.py +0 -0
  397. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_round_oom_killed.py +0 -0
  398. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_round_terminate.py +0 -0
  399. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_round_wait_exit.py +0 -0
  400. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_schedule_gate.py +0 -0
  401. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_sentinel.py +0 -0
  402. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_startup_hooks.py +0 -0
  403. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_wakeup.py +0 -0
  404. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_service_mode.py +0 -0
  405. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_spawn_seam.py +0 -0
  406. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_startup_battery_exit.py +0 -0
  407. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_startup_check.py +0 -0
  408. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_substrate.py +0 -0
  409. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
  410. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_terminate_round_wait_exit.py +0 -0
  411. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_throttle_back_off_ladder.py +0 -0
  412. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_throttle_epoch_bound.py +0 -0
  413. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_throttle_plugin_classification.py +0 -0
  414. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_upgrade_migrate.py +0 -0
  415. {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_vcs_state.py +0 -0
@@ -158,6 +158,7 @@ overlaps
158
158
  plan
159
159
  sandbox
160
160
  cgroup
161
+ sigterm_grace
161
162
 
162
163
  # agent_runner.config.models.AgentConfig
163
164
  command
@@ -167,6 +168,7 @@ env
167
168
  prompt_delivery
168
169
  exec_prefix
169
170
  terminal_marker
171
+ sigterm_grace_s
170
172
 
171
173
  # agent_runner.config.models.Config
172
174
  agent
@@ -5,6 +5,11 @@ 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.5] - 2026-09-15
9
+
10
+ ### Added
11
+ - `[agent] sigterm_grace_s` (default 10): the SIGTERM→SIGKILL grace a round gives its agent when the agent's preset declares itself cooperative (the built-in `gemini` preset does), so an agent that catches SIGTERM to flush or commit its work gets time to finish. A non-cooperative agent (e.g. `claude`) keeps the previous 5 s. Capped at 12 s, inside the supervisor's 15 s wait for the round; `peek --json` (schema `2.4`) and `doctor` report the resolved grace per agent.
12
+
8
13
  ## [0.3.4] - 2026-09-15
9
14
 
10
15
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.3.4
3
+ Version: 0.3.5
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
@@ -62,6 +62,26 @@ def cooperative_manifest_names() -> list[str]:
62
62
  return [m.name for m in _LOADED_MANIFESTS if m.sigterm_cooperative]
63
63
 
64
64
 
65
+ def is_cooperative_agent(agent_binary: str | None) -> bool:
66
+ """Whether the agent's preset declares itself SIGTERM-cooperative.
67
+ `None in [...]` is safely False, so no None-guard is needed."""
68
+ return agent_binary in cooperative_manifest_names()
69
+
70
+
71
+ def resolve_sigterm_grace_s(agent_binary: str | None, sigterm_grace_s: int) -> int:
72
+ """The SIGTERM->SIGKILL grace this round's agent actually gets:
73
+ `sigterm_grace_s` (config's [agent] sigterm_grace_s, boot-capped at
74
+ _ROUND_TERM_GRACE_S) when `agent_binary` names a manifest declaring
75
+ sigterm_cooperative=True, else agent_runtime.REAP_GRACE_S (5s)
76
+ unchanged. One function, two callers: cli.serve_cmd._apply_reap_grace_env
77
+ (what the agent actually gets) and doctor/peek (what an operator sees)."""
78
+ from agent_runner.agent_runtime import REAP_GRACE_S
79
+
80
+ if is_cooperative_agent(agent_binary):
81
+ return sigterm_grace_s
82
+ return REAP_GRACE_S
83
+
84
+
65
85
  def register_manifest(
66
86
  manifest: PluginManifest,
67
87
  *,
@@ -312,13 +312,24 @@ def timeout_budget(round_budget_s: int) -> tuple[int, int]:
312
312
  round subprocess (``api.outer_round_ceiling_s``): ``round_budget_s``
313
313
  plus reap grace + git-commit ceiling + hook allowance, so it only trips
314
314
  when the round supervisor itself is wedged, never during its own
315
- bounded post-round cleanup.
315
+ bounded post-round cleanup. The reap margin must cover the widest grace
316
+ ANY round can be configured with -- a cooperative agent's
317
+ ``sigterm_grace_s`` is boot-capped at ``_ROUND_TERM_GRACE_S``
318
+ (``config/models.py``'s mirror of this module's single source), not
319
+ the non-cooperative default ``_REAP_GRACE_S``, so the margin is
320
+ ``max(_REAP_GRACE_S, _ROUND_TERM_GRACE_S)`` -- else a long cooperative
321
+ grace could trip the outer wall-clock ceiling mid-grace.
316
322
  - ``timeout_stop_sec`` — systemd's ``TimeoutStopSec``
317
323
  (``service_unit.py``): must clear ``outer_ceiling_s`` by enough for a
318
324
  SIGTERM to reach and drain the round (``_ROUND_TERM_GRACE_S``) plus a
319
325
  stop-request overhead pad, so `systemctl stop` never SIGKILLs a round
320
326
  that is draining normally.
321
327
  """
322
- outer_ceiling_s = round_budget_s + _REAP_GRACE_S + _GIT_COMMIT_TIMEOUT_S + _HOOK_ALLOWANCE_S
328
+ outer_ceiling_s = (
329
+ round_budget_s
330
+ + max(_REAP_GRACE_S, _ROUND_TERM_GRACE_S)
331
+ + _GIT_COMMIT_TIMEOUT_S
332
+ + _HOOK_ALLOWANCE_S
333
+ )
323
334
  timeout_stop_sec = outer_ceiling_s + _ROUND_TERM_GRACE_S + _STOP_GRACE_MARGIN_S
324
335
  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.4'
22
- __version_tuple__ = version_tuple = (0, 3, 4)
21
+ __version__ = version = '0.3.5'
22
+ __version_tuple__ = version_tuple = (0, 3, 5)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -161,7 +161,9 @@ def _wait_exit_shielded(
161
161
  continue
162
162
 
163
163
 
164
- def _kill_pgroup(proc: subprocess.Popen, clock: Clock = SYSTEM_CLOCK) -> None:
164
+ def _kill_pgroup(
165
+ proc: subprocess.Popen, clock: Clock = SYSTEM_CLOCK, reap_grace_s: int = REAP_GRACE_S
166
+ ) -> None:
165
167
  """SIGTERM the pgroup, grace, then SIGKILL — the reap primitive shared by
166
168
  the round-timeout path and ``run``'s BaseException handler (which fires on
167
169
  a SIGTERM landing while the round is unwinding from a first one).
@@ -185,7 +187,7 @@ def _kill_pgroup(proc: subprocess.Popen, clock: Clock = SYSTEM_CLOCK) -> None:
185
187
  except OSError:
186
188
  pass
187
189
  deadline = (
188
- clock.monotonic() + REAP_GRACE_S
190
+ clock.monotonic() + reap_grace_s
189
191
  ) # monotonic: an NTP step must not stretch/skip the reap
190
192
  _wait_exit_shielded(proc, deadline=deadline, clock=clock)
191
193
  # "exited" or "timeout": either way, fall through to the SIGKILL last-resort
@@ -574,6 +576,7 @@ def _terminate_agent(
574
576
  container_runtime: str | None,
575
577
  container_cidfile: Path | None,
576
578
  on_container_orphan_risk: Callable[[str, str | None, bool | None], None] | None,
579
+ reap_grace_s: int = REAP_GRACE_S,
577
580
  ) -> None:
578
581
  """Reap the agent pgroup (``_kill_pgroup``), then -- for a
579
582
  container-launching command only -- make a best-effort container `stop`
@@ -581,7 +584,7 @@ def _terminate_agent(
581
584
  hard-kill entry point ``run()`` uses for all three of its termination
582
585
  paths (R1128 wall-clock, grace-kill, and the BaseException reap), so the
583
586
  container handling isn't duplicated three times."""
584
- _kill_pgroup(proc, clock)
587
+ _kill_pgroup(proc, clock, reap_grace_s=reap_grace_s)
585
588
  if container_runtime is not None:
586
589
  cid, stop_ok = _best_effort_container_stop(container_runtime, container_cidfile)
587
590
  if on_container_orphan_risk is not None:
@@ -606,10 +609,16 @@ def run(
606
609
  grace_kill_ignore_patterns: list[re.Pattern[str]] | None = None,
607
610
  on_container_orphan_risk: Callable[[str, str | None, bool | None], None] | None = None,
608
611
  clock: Clock = SYSTEM_CLOCK,
612
+ reap_grace_s: int = REAP_GRACE_S,
609
613
  ) -> RunResult:
610
614
  """Spawn the agent subprocess and wait for exit or timeout.
611
615
 
612
- Wall-clock timeout (R1128). On timeout: SIGTERM pgroup → REAP_GRACE_S → SIGKILL.
616
+ Wall-clock timeout (R1128). On timeout: SIGTERM pgroup → reap_grace_s → SIGKILL.
617
+
618
+ reap_grace_s: the SIGTERM->SIGKILL grace passed straight through to every
619
+ ``_terminate_agent`` call site below (R1128 wall-clock, grace-kill, and
620
+ the BaseException reap) -- one deadline value, threaded end to end.
621
+ Defaults to REAP_GRACE_S so every existing caller is unchanged.
613
622
 
614
623
  work_dir: the agent child's working directory; callers pass the
615
624
  already-absolute cfg.runtime.work_dir. CLIs with no --cwd flag of their
@@ -776,6 +785,7 @@ def run(
776
785
  container_runtime=container_runtime,
777
786
  container_cidfile=container_cidfile,
778
787
  on_container_orphan_risk=_report_orphan_once,
788
+ reap_grace_s=reap_grace_s,
779
789
  )
780
790
  duration = clock.monotonic() - start
781
791
  exit_code = proc.returncode if proc.returncode is not None else -1
@@ -820,6 +830,7 @@ def run(
820
830
  container_runtime=container_runtime,
821
831
  container_cidfile=container_cidfile,
822
832
  on_container_orphan_risk=_report_orphan_once,
833
+ reap_grace_s=reap_grace_s,
823
834
  )
824
835
  duration = clock.monotonic() - start
825
836
  exit_code = proc.returncode if proc.returncode is not None else -1
@@ -860,6 +871,7 @@ def run(
860
871
  container_runtime=container_runtime,
861
872
  container_cidfile=container_cidfile,
862
873
  on_container_orphan_risk=_report_orphan_once,
874
+ reap_grace_s=reap_grace_s,
863
875
  )
864
876
  raise
865
877
  finally:
@@ -362,10 +362,12 @@ def _maybe_defer_for_spawn_hooks(
362
362
 
363
363
 
364
364
  # Grace after TERMing a wedged round before killpg: the round's own SIGTERM handler
365
- # reaps its agent pgroup (agent_runtime.REAP_GRACE_S) then exits, so allow that plus
366
- # margin. test_spawn_round_wedged asserts it stays >= REAP_GRACE_S so the two never
367
- # drift. Single-sourced in _serve_policy (imported above) -- api.py imports the same
368
- # constant, so this and the out-of-process kill path can no longer drift apart.
365
+ # reaps its agent pgroup -- up to agent_runtime.REAP_GRACE_S for a non-cooperative
366
+ # agent, or up to the configured (boot-capped) config.models._MAX_SIGTERM_GRACE_S
367
+ # for a cooperative one -- then exits, so allow that plus margin. test_spawn_round_wedged
368
+ # asserts it stays > _MAX_SIGTERM_GRACE_S so the two never drift. Single-sourced in
369
+ # _serve_policy (imported above) -- api.py imports the same constant, so this and the
370
+ # out-of-process kill path can no longer drift apart.
369
371
  #
370
372
  # _ROUND_UNREAPED_RC (the D-state-leader sentinel returned below) is likewise
371
373
  # single-sourced in _serve_policy (imported above), not defined here.
@@ -19,7 +19,7 @@ from agent_runner.hooks import plugin_context_enrichers, post_round_hooks, pre_r
19
19
  from agent_runner.monitor import plugin_detectors
20
20
  from agent_runner.vcs_state import plugin_owned_paths
21
21
 
22
- PEEK_SCHEMA_VERSION = "2.3"
22
+ PEEK_SCHEMA_VERSION = "2.4"
23
23
 
24
24
 
25
25
  def cfg_from_args(args) -> Config:
@@ -98,7 +98,10 @@ def emit(value: Any, *, json_mode: bool, cfg: Config | None = None) -> None:
98
98
  if json_mode:
99
99
  if isinstance(value, ProjectState):
100
100
  from agent_runner import _sandbox_probe, disabled_plugin_names
101
- from agent_runner._plugin_manifest import cooperative_manifest_names
101
+ from agent_runner._plugin_manifest import (
102
+ cooperative_manifest_names,
103
+ resolve_sigterm_grace_s,
104
+ )
102
105
 
103
106
  plugins_block = {
104
107
  "event_kinds": plugin_event_kinds(),
@@ -112,6 +115,9 @@ def emit(value: Any, *, json_mode: bool, cfg: Config | None = None) -> None:
112
115
  }
113
116
  if cfg is not None:
114
117
  plugins_block.update(_sandbox_probe.peek_snapshot(cfg))
118
+ plugins_block["resolved_sigterm_grace_s"] = resolve_sigterm_grace_s(
119
+ cfg.agent.binary, cfg.agent.sigterm_grace_s
120
+ )
115
121
  wrapped = {
116
122
  "schema_version": PEEK_SCHEMA_VERSION,
117
123
  "plugins": plugins_block,
@@ -17,6 +17,7 @@ class DoctorReport:
17
17
  plan: list[dict]
18
18
  sandbox: dict
19
19
  cgroup: dict
20
+ sigterm_grace: dict
20
21
 
21
22
 
22
23
  def add_parser(sub, parent) -> None:
@@ -65,12 +66,21 @@ def cmd_doctor(args) -> int:
65
66
  from agent_runner._sandbox_probe import doctor_snapshot
66
67
 
67
68
  cfg = cfg_from_args(args)
69
+ is_cooperative = _plugin_manifest.is_cooperative_agent(cfg.agent.binary)
70
+ sigterm_grace = {
71
+ "agent": cfg.agent.binary,
72
+ "cooperative": is_cooperative,
73
+ "resolved_s": _plugin_manifest.resolve_sigterm_grace_s(
74
+ cfg.agent.binary, cfg.agent.sigterm_grace_s
75
+ ),
76
+ }
68
77
  report = DoctorReport(
69
78
  checks=startup_check.run_battery(cfg),
70
79
  overlaps=phase_select.find_phase_window_overlaps(cfg),
71
80
  plan=_plan(cfg, args.rounds),
72
81
  sandbox=doctor_snapshot(cfg),
73
82
  cgroup=_cgroup_report(),
83
+ sigterm_grace=sigterm_grace,
74
84
  )
75
85
  json_mode = getattr(args, "json", False)
76
86
  if json_mode:
@@ -122,6 +132,11 @@ def _format(report: DoctorReport) -> str:
122
132
  lines.append(f' {name} = "{digest}"')
123
133
  cooperative = _plugin_manifest.cooperative_manifest_names()
124
134
  lines.append(f"cooperative presets: {', '.join(sorted(cooperative)) or '(none)'}")
135
+ sg = report.sigterm_grace
136
+ lines.append(
137
+ f"sigterm grace: {sg['resolved_s']}s "
138
+ f"({'cooperative' if sg['cooperative'] else 'non-cooperative'}, agent={sg['agent']})"
139
+ )
125
140
  cgroup = report.cgroup
126
141
  lines.append("cgroup:")
127
142
  lines.append(f" cgroup_path: {cgroup['cgroup_path']}")
@@ -598,6 +598,51 @@ def _apply_round_num_env(round_env: dict, round_num: int) -> None:
598
598
  round_env["AGENT_RUNNER_ROUND_NUM"] = str(round_num)
599
599
 
600
600
 
601
+ def _resolve_reap_grace_by_phase(cfg) -> dict[str | None, int]:
602
+ """Precompute this serve lifetime's per-phase SIGTERM grace ONCE --
603
+ boot-invariant like cgroup_probe_defer/overlaps below (for a fixed
604
+ phase_arg, resolve_sigterm_grace_s depends only on the per-phase-static
605
+ agent binary + sigterm_grace_s + the once-populated plugin-manifest
606
+ registry), so _apply_reap_grace_env does a single dict lookup per round
607
+ instead of re-resolving from scratch. Keyed by the finite known
608
+ phase_arg set: None (base) plus every configured [phases.<name>]
609
+ override."""
610
+ from agent_runner._plugin_manifest import resolve_sigterm_grace_s
611
+
612
+ phase_args: list[str | None] = [None, *cfg.phases.overrides.keys()]
613
+ return {
614
+ phase_arg: resolve_sigterm_grace_s(
615
+ cfg.profile_for(phase_arg).agent.binary,
616
+ cfg.profile_for(phase_arg).agent.sigterm_grace_s,
617
+ )
618
+ for phase_arg in phase_args
619
+ }
620
+
621
+
622
+ def _apply_reap_grace_env(
623
+ cfg, round_env: dict, phase_arg: str | None, grace_by_phase: dict[str | None, int]
624
+ ) -> None:
625
+ """Publish this round's agent's SIGTERM grace via
626
+ AGENT_RUNNER_REAP_GRACE_S -- the same single-source-from-serve pattern
627
+ AGENT_RUNNER_ROUND_NUM uses, so the round child's grace can't skew from
628
+ the phase serve actually selected this round (see
629
+ _plugin_manifest.resolve_sigterm_grace_s for the decision itself). The
630
+ grace is looked up in `grace_by_phase` (precomputed once per serve
631
+ lifetime by _resolve_reap_grace_by_phase) rather than re-resolved here --
632
+ but the env write itself stays UNCONDITIONAL every round, so a
633
+ cooperative<->non-cooperative phase transition still overwrites the
634
+ previous round's value."""
635
+ from agent_runner._plugin_manifest import resolve_sigterm_grace_s
636
+
637
+ grace = grace_by_phase.get(phase_arg)
638
+ if grace is None:
639
+ # Defensive only -- every phase_arg serve can select is one of the
640
+ # keys _resolve_reap_grace_by_phase precomputed from this same cfg.
641
+ agent = cfg.profile_for(phase_arg).agent
642
+ grace = resolve_sigterm_grace_s(agent.binary, agent.sigterm_grace_s)
643
+ round_env["AGENT_RUNNER_REAP_GRACE_S"] = str(grace)
644
+
645
+
601
646
  def _capture_substrate(work_dir, cfg, log_dir, round_num, *, when):
602
647
  """Snapshot git-head + paths-hash and emit the round-substrate event for `when`."""
603
648
  git_head = compute_git_head(work_dir)
@@ -689,6 +734,9 @@ def cmd(args) -> int:
689
734
  # Static config check -- phase-window collisions can't change mid-lifetime, so
690
735
  # (like the cgroup probe above) this runs once at boot, not once per round.
691
736
  overlaps = phase_select.find_phase_window_overlaps(cfg)
737
+ # Boot-invariant per-phase SIGTERM grace -- resolved once here, looked up
738
+ # (not re-resolved) every round by _apply_reap_grace_env below.
739
+ grace_by_phase = _resolve_reap_grace_by_phase(cfg)
692
740
  if overlaps:
693
741
  detail = phase_select.describe_overlaps(overlaps)
694
742
  _release_serve_lock(serve_lock_fd)
@@ -756,6 +804,7 @@ def cmd(args) -> int:
756
804
  _capture_substrate(work_dir, cfg, log_dir, round_num, when="before")
757
805
  _apply_fresh_eyes(cfg, log_dir, round_num, round_env)
758
806
  _apply_round_num_env(round_env, round_num)
807
+ _apply_reap_grace_env(cfg, round_env, phase_arg, grace_by_phase)
759
808
  round_log_path = log_dir / f"round-{round_num}.log"
760
809
  round_started = SYSTEM_CLOCK.monotonic()
761
810
  round_argv = [
@@ -28,6 +28,24 @@ _VALID_PROMPT_DELIVERY: frozenset[str] = frozenset({"argv", "stdin"})
28
28
  # this literal but is decoupled to avoid an agent_runtime -> config import.
29
29
  DEFAULT_TERMINAL_MARKER = '"type":"result"'
30
30
 
31
+ # Default [agent] sigterm_grace_s: the SIGTERM->SIGKILL grace a cooperative
32
+ # agent's preset gets from the round leader (see AgentConfig.sigterm_grace_s).
33
+ DEFAULT_SIGTERM_GRACE_S = 10
34
+
35
+ # Ceiling for [agent] sigterm_grace_s: sits a STRICT ~3s BELOW _serve_policy's
36
+ # _ROUND_TERM_GRACE_S (15, the supervisor's own wait for the round leader) --
37
+ # NOT equal to it. The margin matters on the out-of-process `agent-runner kill`
38
+ # path (_lifecycle._terminate_round_pid), which SIGKILLs only the leader pid
39
+ # with no stray-reap: without it, a leader whose own killpg(SIGKILL) of its
40
+ # agent fires at the same instant the supervisor SIGKILLs the leader could lose
41
+ # the race, leaving the agent permanently orphaned. config/ cannot import
42
+ # _serve_policy (a cycle: _serve_policy already imports agent_runner.config for
43
+ # ConfigError), so this is a LITERAL mirror-with-margin, pinned by
44
+ # tests/invariants/test_timeout_budget_invariant.py against drift, the same
45
+ # pattern _serve_policy uses for its own agent_runtime.REAP_GRACE_S /
46
+ # vcs_state.GIT_COMMIT_TIMEOUT_S mirrors.
47
+ _MAX_SIGTERM_GRACE_S = 12
48
+
31
49
 
32
50
  @dataclass(frozen=True)
33
51
  class AgentConfig:
@@ -42,6 +60,14 @@ class AgentConfig:
42
60
  the agent's terminal record (see ``agent_runtime.run``'s marker scan).
43
61
  Defaults to claude's own JSONL token so existing configs are unaffected;
44
62
  an empty string opts out of the marker scan entirely (wall-clock-ceiling-only)."""
63
+ sigterm_grace_s: int = DEFAULT_SIGTERM_GRACE_S
64
+ """The SIGTERM->SIGKILL grace this round's leader gives its agent, applied
65
+ only when the agent's preset declares itself cooperative (sigterm_cooperative
66
+ on its PluginManifest) -- a non-cooperative agent always gets the fixed
67
+ agent_runtime.REAP_GRACE_S instead. Capped at _MAX_SIGTERM_GRACE_S, which sits
68
+ strictly inside (not equal to) _serve_policy._ROUND_TERM_GRACE_S, the
69
+ supervisor's own wait for the round leader -- a value above the cap is
70
+ rejected at boot, not clamped."""
45
71
 
46
72
  @property
47
73
  def binary(self) -> str | None:
@@ -22,6 +22,7 @@ from agent_runner.config.models import (
22
22
  _HOST_HEALTH_DISK_ALLOWED_FIELDS,
23
23
  _HOST_HEALTH_MEMORY_ALLOWED_FIELDS,
24
24
  _HOST_HEALTH_PRESSURE_ALLOWED_FIELDS,
25
+ _MAX_SIGTERM_GRACE_S,
25
26
  _MONITOR_ALLOWED_FIELDS,
26
27
  _MONITOR_HOST_HEALTH_ALLOWED_FIELDS,
27
28
  _PHASE_OVERRIDE_ALLOWED_FIELDS,
@@ -36,6 +37,7 @@ from agent_runner.config.models import (
36
37
  _VALID_SANDBOX_MODES,
37
38
  _VALID_TRANSIENT_ERROR_ACTIONS,
38
39
  _VCS_ALLOWED_FIELDS,
40
+ DEFAULT_SIGTERM_GRACE_S,
39
41
  DEFAULT_TERMINAL_MARKER,
40
42
  AgentConfig,
41
43
  MonitorConfig,
@@ -67,6 +69,23 @@ from agent_runner.config.validators import (
67
69
  )
68
70
 
69
71
 
72
+ def _validate_sigterm_grace_s(value: Any, *, field: str) -> int:
73
+ """Validate [agent] sigterm_grace_s (or a per-phase override): positive
74
+ int, capped at _MAX_SIGTERM_GRACE_S -- a value strictly inside (not equal
75
+ to) the supervisor's own wait for the round leader, preserving leader-exit
76
+ margin. Mirrors _validate_remote_failure_tolerance's require-then-cap
77
+ shape; `field` is threaded through (unlike that single-call-site
78
+ validator) since this one validates both the base [agent] table and each
79
+ [phases.<name>.agent] override, under different prefixes."""
80
+ v = _require_positive_int(value, field=field)
81
+ if v > _MAX_SIGTERM_GRACE_S:
82
+ raise ConfigError(
83
+ f"{field}: must be <= {_MAX_SIGTERM_GRACE_S} "
84
+ f"(strictly inside the supervisor's own wait for the round leader), got {v}"
85
+ )
86
+ return v
87
+
88
+
70
89
  def _parse_agent(
71
90
  agent_d: dict, *, field_prefix: str, require_prompt_placeholder: bool = True
72
91
  ) -> AgentConfig:
@@ -119,6 +138,10 @@ def _parse_agent(
119
138
  agent_d.get("exec_prefix", []), field=f"{field_prefix} exec_prefix"
120
139
  )
121
140
  terminal_marker = str(agent_d.get("terminal_marker", DEFAULT_TERMINAL_MARKER))
141
+ sigterm_grace_s = _validate_sigterm_grace_s(
142
+ agent_d.get("sigterm_grace_s", DEFAULT_SIGTERM_GRACE_S),
143
+ field=f"{field_prefix} sigterm_grace_s",
144
+ )
122
145
  return AgentConfig(
123
146
  command=command,
124
147
  prompt_arg_template=prompt_arg_template,
@@ -127,6 +150,7 @@ def _parse_agent(
127
150
  prompt_delivery=prompt_delivery, # type: ignore[arg-type] # narrowed above
128
151
  exec_prefix=exec_prefix,
129
152
  terminal_marker=terminal_marker,
153
+ sigterm_grace_s=sigterm_grace_s,
130
154
  )
131
155
 
132
156
 
@@ -190,6 +190,29 @@ def _resolve_round_num(log_dir: Path) -> int:
190
190
  return next_round_num(log_dir)
191
191
 
192
192
 
193
+ def _resolve_reap_grace_s() -> int:
194
+ """The SIGTERM->SIGKILL grace this round's agent gets, published by serve
195
+ via AGENT_RUNNER_REAP_GRACE_S (cli/serve_cmd.py's _apply_reap_grace_env)
196
+ so the child never re-derives (and skews) serve's own cooperative-manifest
197
+ resolution; a standalone `agent-runner round` (no env) falls back to
198
+ agent_runtime.REAP_GRACE_S -- the same non-cooperative default -- when the
199
+ env var is absent, unparseable, or non-positive (a leaked <=0 value would
200
+ make a past deadline and kill with zero grace; serve only ever publishes a
201
+ boot-validated positive value or REAP_GRACE_S, so this only guards a
202
+ hand-set/leaked standalone env)."""
203
+ from agent_runner.agent_runtime import REAP_GRACE_S
204
+
205
+ raw = os.environ.get("AGENT_RUNNER_REAP_GRACE_S")
206
+ if raw is not None:
207
+ try:
208
+ parsed = int(raw)
209
+ except ValueError:
210
+ parsed = None
211
+ if parsed is not None and parsed > 0:
212
+ return parsed
213
+ return REAP_GRACE_S
214
+
215
+
193
216
  def _previous_block(prev: context_store.Status | None, dirty_last: bool) -> dict[str, Any] | None:
194
217
  if prev is None:
195
218
  return None
@@ -573,6 +596,7 @@ def _run_one_round_inner(cfg: Config, *, phase_override: str | None = None) -> R
573
596
  on_grace_extended=_grace_extended_emit,
574
597
  grace_kill_ignore_patterns=grace_kill_ignore_patterns,
575
598
  on_container_orphan_risk=_container_orphan_risk_emit,
599
+ reap_grace_s=_resolve_reap_grace_s(),
576
600
  )
577
601
  events.emit(
578
602
  log_dir,
@@ -294,7 +294,7 @@ The transforms `migrate` applies (generated from the registry):
294
294
  - unknown [schedule] key(s) rejected in 0.2.12; delete them (allowed: ['pause_windows', 'run_windows', 'timezone'])
295
295
  - monitor.anomaly_repetitive_threshold > anomaly_repetitive_window: lower the threshold or raise the window so the detector can fire
296
296
  - [<table>] given as a scalar, not a table; give it real [table] content (no auto-fix possible)
297
- - unknown [agent] key(s) rejected in 0.2.13; delete them (allowed: ['command', 'env', 'exec_prefix', 'name', 'prompt_arg_template', 'prompt_delivery', 'terminal_marker'])
297
+ - unknown [agent] key(s) rejected in 0.2.13; delete them (allowed: ['command', 'env', 'exec_prefix', 'name', 'prompt_arg_template', 'prompt_delivery', 'sigterm_grace_s', 'terminal_marker'])
298
298
  - unknown [runtime] key(s) rejected in 0.2.13; delete them (allowed: ['disable_pre_round_hooks', 'dry_run', 'fresh_eyes_every_n', 'grace_kill_ignore_patterns', 'log_dir', 'max_grace_after_result_s', 'max_rounds', 'narrative_file', 'restart_delay_s', 'round_budget_s', 'round_log_retention', 'stop_file', 'substrate_fingerprint_paths', 'transient_error_action', 'work_dir'])
299
299
  - unknown [vcs] key(s) rejected in 0.2.13; delete them (allowed: ['dirty_action', 'stash_idempotency_s'])
300
300
  - unknown [monitor] key(s) rejected in 0.2.13; delete them (allowed: ['anomaly_repetitive_threshold', 'anomaly_repetitive_window', 'auth_fail_hint', 'auth_fail_patterns', 'auto_stop_on', 'host_health', 'remote_failure_tolerance_s', 'round_progress_interval_s', 'supervisor_stale_threshold_s'])
@@ -32,6 +32,7 @@ running with newly-set `dirty_action = "auto_commit"` is undefined).
32
32
  | `prompt_delivery` | `Literal['argv', 'stdin']` | 'argv' |
33
33
  | `exec_prefix` | `list[str]` | [] |
34
34
  | `terminal_marker` | `str` | '"type":"result"' |
35
+ | `sigterm_grace_s` | `int` | 10 |
35
36
 
36
37
  ### `[runtime]`
37
38
 
@@ -184,6 +185,20 @@ grace-kill never engages and a round runs to the wall-clock
184
185
  `round_budget_s` ceiling instead. An empty string disables the marker scan
185
186
  outright — an honest opt-out, not an empty-string-matches-everything trap.
186
187
 
188
+ ### `agent.sigterm_grace_s` (0.3.5+)
189
+
190
+ Type: int, default `10`, max `12`
191
+
192
+ The SIGTERM→SIGKILL grace the round leader gives ITS AGENT — but only when
193
+ the configured agent's preset declares `sigterm_cooperative = true` (the
194
+ built-in `gemini` preset does; see `peek --json`'s `plugins.sigterm_cooperative`
195
+ or `doctor`'s "cooperative presets" line). A non-cooperative agent (e.g.
196
+ `claude`) always gets the fixed 5s grace regardless of this setting. Capped
197
+ at 12 — 3 s inside the supervisor's own 15 s wait for the round
198
+ (`_ROUND_TERM_GRACE_S`) — a config above that is rejected at boot, not
199
+ silently clamped: a grace longer than the supervisor waits for the leader
200
+ would let the supervisor SIGKILL the leader mid-wrap-up, defeating the point.
201
+
187
202
  ### `runtime.round_log_retention`
188
203
 
189
204
  **Pruning is opt-in. `0` — the default — never prunes anything.** Round logs
@@ -473,6 +473,29 @@ The decision RULES are byte-identical: none of this changes the inputs→verdict
473
473
 
474
474
  Numbers are dev-host-relative (the macOS `kqueue` harness). The Linux `pidfd` fast path is exercised only on CI (ubuntu runners); the real 462 MB / 256 MB constrained-host wake-latency and `pidfd`/`kqueue`-under-swap-pressure confirmation stays deferred to 0.3.5 while that host is offline — measured on dev/CI, documented as deferred for the constrained host, never reported as zero-cost.
475
475
 
476
+ ## 0.3.4 → 0.3.5 (2026-09-15)
477
+
478
+ "Cooperative wrap-up grace": a configurable, manifest-gated SIGTERM→SIGKILL grace threaded to the round leader's actual reap deadline (a cooperative agent like `gemini` gets `[agent] sigterm_grace_s`, default 10 s; `claude` keeps 5 s). Same macOS harness. <!-- authored: default sigterm_grace_s / REAP_GRACE_S; SSOT agent_runner/config/models.py, agent_runner/agent_runtime.py -->
479
+
480
+ ### 1. Import/startup RSS
481
+
482
+ | | 0.3.4 | 0.3.5 | Δ |
483
+ |---|---|---|---|
484
+ | RSS (avg of 5 cold runs) | 24.0 MB | 24.1 MB | ~0 MB (flat, within noise) |
485
+ | RSS range | 23.95–24.12 MB | 23.91–24.25 MB | |
486
+
487
+ Flat: the change is a config field, a pure grace-resolution function, an env round-trip, and two observability fields — no new module on the cold-startup graph, no new dependency (base stays `psutil>=5.9`). `test_import_footprint.py` + `test_round_alloc_growth.py` green.
488
+
489
+ ### 2. Efficiency
490
+
491
+ No new internal wait — the grace window rides the v0.3.4 `wait_exit` deadline (zero polling during the grace), so a 12 s cooperative grace costs zero wakeups, same as the old 5 s. Two structural wins rather than a headline number:
492
+ - **One grace code path, not two.** The reap deadline is now a single threaded value (defaulting to `REAP_GRACE_S`) instead of a hardcoded constant plus a would-be cooperative branch — the branch was never created, closing the v0.3.3 dark-code shape at the source.
493
+ - **No wasted force-kills.** A cooperative agent that finishes flushing within its grace is reaped normally instead of SIGKILLed mid-write — work that the old fixed 5 s could truncate now completes. The behavioral efficiency win, measured by the wall-clock PROPERTY test (`test_cooperative_grace_property.py`), not RSS.
494
+
495
+ ### 3. Constrained-host (honesty)
496
+
497
+ Dev-host-relative (macOS). The grace's real value shows on the constrained fleet (a `gemini` round flushing under memory pressure before an early-SIGTERM nudge) — that nudge is the 462 MB-constrained-host-gated v0.3.7 work; this release ships only the correct, configurable grace it will use. No new constrained-host claim.
498
+
476
499
  ## Enforcement
477
500
 
478
501
  Four invariant tests keep these numbers from drifting silently:
@@ -188,7 +188,7 @@ The round itself continues — a broken plugin must not crash the supervisor.
188
188
 
189
189
  ```json
190
190
  {
191
- "schema_version": "2.3",
191
+ "schema_version": "2.4",
192
192
  "plugins": {
193
193
  "event_kinds": [...],
194
194
  "context_enrichers": ["current_branch"],
@@ -550,7 +550,7 @@ detector. Other plugin detectors and all builtins still run normally.
550
550
 
551
551
  ```json
552
552
  {
553
- "schema_version": "2.3",
553
+ "schema_version": "2.4",
554
554
  "plugins": {
555
555
  "event_kinds": [...],
556
556
  "context_enrichers": [...],
@@ -221,7 +221,15 @@ def test_kill_should_reap_round_and_agent_pgroup_when_using_holder_sidecar(
221
221
 
222
222
  assert result.active is False
223
223
  round_proc.wait(timeout=20) # own child: an OS condition, not an event
224
- assert not pid_alive(agent_pid[0]), "the round's agent was orphaned by kill()"
224
+ # Poll, don't assert-immediately: the agent (a grandchild reparented to
225
+ # init once the leader is reaped) is SIGKILLed by the holder-sidecar reap,
226
+ # but init reaps its zombie asynchronously -- a sub-30ms window where
227
+ # pid_alive() still reads the reaping pid as alive (surfaced as a flake
228
+ # only under CI load). A genuinely orphaned agent rides out its lifetime,
229
+ # so this poll fails after the timeout for the real bug.
230
+ assert poll_until(lambda: not pid_alive(agent_pid[0]), timeout_s=10), (
231
+ "the round's agent was orphaned by kill()"
232
+ )
225
233
  assert serve_stub.poll() is not None # the serve stand-in was reaped too
226
234
  finally:
227
235
  for p in (round_proc, serve_stub):