cli-agent-runner 0.3.6__tar.gz → 0.3.7__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 (419) hide show
  1. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.vulture-whitelist.py +7 -0
  2. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/CHANGELOG.md +7 -0
  3. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/PKG-INFO +1 -1
  4. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_emit/__init__.py +6 -0
  5. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_emit/memory.py +64 -1
  6. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_version.py +2 -2
  7. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/api.py +3 -0
  8. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/_serve_cgroup.py +103 -11
  9. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/_serve_round.py +110 -9
  10. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/common.py +5 -1
  11. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/doctor_cmd.py +21 -4
  12. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/serve_cmd.py +2 -1
  13. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/config/__init__.py +1 -0
  14. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/config/models.py +28 -2
  15. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/config/parsers.py +41 -0
  16. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/events.py +3 -0
  17. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/metrics.py +125 -6
  18. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/architecture.md +3 -0
  19. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/commands.md +1 -1
  20. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/configuration.md +4 -2
  21. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/memory-efficiency.md +33 -0
  22. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/plugins.md +2 -2
  23. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/contract/test_public_api_surface.py +3 -0
  24. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_spawn_round_mem_floor.py +521 -0
  25. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_architecture.py +16 -2
  26. cli_agent_runner-0.3.7/tests/invariants/test_brake_defaults_off.py +46 -0
  27. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_event_kinds_ssot.py +5 -0
  28. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_layer_2_loop_size.py +1 -1
  29. cli_agent_runner-0.3.7/tests/invariants/test_no_direct_inspect_import.py +47 -0
  30. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_peek_schema_version.py +3 -1
  31. cli_agent_runner-0.3.7/tests/linux/test_memory_high_nudge_e2e.py +180 -0
  32. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cgroup_probe.py +279 -3
  33. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config.py +20 -0
  34. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_nested_strictness.py +61 -0
  35. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_doctor_cmd.py +84 -0
  36. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_exec_prefix.py +3 -3
  37. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_round_outcome.py +45 -0
  38. cli_agent_runner-0.3.7/tests/unit/test_serve_round.py +81 -0
  39. cli_agent_runner-0.3.6/tests/unit/test_serve_round.py +0 -34
  40. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.codecov.yml +0 -0
  41. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  42. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  43. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  44. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  45. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/dependabot.yml +0 -0
  46. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/workflows/ci.yml +0 -0
  47. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/workflows/release.yml +0 -0
  48. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.gitignore +0 -0
  49. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/BACKLOG.md +0 -0
  50. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/CODE_OF_CONDUCT.md +0 -0
  51. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/CONTRIBUTING.md +0 -0
  52. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/LICENSE +0 -0
  53. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/README.md +0 -0
  54. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/README.zh.md +0 -0
  55. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/SECURITY.md +0 -0
  56. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/__init__.py +0 -0
  57. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_docgen.py +0 -0
  58. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_emit/agent.py +0 -0
  59. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_emit/plugins.py +0 -0
  60. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_emit/rounds.py +0 -0
  61. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_emit/serve.py +0 -0
  62. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_install.py +0 -0
  63. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_lifecycle.py +0 -0
  64. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_monitor_detectors.py +0 -0
  65. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_monitor_registry.py +0 -0
  66. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_monitor_state.py +0 -0
  67. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_notify.py +0 -0
  68. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_observe.py +0 -0
  69. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_plugin_checksum.py +0 -0
  70. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_plugin_manifest.py +0 -0
  71. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_plugin_sandbox.py +0 -0
  72. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_plugin_scan.py +0 -0
  73. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_procwait.py +0 -0
  74. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_redact.py +0 -0
  75. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_registry.py +0 -0
  76. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_resolve.py +0 -0
  77. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_round_outcome.py +0 -0
  78. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_round_support.py +0 -0
  79. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_sandbox_probe.py +0 -0
  80. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_serve_policy.py +0 -0
  81. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_substrate.py +0 -0
  82. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_throttle.py +0 -0
  83. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/agent_runtime.py +0 -0
  84. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/api_types.py +0 -0
  85. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/__init__.py +0 -0
  86. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/_constants.py +0 -0
  87. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  88. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/codewhale.py +0 -0
  89. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
  90. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/gemini.py +0 -0
  91. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/kimi.py +0 -0
  92. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/pi.py +0 -0
  93. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/__init__.py +0 -0
  94. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/__main__.py +0 -0
  95. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/events_cmd.py +0 -0
  96. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/init_cmd.py +0 -0
  97. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/install_cmd.py +0 -0
  98. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/migrate_cmd.py +0 -0
  99. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/monitor_cmd.py +0 -0
  100. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/peek_cmd.py +0 -0
  101. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/round_cmd.py +0 -0
  102. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/service_cmd.py +0 -0
  103. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/upgrade_cmd.py +0 -0
  104. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/clock.py +0 -0
  105. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/config/errors.py +0 -0
  106. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/config/loader.py +0 -0
  107. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/config/validators.py +0 -0
  108. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/context_store.py +0 -0
  109. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/defenses.py +0 -0
  110. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/hooks.py +0 -0
  111. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/host_health.py +0 -0
  112. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/http_progress.py +0 -0
  113. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/lifecycle.py +0 -0
  114. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/migrations.py +0 -0
  115. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/monitor.py +0 -0
  116. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/phase_select.py +0 -0
  117. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/__init__.py +0 -0
  118. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/aider.toml +0 -0
  119. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/claude.toml +0 -0
  120. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/codewhale.toml +0 -0
  121. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/gemini.toml +0 -0
  122. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/kimi.toml +0 -0
  123. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/pi.toml +0 -0
  124. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/prompt_loader.py +0 -0
  125. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/remote_relay.py +0 -0
  126. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/round_log.py +0 -0
  127. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/round_view.py +0 -0
  128. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/runner.py +0 -0
  129. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/scaffold.py +0 -0
  130. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/schedule.py +0 -0
  131. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/service_unit.py +0 -0
  132. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/startup_check.py +0 -0
  133. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/vcs_state.py +0 -0
  134. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/build.sh +0 -0
  135. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/deploy/Dockerfile +0 -0
  136. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/deploy/docker-compose.yml +0 -0
  137. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/README.md +0 -0
  138. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/events.md +0 -0
  139. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/long-running-agents.md +0 -0
  140. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/migrations/0.2.md +0 -0
  141. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/migrations/0.3.md +0 -0
  142. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/quickstart.md +0 -0
  143. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/recipes/aider.md +0 -0
  144. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/recipes/codewhale.md +0 -0
  145. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/recipes/container-pi.md +0 -0
  146. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/recipes/deploy-all-in-one.md +0 -0
  147. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/recipes/kimi.md +0 -0
  148. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/recipes/pi.md +0 -0
  149. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/runbook.md +0 -0
  150. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/thesis.md +0 -0
  151. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/pyproject.toml +0 -0
  152. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/__init__.py +0 -0
  153. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/_clock.py +0 -0
  154. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/_test_helpers.py +0 -0
  155. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/conftest.py +0 -0
  156. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/contract/__init__.py +0 -0
  157. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/e2e/__init__.py +0 -0
  158. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/e2e/conftest.py +0 -0
  159. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  160. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/e2e/test_e2e_install_systemd.py +0 -0
  161. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  162. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  163. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  164. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  165. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  166. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/generate_vulture_whitelist.py +0 -0
  167. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/__init__.py +0 -0
  168. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_auto_stop_e2e.py +0 -0
  169. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_bounded_run.py +0 -0
  170. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_context_enricher_namespacing.py +0 -0
  171. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_cooperative_grace_property.py +0 -0
  172. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_dirty_handler_seam.py +0 -0
  173. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_fresh_eyes_signal.py +0 -0
  174. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_grace_kill_emission.py +0 -0
  175. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_install_dry_run.py +0 -0
  176. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_monitor_seeded.py +0 -0
  177. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_plugin_detector_loaded.py +0 -0
  178. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_plugin_owned_paths.py +0 -0
  179. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_plugin_real_flow.py +0 -0
  180. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  181. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_reap_property.py +0 -0
  182. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  183. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_scaffold_presets.py +0 -0
  184. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_serve_loop.py +0 -0
  185. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_spawn_round_wedged.py +0 -0
  186. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_substrate_fingerprint.py +0 -0
  187. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_transient_error_backoff.py +0 -0
  188. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/__init__.py +0 -0
  189. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/_docs.py +0 -0
  190. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/_event_scan.py +0 -0
  191. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_atomic_write_enforced.py +0 -0
  192. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_authored_facts_declared.py +0 -0
  193. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  194. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_bdd_test_conventions.py +0 -0
  195. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_builtin_plugin_names_sync.py +0 -0
  196. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_catalogs.py +0 -0
  197. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_classification_ssot.py +0 -0
  198. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_config_error_consistency.py +0 -0
  199. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_config_value_set_ssot.py +0 -0
  200. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_container_stop_bound.py +0 -0
  201. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
  202. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  203. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
  204. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_cli_claims.py +0 -0
  205. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_operator_surface.py +0 -0
  206. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
  207. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  208. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_usage_event_fields.py +0 -0
  209. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_docs_generated.py +0 -0
  210. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_docs_index_complete.py +0 -0
  211. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_entry_points_resolve.py +0 -0
  212. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_event_int_coercion.py +0 -0
  213. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_event_kind_registry.py +0 -0
  214. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_events_doc_contract.py +0 -0
  215. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
  216. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_hook_contract_docs.py +0 -0
  217. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_import_footprint.py +0 -0
  218. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_kill_tests_not_skipped.py +0 -0
  219. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_migrations_registry_ssot.py +0 -0
  220. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_module_boundaries.py +0 -0
  221. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_module_sizes.py +0 -0
  222. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_monitor_self_exclusion.py +0 -0
  223. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_ai_signatures.py +0 -0
  224. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_asyncio_select.py +0 -0
  225. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_internal_refs.py +0 -0
  226. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_price_logic.py +0 -0
  227. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  228. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_raw_time.py +0 -0
  229. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_vacuous_invariants.py +0 -0
  230. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_version_refs.py +0 -0
  231. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_phase_select_stateless.py +0 -0
  232. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_plugins_config_stable.py +0 -0
  233. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_procwait_shield.py +0 -0
  234. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  235. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  236. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_round_alloc_growth.py +0 -0
  237. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_round_log_decode_boundary.py +0 -0
  238. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_round_num_single_source.py +0 -0
  239. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_round_result_stable.py +0 -0
  240. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  241. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  242. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_timeout_budget_invariant.py +0 -0
  243. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_upstream_schema_canary.py +0 -0
  244. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  245. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/linux/__init__.py +0 -0
  246. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/linux/test_plugin_sandbox_kill.py +0 -0
  247. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/literate/__init__.py +0 -0
  248. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/literate/parser.py +0 -0
  249. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/literate/test_parser.py +0 -0
  250. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/literate/test_quickstart.py +0 -0
  251. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/__init__.py +0 -0
  252. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime.py +0 -0
  253. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_container.py +0 -0
  254. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_grace.py +0 -0
  255. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
  256. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_progress.py +0 -0
  257. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_reap.py +0 -0
  258. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  259. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_assemble_prompt.py +0 -0
  260. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_events_stream.py +0 -0
  261. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_install.py +0 -0
  262. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_observation.py +0 -0
  263. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_read_round_num.py +0 -0
  264. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_resolve_phase.py +0 -0
  265. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_service.py +0 -0
  266. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_types.py +0 -0
  267. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_apply_reap_grace_env.py +0 -0
  268. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_auto_stop_gating.py +0 -0
  269. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_builtin_provenance.py +0 -0
  270. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_catalogs.py +0 -0
  271. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_claude_error_detector.py +0 -0
  272. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_claude_foreign_schema.py +0 -0
  273. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli.py +0 -0
  274. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli_common.py +0 -0
  275. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli_init_install.py +0 -0
  276. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli_main_boundary.py +0 -0
  277. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli_monitor_http.py +0 -0
  278. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  279. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli_upgrade.py +0 -0
  280. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_codewhale_code_precedence.py +0 -0
  281. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_codewhale_plugin.py +0 -0
  282. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_coerce_int_poison.py +0 -0
  283. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_collapse_spawn_decisions.py +0 -0
  284. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_fresh_eyes.py +0 -0
  285. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_max_rounds.py +0 -0
  286. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_prompt_delivery.py +0 -0
  287. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_schedule.py +0 -0
  288. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_stop_file.py +0 -0
  289. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_strictness.py +0 -0
  290. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  291. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_transient_error_action.py +0 -0
  292. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_context_store.py +0 -0
  293. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_control_char_config.py +0 -0
  294. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_default_dirty_handler.py +0 -0
  295. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_defenses.py +0 -0
  296. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_detect_hung_latch.py +0 -0
  297. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_detector_isolation.py +0 -0
  298. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_detector_protocol.py +0 -0
  299. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_dirty_handlers.py +0 -0
  300. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_dispatch_dirty_owner_branch.py +0 -0
  301. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_docgen.py +0 -0
  302. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_doctor_sandbox.py +0 -0
  303. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_effective_throttle_view.py +0 -0
  304. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_emit_config_migrated.py +0 -0
  305. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_emit_schedule.py +0 -0
  306. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
  307. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_events.py +0 -0
  308. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_events_cmd.py +0 -0
  309. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_events_cmd_rollover.py +0 -0
  310. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_events_cmd_tail.py +0 -0
  311. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_events_jsonl_decode.py +0 -0
  312. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_example_dirty_handler.py +0 -0
  313. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_example_plugin.py +0 -0
  314. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_exit_cause.py +0 -0
  315. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_exit_classifier.py +0 -0
  316. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  317. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_gate_serve_boot.py +0 -0
  318. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_gemini_plugin.py +0 -0
  319. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_git_timeout.py +0 -0
  320. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_hook_failure_isolation.py +0 -0
  321. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_hook_redact_order.py +0 -0
  322. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_hooks.py +0 -0
  323. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_host_health.py +0 -0
  324. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_http_progress.py +0 -0
  325. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_http_progress_epoch.py +0 -0
  326. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_init_entry_points.py +0 -0
  327. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
  328. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_kill_pgroup_shielding.py +0 -0
  329. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_kimi_plugin.py +0 -0
  330. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_lifecycle.py +0 -0
  331. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_load_fail_closed.py +0 -0
  332. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_loader_split.py +0 -0
  333. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_manifest_spawn_hooks.py +0 -0
  334. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_metrics.py +0 -0
  335. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_metrics_cgroup_usage.py +0 -0
  336. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_metrics_swap.py +0 -0
  337. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_migrate_cmd.py +0 -0
  338. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_migrations.py +0 -0
  339. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_assembly.py +0 -0
  340. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_cmd_severity.py +0 -0
  341. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_dedup_identity.py +0 -0
  342. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_dedup_rearm.py +0 -0
  343. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  344. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  345. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  346. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_detectors.py +0 -0
  347. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_event_tail.py +0 -0
  348. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_loop.py +0 -0
  349. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_on_alert.py +0 -0
  350. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_remote.py +0 -0
  351. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
  352. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_schedule_pause.py +0 -0
  353. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_network_blip_signal_guard.py +0 -0
  354. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_notify.py +0 -0
  355. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_open_round_log.py +0 -0
  356. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_orphan_chain_single_pass.py +0 -0
  357. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_outer_round_ceiling.py +0 -0
  358. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_peek_argparse.py +0 -0
  359. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_peek_project_isolation.py +0 -0
  360. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_peek_sandbox_block.py +0 -0
  361. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_peek_schedule.py +0 -0
  362. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_peek_select.py +0 -0
  363. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_phase_agent_launch.py +0 -0
  364. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_phase_profiles.py +0 -0
  365. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_phase_select.py +0 -0
  366. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_pi_plugin.py +0 -0
  367. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_checksum.py +0 -0
  368. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_constants.py +0 -0
  369. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_manifest.py +0 -0
  370. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_name_mismatch.py +0 -0
  371. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_sandbox_wire.py +0 -0
  372. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_scan_parity.py +0 -0
  373. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugins_config_fields.py +0 -0
  374. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_presets.py +0 -0
  375. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_primary_prompt_file_home.py +0 -0
  376. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_procwait.py +0 -0
  377. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_prompt_loader.py +0 -0
  378. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_read_json_hardening.py +0 -0
  379. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_redact.py +0 -0
  380. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_resolve.py +0 -0
  381. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_round_cmd.py +0 -0
  382. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_round_cmd_sigterm.py +0 -0
  383. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_round_log_helpers.py +0 -0
  384. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_round_outcome_agent_axis.py +0 -0
  385. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_round_view.py +0 -0
  386. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_runner.py +0 -0
  387. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_runner_sidecar_ordering.py +0 -0
  388. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_runner_throttle.py +0 -0
  389. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_sandbox_probe.py +0 -0
  390. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_scaffold.py +0 -0
  391. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_schedule.py +0 -0
  392. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_cmd_bounded.py +0 -0
  393. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_config_broken.py +0 -0
  394. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_crash_loop.py +0 -0
  395. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_mem_gate.py +0 -0
  396. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_phase_schedule.py +0 -0
  397. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_policy.py +0 -0
  398. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
  399. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_log.py +0 -0
  400. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_oom_killed.py +0 -0
  401. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_terminate.py +0 -0
  402. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_wait_exit.py +0 -0
  403. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_schedule_gate.py +0 -0
  404. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_sentinel.py +0 -0
  405. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_startup_hooks.py +0 -0
  406. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_wakeup.py +0 -0
  407. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_service_mode.py +0 -0
  408. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_service_unit.py +0 -0
  409. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_spawn_seam.py +0 -0
  410. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_startup_battery_exit.py +0 -0
  411. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_startup_check.py +0 -0
  412. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_substrate.py +0 -0
  413. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
  414. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_terminate_round_wait_exit.py +0 -0
  415. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_throttle_back_off_ladder.py +0 -0
  416. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_throttle_epoch_bound.py +0 -0
  417. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_throttle_plugin_classification.py +0 -0
  418. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_upgrade_migrate.py +0 -0
  419. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_vcs_state.py +0 -0
@@ -195,6 +195,7 @@ supervisor_stale_threshold_s
195
195
  disk
196
196
  memory
197
197
  pressure
198
+ brake
198
199
 
199
200
  # agent_runner.config.models.PhaseOverride
200
201
  round_budget_s
@@ -255,6 +256,11 @@ pause_windows
255
256
  stash_idempotency_s
256
257
  dirty_action
257
258
 
259
+ # agent_runner.config.models._HostHealthBrakeConfig
260
+ memory_high
261
+ memory_high_step_pct
262
+ warning_consecutive_samples
263
+
258
264
  # agent_runner.config.models._HostHealthDiskConfig
259
265
  warning_pct
260
266
  critical_pct
@@ -270,6 +276,7 @@ some_avg10_warning
270
276
  critical_consecutive_samples
271
277
  in_round_terminate
272
278
  cgroup_growth_rate_warning_mb_per_min
279
+ in_round_nudge
273
280
 
274
281
  # agent_runner.context_store.OrphanState
275
282
  round_num
@@ -5,6 +5,13 @@ 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.7] - 2026-09-15
9
+
10
+ ### Added
11
+ - Opt-in cgroup v2 `memory.high` soft-brake (`[monitor.host_health.brake] memory_high`, default off): under sustained warning-level memory pressure the supervisor reversibly writes `memory.high` on its OWN cgroup leaf to throttle a growing round before the host swaps, then restores it on recovery or at round end. `memory_high_step_pct` defaults to `0` = cap-at-current (throttle further growth with no synchronous reclaim burst — safe for SD-backed hosts); `step_pct >= 1` is opt-in aggressive reclaim that dumps ~step% of the leaf to swap at engage. Fail-open; arms only when the leaf is delegated.
12
+ - Opt-in early cooperative SIGTERM (`[monitor.host_health.pressure] in_round_nudge`, default off): fires the hard floor's SIGTERM two samples earlier so a cooperative agent gets its wrap-up grace; recorded as `round_mem_terminated` with a new `tier` field so the mem-loop give-up still converges.
13
+ - New events `memory_high_engaged` / `memory_high_released` / `memory_high_write_failed`; `doctor` and `peek --json` (schema `2.5`) report the brake as `off | armed | inert(<reason>)`.
14
+
8
15
  ## [0.3.6] - 2026-09-15
9
16
 
10
17
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.3.6
3
+ Version: 0.3.7
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
@@ -32,6 +32,9 @@ from agent_runner._emit.memory import ( # noqa: F401 — public re-export
32
32
  emit_cgroup_growth_rate_warning,
33
33
  emit_host_cgroup_memory_limit,
34
34
  emit_mem_pressure_deferred_to_cgroup,
35
+ emit_memory_high_engaged,
36
+ emit_memory_high_released,
37
+ emit_memory_high_write_failed,
35
38
  emit_round_cgroup_memory,
36
39
  emit_round_deferred,
37
40
  emit_round_mem_critical_sample,
@@ -88,6 +91,9 @@ __all__ = [
88
91
  "emit_mem_loop",
89
92
  "emit_mem_loop_persistent",
90
93
  "emit_mem_pressure_deferred_to_cgroup",
94
+ "emit_memory_high_engaged",
95
+ "emit_memory_high_released",
96
+ "emit_memory_high_write_failed",
91
97
  "emit_plugin_builtin_name_squat",
92
98
  "emit_plugin_checksum_mismatch",
93
99
  "emit_plugin_sandbox_degraded",
@@ -36,6 +36,7 @@ def emit_round_mem_terminated(
36
36
  message: str,
37
37
  consecutive: int,
38
38
  context: dict,
39
+ tier: str = "terminate",
39
40
  ) -> None:
40
41
  """Emit when _spawn_round's mid-round hard floor terminated a ballooning
41
42
  round on critical host_health pressure -- the actual coma-preventer (a
@@ -45,7 +46,15 @@ def emit_round_mem_terminated(
45
46
  ``consecutive`` (the critical_streak that crossed the threshold)
46
47
  and ``context`` (Pressure.context -- the actual psi/mem numbers, e.g.
47
48
  psi_full_avg10) make the kill legible from the event stream alone, so an
48
- operator can retune host_health thresholds without SSH."""
49
+ operator can retune host_health thresholds without SSH.
50
+
51
+ ``tier`` (``"terminate"`` | ``"nudge"``) distinguishes the hard floor's
52
+ ``_terminate_round`` (TERM -> grace -> killpg) from the early cooperative
53
+ nudge (a bare ``proc.terminate()`` two samples earlier, no wait, no
54
+ killpg) -- both fire this SAME event kind so ``round_was_mem_terminated``
55
+ and the ``mem_loop`` give-up breaker count a nudged round exactly like a
56
+ hard-terminated one; the default keeps every existing caller byte-
57
+ compatible."""
49
58
  from agent_runner.events import ROUND_MEM_TERMINATED, emit
50
59
 
51
60
  emit(
@@ -57,6 +66,7 @@ def emit_round_mem_terminated(
57
66
  message=message,
58
67
  consecutive=consecutive,
59
68
  context=context,
69
+ tier=tier,
60
70
  )
61
71
 
62
72
 
@@ -246,6 +256,59 @@ def emit_mem_pressure_deferred_to_cgroup(
246
256
  emit(log_dir, MEM_PRESSURE_DEFERRED_TO_CGROUP, pid=pid, signal=signal, message=message)
247
257
 
248
258
 
259
+ def emit_memory_high_engaged(
260
+ log_dir: Path,
261
+ *,
262
+ round_num: int,
263
+ previous: str,
264
+ written: int,
265
+ memory_current: int,
266
+ write_ms: int,
267
+ signal: str,
268
+ context: dict,
269
+ rate_mb_per_min: float | None,
270
+ ) -> None:
271
+ """Emit when the reversible cgroup memory.high soft-brake engaged on serve's
272
+ own leaf under sustained warning pressure. ``previous``/``written`` bracket
273
+ the reversible write; ``signal``/``context`` are the triggering Pressure;
274
+ ``rate_mb_per_min`` rides along as growth-rate context for Pi calibration."""
275
+ from agent_runner.events import MEMORY_HIGH_ENGAGED, emit
276
+
277
+ emit(
278
+ log_dir,
279
+ MEMORY_HIGH_ENGAGED,
280
+ round_num=round_num,
281
+ previous=previous,
282
+ written=written,
283
+ memory_current=memory_current,
284
+ write_ms=write_ms,
285
+ signal=signal,
286
+ context=context,
287
+ rate_mb_per_min=rate_mb_per_min,
288
+ )
289
+
290
+
291
+ def emit_memory_high_released(log_dir: Path, *, round_num: int, reason: str) -> None:
292
+ """Emit when the soft-brake was restored to its stashed value. ``reason`` is
293
+ ``recovered`` (N healthy ticks) or ``round_end`` (the finally restore, which
294
+ covers every exit path out of the round incl. wedged/exception)."""
295
+ from agent_runner.events import MEMORY_HIGH_RELEASED, emit
296
+
297
+ emit(log_dir, MEMORY_HIGH_RELEASED, round_num=round_num, reason=reason)
298
+
299
+
300
+ def emit_memory_high_write_failed(log_dir: Path, *, round_num: int, errno: int | None) -> None:
301
+ """Emit once when a memory.high write (engage or restore) hit an OSError --
302
+ fail-open: the brake disarms for the round and serve continues unthrottled,
303
+ exactly as when the brake is configured off. ``errno`` is the real OSError
304
+ code from the engage path; the restore path (which only sees a bool from
305
+ :func:`agent_runner.metrics.restore_leaf_memory_high`) passes ``None`` --
306
+ never ``0``, which conventionally reads as POSIX success."""
307
+ from agent_runner.events import MEMORY_HIGH_WRITE_FAILED, emit
308
+
309
+ emit(log_dir, MEMORY_HIGH_WRITE_FAILED, round_num=round_num, errno=errno)
310
+
311
+
249
312
  def emit_cgroup_growth_rate_warning(
250
313
  log_dir: Path,
251
314
  *,
@@ -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.6'
22
- __version_tuple__ = version_tuple = (0, 3, 6)
21
+ __version__ = version = '0.3.7'
22
+ __version_tuple__ = version_tuple = (0, 3, 7)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -41,6 +41,9 @@ from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-ex
41
41
  emit_mem_loop,
42
42
  emit_mem_loop_persistent,
43
43
  emit_mem_pressure_deferred_to_cgroup,
44
+ emit_memory_high_engaged,
45
+ emit_memory_high_released,
46
+ emit_memory_high_write_failed,
44
47
  emit_plugin_builtin_name_squat,
45
48
  emit_plugin_checksum_mismatch,
46
49
  emit_plugin_sandbox_degraded,
@@ -22,6 +22,8 @@ from agent_runner import metrics
22
22
  from agent_runner._serve_policy import _ROUND_UNREAPED_RC
23
23
  from agent_runner.api import (
24
24
  emit_host_cgroup_memory_limit,
25
+ emit_memory_high_released,
26
+ emit_memory_high_write_failed,
25
27
  emit_round_cgroup_memory,
26
28
  emit_round_oom_killed,
27
29
  )
@@ -37,6 +39,27 @@ from agent_runner.api import (
37
39
  # round.
38
40
  _ROUND_CGROUP_STATE_BY_LOG_DIR: dict[Path, dict] = {}
39
41
 
42
+ # Boot-armed soft-brake gate: True iff the brake is configured on AND this
43
+ # process's own cgroup leaf is delegated (memory.high writable) -- probed ONCE
44
+ # at serve boot (_probe_and_emit_cgroup_defer already reads cgroup_delegated),
45
+ # read every mid-round tick by _serve_round via _brake_step_for. Same
46
+ # per-log_dir stable-default pattern as _ROUND_CGROUP_STATE_BY_LOG_DIR (one
47
+ # entry per serve process in production); cmd() cannot grow to thread it (145
48
+ # budget), so it rides in a module dict, not an argument.
49
+ _BRAKE_ARMED_BY_LOG_DIR: dict[Path, bool] = {}
50
+
51
+
52
+ def _brake_step_for(log_dir: Path, host_health_cfg) -> int | None:
53
+ """The armed soft-brake step_pct for this log_dir, or None when the brake is
54
+ disarmed: config off, or the boot probe found the leaf undelegated / no
55
+ cgroup v2. The step itself is single-sourced from config; the dict only
56
+ carries the boot-time delegated-and-on decision."""
57
+ if not host_health_cfg.brake.memory_high:
58
+ return None
59
+ if not _BRAKE_ARMED_BY_LOG_DIR.get(log_dir, False):
60
+ return None
61
+ return host_health_cfg.brake.memory_high_step_pct
62
+
40
63
 
41
64
  def _stash_round_cgroup_state(
42
65
  log_dir: Path, cg_base: dict, peak_current: int, peak_swap: int
@@ -190,18 +213,69 @@ def _maybe_emit_oom_killed(
190
213
  # report ask #3). Advisory only -- never changes the operator's cgroup/unit.
191
214
  _SWAP_CAP_ADVISORY_PCT = 25.0
192
215
 
193
- # The delegation-readiness advisory: memory.max/memory.high are bound on this
194
- # cgroup, but metrics.cgroup_delegated found the leaf is NOT delegated
195
- # (systemd Delegate=yes) -- a future release that wants to write memory.high
196
- # on a round-scoped nested cgroup needs delegation first. Advisory only; this
197
- # probe and its hint never write to the cgroup or the unit.
216
+ # The delegation-readiness / brake-inert advisory. Fires in two DISTINCT
217
+ # cases, both meaning "memory.high can't be written from here":
218
+ # (1) delegated is False (CONFIRMED not delegated -- systemd Delegate=yes
219
+ # is absent) AND memory.max/memory.high are already bound (memory_high
220
+ # set, or the own_scope "add memory.high" hint above just fired) --
221
+ # the existing config is informational-only without delegation.
222
+ # (2) the operator explicitly turned the soft-brake on
223
+ # ([monitor.host_health.brake] memory_high=true, threaded in here as
224
+ # brake_memory_high) but delegated is not True (False OR None) -- the
225
+ # brake will silently do nothing, worth announcing at boot even with
226
+ # no memory.max/memory.high configured yet.
227
+ # Deliberately NOT ``delegated is not True`` across the board: cgroup_delegated
228
+ # returns None for two different reasons -- cgroup v2 unavailable (harmless)
229
+ # AND the leaf's own os.stat() raising OSError (delegation status genuinely
230
+ # UNKNOWN, even though memory_high/own_scope's ancestor-walk resolved fine).
231
+ # Broadening case (1) to None would assert "not writable" where we actually
232
+ # don't know -- an overclaim this release can't afford. Case (2) is the one
233
+ # exception: the operator asked for the brake, so ANY non-confirmed-delegated
234
+ # state (False or unknown) is worth flagging as "may be inert" up front.
235
+ # Advisory only; this probe and its hint never write to the cgroup or the unit.
198
236
  _UNDELEGATED_HINT = (
199
- "this cgroup is not delegated (systemd Delegate=yes) -- memory.high can be "
200
- "read but not managed here until it is"
237
+ "memory.high on this cgroup is not writable by this process; the soft-brake "
238
+ "is inert. Run serve as a user-mode unit (`systemctl --user` + `loginctl "
239
+ "enable-linger`) or as a root system unit."
201
240
  )
202
241
 
203
242
 
204
- def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
243
+ def brake_report_state(enabled: bool, delegated: bool | None) -> str:
244
+ """The operator-facing soft-brake state for doctor/peek: ``off`` (not
245
+ configured), ``armed`` (on AND the leaf is delegated so memory.high is
246
+ writable), or ``inert(<reason>)`` (on but the write path can't arm)."""
247
+ if not enabled:
248
+ return "off"
249
+ if delegated is True:
250
+ return "armed"
251
+ return "inert(undelegated)" if delegated is False else "inert(no cgroup v2)"
252
+
253
+
254
+ def current_brake_state(cfg) -> str:
255
+ """Resolve the soft-brake state string for `cfg` from live cgroup facts --
256
+ the one-shot ``cgroup_memory_limits() -> cgroup_delegated(self_cgroup=...) ->
257
+ brake_report_state`` sequence, shared by peek (cli.common.emit) and doctor
258
+ so neither re-derives it inline."""
259
+ limits = metrics.cgroup_memory_limits()
260
+ delegated = metrics.cgroup_delegated(self_cgroup=limits["cgroup_path"])
261
+ return brake_report_state(cfg.monitor.host_health.brake.memory_high, delegated)
262
+
263
+
264
+ def _release_brake(log_dir: Path, round_num: int, previous: str, *, reason: str) -> bool:
265
+ """Restore memory.high on serve's own leaf to `previous` and emit the
266
+ matching event: ``memory_high_released`` on success, ``memory_high_write_failed``
267
+ (errno None -- restore only yields a bool) on OSError. Returns whether the
268
+ restore succeeded; callers own their own brake_engaged/brake_previous/
269
+ brake_restore_failed transitions off that result. Shared by _spawn_round's
270
+ mid-round recovery-release site and its unconditional round-end finally."""
271
+ if metrics.restore_leaf_memory_high(previous):
272
+ emit_memory_high_released(log_dir, round_num=round_num, reason=reason)
273
+ return True
274
+ emit_memory_high_write_failed(log_dir, round_num=round_num, errno=None)
275
+ return False
276
+
277
+
278
+ def _probe_and_emit_cgroup_defer(log_dir: Path, *, brake_memory_high: bool = False) -> bool:
205
279
  """Probe this process's cgroup v2 memory budget once at serve startup,
206
280
  emit host_cgroup_memory_limit for observability, and return whether the
207
281
  mid-round hard floor should defer to kernel cgroup-OOM: True only when
@@ -249,8 +323,22 @@ def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
249
323
  the bounding ancestor's ``memory.high`` soft-throttle threshold
250
324
  (``metrics.cgroup_memory_high``), ``None`` when unset -- for the same
251
325
  reason: an operator asking "is MemoryHigh even set" is exactly this
252
- release's field ask. Advisory only -- this never changes the operator's
253
- cgroup or systemd unit."""
326
+ release's field ask.
327
+
328
+ A THIRD advisory, ``_UNDELEGATED_HINT``, reads ``brake_memory_high`` --
329
+ the caller-supplied ``[monitor.host_health.brake] memory_high`` config
330
+ switch -- alongside ``delegated``: when the operator turned that brake
331
+ on but this process's own cgroup leaf isn't confirmed delegated
332
+ (``delegated is not True``), the brake would silently write nothing, so
333
+ boot announces it up front even with no memory.max/memory.high
334
+ configured. Independently of the brake, the SAME hint also covers the
335
+ pre-existing "config is informational without delegation" case
336
+ (``delegated is False`` -- CONFIRMED not delegated, never the merely
337
+ unresolved ``None``) when memory_high is already set or the own_scope
338
+ hint above just fired. See the comment block above ``_UNDELEGATED_HINT``
339
+ for why those two triggers use different ``delegated`` strictness.
340
+ Advisory only -- this never changes the operator's cgroup or systemd
341
+ unit."""
254
342
  limits = metrics.cgroup_memory_limits()
255
343
  swap_total = metrics.swap_total_bytes()
256
344
  # Reuse the leaf cgroup_memory_limits already resolved above -- skips
@@ -263,6 +351,8 @@ def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
263
351
  # way as calling with no override would.
264
352
  memory_high = metrics.cgroup_memory_high(self_cgroup=limits["cgroup_path"])
265
353
  delegated = metrics.cgroup_delegated(self_cgroup=limits["cgroup_path"])
354
+ if brake_memory_high:
355
+ _BRAKE_ARMED_BY_LOG_DIR[log_dir] = delegated is True
266
356
  swap_max = limits["memory_swap_max"]
267
357
  swap_cap_pct = (
268
358
  round(100.0 * swap_max / swap_total, 1) if swap_max is not None and swap_total > 0 else None
@@ -299,7 +389,9 @@ def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
299
389
  "throttle's PSI-full rise doesn't trip the mid-round floor"
300
390
  )
301
391
  advisories.append(hint)
302
- if delegated is False and (memory_high is not None or own_scope):
392
+ if (brake_memory_high and delegated is not True) or (
393
+ delegated is False and (memory_high is not None or own_scope)
394
+ ):
303
395
  advisories.append(_UNDELEGATED_HINT)
304
396
  advisory = "; ".join(advisories) or None
305
397
  if advisory is not None:
@@ -62,6 +62,8 @@ from agent_runner.api import (
62
62
  emit_mem_loop,
63
63
  emit_mem_loop_persistent,
64
64
  emit_mem_pressure_deferred_to_cgroup,
65
+ emit_memory_high_engaged,
66
+ emit_memory_high_write_failed,
65
67
  emit_plugin_spawn_decision,
66
68
  emit_plugin_spawn_override_ignored,
67
69
  emit_round_deferred,
@@ -74,8 +76,10 @@ from agent_runner.api import (
74
76
  )
75
77
  from agent_runner.api_types import SpawnDecision
76
78
  from agent_runner.cli._serve_cgroup import (
79
+ _brake_step_for,
77
80
  _emit_round_cgroup_memory,
78
81
  _maybe_emit_oom_killed,
82
+ _release_brake,
79
83
  _stash_round_cgroup_state,
80
84
  )
81
85
  from agent_runner.clock import SYSTEM_CLOCK, Clock
@@ -461,8 +465,8 @@ _MEM_CHECK_INTERVAL_S = 10
461
465
 
462
466
 
463
467
  def _mid_round_action(
464
- cfg, defer_to_cgroup: bool, critical_streak: int
465
- ) -> Literal["terminate", "defer", "count_only"]:
468
+ cfg, defer_to_cgroup: bool, critical_streak: int, nudged: bool
469
+ ) -> Literal["terminate", "defer", "count_only", "nudge"]:
466
470
  """Pure, subprocess-free verdict for a critical mid-round
467
471
  tick, once the caller has already incremented ``critical_streak`` and
468
472
  (when the streak is within the emit cap) emitted
@@ -484,13 +488,29 @@ def _mid_round_action(
484
488
  - ``"terminate"`` — sustained critical pressure, no cgroup containment to
485
489
  defer to: the caller ``_terminate_round``s and emits
486
490
  ``round_mem_terminated``.
491
+ - ``"nudge"`` -- ``in_round_nudge`` is on and this is the FIRST critical
492
+ sample (``critical_streak == 1``, ``nudged`` still False) of a streak whose
493
+ hard verdict WOULD be ``terminate`` (``in_round_terminate`` and not
494
+ ``defer_to_cgroup``): fire the hard floor's SIGTERM at the first critical
495
+ sample (not the sustained threshold) so a cooperative agent gets its
496
+ wrap-up grace. ``defer_to_cgroup`` disables the nudge (no terminate to
497
+ move earlier) but NOT the brake -- so does ``critical_consecutive_samples
498
+ == 1``, since the hard verdict above already matches at streak 1.
487
499
  """
488
- if not (
500
+ if (
489
501
  cfg.pressure.in_round_terminate
490
502
  and critical_streak >= cfg.pressure.critical_consecutive_samples
491
503
  ):
492
- return "count_only"
493
- return "defer" if defer_to_cgroup else "terminate"
504
+ return "defer" if defer_to_cgroup else "terminate"
505
+ if (
506
+ cfg.pressure.in_round_nudge
507
+ and not nudged
508
+ and critical_streak == 1
509
+ and cfg.pressure.in_round_terminate
510
+ and not defer_to_cgroup
511
+ ):
512
+ return "nudge"
513
+ return "count_only"
494
514
 
495
515
 
496
516
  def _spawn_round(
@@ -586,6 +606,12 @@ def _spawn_round(
586
606
  stderr=subprocess.STDOUT,
587
607
  start_new_session=True,
588
608
  )
609
+ brake_engaged = False
610
+ brake_previous: str | None = None
611
+ brake_disarmed = False
612
+ brake_restore_failed = False
613
+ warning_streak = 0
614
+ none_streak = 0
589
615
  try:
590
616
  deadline = clock.monotonic() + timeout_s
591
617
  next_mem_check = (
@@ -600,6 +626,7 @@ def _spawn_round(
600
626
  # terminates the round.
601
627
  prev_tick_sample: dict | None = None
602
628
  critical_streak = 0
629
+ nudged = False
603
630
  cgroup_defer_notified = False
604
631
  prev_mem_bytes: int | None = None
605
632
  prev_mem_mono: float | None = None
@@ -644,6 +671,7 @@ def _spawn_round(
644
671
  cur_sample, prev_tick_sample, host_health_cfg
645
672
  )
646
673
  prev_tick_sample = cur_sample
674
+ rate_mb_per_min: float | None = None
647
675
  if cg_base:
648
676
  cg_now = metrics.cgroup_memory_usage(bounding_cgroup=cg_bounding)
649
677
  cg_peak_current = max(cg_peak_current, cg_now.get("memory_current", 0))
@@ -655,7 +683,6 @@ def _spawn_round(
655
683
  mem_source = "rss_sum"
656
684
  now_mono = clock.monotonic()
657
685
  if cur_mem_bytes is not None:
658
- rate_mb_per_min = None
659
686
  if prev_mem_bytes is not None and prev_mem_mono is not None:
660
687
  elapsed_s = max(now_mono - prev_mem_mono, 0.0)
661
688
  rate_mb_per_min = (
@@ -692,6 +719,55 @@ def _spawn_round(
692
719
  # (a {}/None source read). Keep prev_mem_bytes/prev_mem_mono
693
720
  # untouched so the NEXT successful tick still diffs against
694
721
  # the last real reading, not a spurious reset.
722
+ if pressure is not None:
723
+ warning_streak += 1
724
+ none_streak = 0
725
+ else:
726
+ warning_streak = 0
727
+ none_streak += 1
728
+ if (
729
+ brake_engaged
730
+ and brake_previous is not None
731
+ and not brake_restore_failed
732
+ and none_streak >= host_health_cfg.brake.warning_consecutive_samples
733
+ ):
734
+ if _release_brake(
735
+ log_dir, round_num, brake_previous, reason="recovered"
736
+ ):
737
+ brake_engaged = False
738
+ brake_previous = None
739
+ else:
740
+ brake_restore_failed = True
741
+ brake_step = _brake_step_for(log_dir, host_health_cfg)
742
+ if (
743
+ pressure is not None
744
+ and brake_step is not None
745
+ and not brake_disarmed
746
+ and not brake_engaged
747
+ and warning_streak >= host_health_cfg.brake.warning_consecutive_samples
748
+ ):
749
+ t0 = clock.monotonic()
750
+ result = metrics.engage_leaf_memory_high(step_pct=brake_step)
751
+ write_ms = int((clock.monotonic() - t0) * 1000)
752
+ if result.get("engaged"):
753
+ brake_engaged = True
754
+ brake_previous = result["previous"]
755
+ emit_memory_high_engaged(
756
+ log_dir,
757
+ round_num=round_num,
758
+ previous=result["previous"],
759
+ written=result["written"],
760
+ memory_current=result["memory_current"],
761
+ write_ms=write_ms,
762
+ signal=pressure.signal,
763
+ context=pressure.context,
764
+ rate_mb_per_min=rate_mb_per_min,
765
+ )
766
+ elif "errno" in result:
767
+ emit_memory_high_write_failed(
768
+ log_dir, round_num=round_num, errno=result["errno"]
769
+ )
770
+ brake_disarmed = True
695
771
  if pressure is not None and pressure.severity == "critical":
696
772
  critical_streak += 1
697
773
  if (
@@ -705,9 +781,22 @@ def _spawn_round(
705
781
  context=pressure.context,
706
782
  )
707
783
  action = _mid_round_action(
708
- host_health_cfg, defer_to_cgroup, critical_streak
784
+ host_health_cfg, defer_to_cgroup, critical_streak, nudged
709
785
  )
710
- if action == "terminate":
786
+ if action == "nudge":
787
+ nudged = True
788
+ proc.terminate() # bare SIGTERM to the leader -- NO wait, NO killpg
789
+ emit_round_mem_terminated(
790
+ log_dir,
791
+ pid=proc.pid,
792
+ severity=pressure.severity,
793
+ signal=pressure.signal,
794
+ message=pressure.message,
795
+ consecutive=critical_streak,
796
+ context=pressure.context,
797
+ tier="nudge",
798
+ )
799
+ elif action == "terminate":
711
800
  returncode = _terminate_round(proc, clock=clock)
712
801
  emit_round_mem_terminated(
713
802
  log_dir,
@@ -717,10 +806,11 @@ def _spawn_round(
717
806
  message=pressure.message,
718
807
  consecutive=critical_streak,
719
808
  context=pressure.context,
809
+ tier="terminate",
720
810
  )
721
811
  _stash_cgroup()
722
812
  return returncode
723
- if action == "defer" and not cgroup_defer_notified:
813
+ elif action == "defer" and not cgroup_defer_notified:
724
814
  emit_mem_pressure_deferred_to_cgroup(
725
815
  log_dir,
726
816
  pid=proc.pid,
@@ -736,6 +826,17 @@ def _spawn_round(
736
826
  # exception-path cleanup: never orphan the round pgroup
737
827
  _terminate_round(proc, clock=clock)
738
828
  raise
829
+ finally:
830
+ # Unconditional restore (mirrors _stash_cgroup): runs on every exit path out of the try
831
+ # above -- both mid-loop returns, the exception re-raise, and the wedged fall-through
832
+ # below. A skipped write here leaves the cgroup throttled indefinitely. _release_brake's
833
+ # restore is fail-open (never raises); its best-effort emit is shielded here, so a
834
+ # disk-full OSError can never replace the ORIGINAL propagating exception.
835
+ if brake_engaged and brake_previous is not None:
836
+ try:
837
+ _release_brake(log_dir, round_num, brake_previous, reason="round_end")
838
+ except Exception:
839
+ pass
739
840
  # Safety kill BEFORE the observability write: the wedged round must not
740
841
  # keep burning wall-clock (and, if its own reap hangs, its agent's
741
842
  # budget) waiting on the least-reliable step (a disk write) to finish
@@ -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.4"
22
+ PEEK_SCHEMA_VERSION = "2.5"
23
23
 
24
24
 
25
25
  def cfg_from_args(args) -> Config:
@@ -123,6 +123,10 @@ def emit(value: Any, *, json_mode: bool, cfg: Config | None = None) -> None:
123
123
  "plugins": plugins_block,
124
124
  **_to_jsonable(value),
125
125
  }
126
+ if cfg is not None:
127
+ from agent_runner.cli._serve_cgroup import current_brake_state
128
+
129
+ wrapped["brake"] = current_brake_state(cfg)
126
130
  print(json.dumps(wrapped, indent=2, default=str))
127
131
  else:
128
132
  print(json.dumps(_to_jsonable(value), indent=2, default=str))
@@ -45,20 +45,23 @@ def _plan(cfg, rounds: int) -> list[dict]:
45
45
  return out
46
46
 
47
47
 
48
- def _cgroup_report() -> dict:
48
+ def _cgroup_report(cfg) -> dict:
49
49
  """Read-only cgroup delegation-readiness snapshot for `doctor` -- never
50
50
  emits an event (unlike serve's `_probe_and_emit_cgroup_defer`, which
51
51
  shares the same underlying `metrics` probes but writes
52
52
  `host_cgroup_memory_limit`). `doctor` never launches the agent or writes
53
53
  anything; this section is the same discipline applied to cgroup state."""
54
54
  from agent_runner import metrics
55
+ from agent_runner.cli._serve_cgroup import brake_report_state
55
56
 
56
57
  limits = metrics.cgroup_memory_limits()
58
+ delegated = metrics.cgroup_delegated(self_cgroup=limits["cgroup_path"])
57
59
  return {
58
60
  "cgroup_path": limits["cgroup_path"],
59
61
  "memory_max": limits["memory_max"],
60
62
  "memory_high": metrics.cgroup_memory_high(self_cgroup=limits["cgroup_path"]),
61
- "delegated": metrics.cgroup_delegated(self_cgroup=limits["cgroup_path"]),
63
+ "delegated": delegated,
64
+ "brake": brake_report_state(cfg.monitor.host_health.brake.memory_high, delegated),
62
65
  }
63
66
 
64
67
 
@@ -79,7 +82,7 @@ def cmd_doctor(args) -> int:
79
82
  overlaps=phase_select.find_phase_window_overlaps(cfg),
80
83
  plan=_plan(cfg, args.rounds),
81
84
  sandbox=doctor_snapshot(cfg),
82
- cgroup=_cgroup_report(),
85
+ cgroup=_cgroup_report(cfg),
83
86
  sigterm_grace=sigterm_grace,
84
87
  )
85
88
  json_mode = getattr(args, "json", False)
@@ -143,7 +146,21 @@ def _format(report: DoctorReport) -> str:
143
146
  lines.append(f" memory_max: {cgroup['memory_max']}")
144
147
  lines.append(f" memory_high: {cgroup['memory_high']}")
145
148
  lines.append(f" delegated: {cgroup['delegated']}")
146
- if cgroup["delegated"] is False:
149
+ lines.append(f" brake: {cgroup['brake']}")
150
+ # Approximates serve's own advisory (_serve_cgroup._probe_and_emit_cgroup_defer):
151
+ # only surface the "soft-brake is inert" hint when it could actually apply --
152
+ # the brake is enabled (report.cgroup["brake"] != "off") or a memory_high/
153
+ # memory_max limit is already bound. Otherwise the brake is at its default
154
+ # (off) with nothing bound, and the hint would contradict "brake: off" above.
155
+ # Coarser than serve's own_scope (which additionally requires the bound to
156
+ # originate from the caller's OWN leaf, not an inherited ancestor) -- doctor's
157
+ # _cgroup_report never tracks the bounding cgroup path, so this errs toward
158
+ # showing the readiness hint in the ancestor-bound case.
159
+ if cgroup["delegated"] is False and (
160
+ cgroup["brake"] != "off"
161
+ or cgroup["memory_high"] is not None
162
+ or cgroup["memory_max"] is not None
163
+ ):
147
164
  from agent_runner.cli._serve_cgroup import _UNDELEGATED_HINT
148
165
 
149
166
  lines.append(f" hint: {_UNDELEGATED_HINT}")
@@ -744,7 +744,8 @@ def cmd(args) -> int:
744
744
  work_dir = cfg.runtime.work_dir
745
745
  # Emitted exactly once per serve lifetime -- the per-round defer decision below
746
746
  # reuses this bool rather than re-probing/re-emitting every round.
747
- cgroup_probe_defer = _probe_and_emit_cgroup_defer(log_dir)
747
+ brake_memory_high = cfg.monitor.host_health.brake.memory_high
748
+ cgroup_probe_defer = _probe_and_emit_cgroup_defer(log_dir, brake_memory_high=brake_memory_high)
748
749
  # Static config check -- phase-window collisions can't change mid-lifetime, so
749
750
  # (like the cgroup probe above) this runs once at boot, not once per round.
750
751
  overlaps = phase_select.find_phase_window_overlaps(cfg)
@@ -49,6 +49,7 @@ from agent_runner.config.models import ( # noqa: F401 — public re-export
49
49
  RuntimeConfig,
50
50
  ScheduleConfig,
51
51
  VcsConfig,
52
+ _HostHealthBrakeConfig,
52
53
  _HostHealthDiskConfig,
53
54
  _HostHealthMemoryConfig,
54
55
  _HostHealthPressureConfig,