cli-agent-runner 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (379) hide show
  1. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.vulture-whitelist.py +2 -0
  2. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/CHANGELOG.md +6 -0
  3. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/PKG-INFO +1 -1
  4. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_emit/__init__.py +2 -0
  5. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_emit/memory.py +30 -0
  6. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_observe.py +6 -0
  7. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_version.py +2 -2
  8. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/agent_runtime.py +20 -0
  9. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/api.py +1 -0
  10. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/api_types.py +1 -0
  11. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/_serve_round.py +61 -2
  12. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/common.py +1 -1
  13. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/config/models.py +4 -0
  14. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/config/parsers.py +5 -0
  15. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/config/validators.py +13 -0
  16. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/events.py +1 -0
  17. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/host_health.py +22 -0
  18. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/architecture.md +1 -0
  19. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/configuration.md +3 -2
  20. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/memory-efficiency.md +11 -0
  21. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/plugins.md +2 -2
  22. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/contract/test_public_api_surface.py +1 -0
  23. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_architecture.py +5 -1
  24. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_peek_schema_version.py +1 -1
  25. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime_grace.py +63 -0
  26. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_observation.py +33 -0
  27. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config.py +38 -0
  28. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_events.py +27 -0
  29. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_host_health.py +53 -0
  30. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_round_cgroup_spine.py +168 -0
  31. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.codecov.yml +0 -0
  32. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  33. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  34. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  35. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  36. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/dependabot.yml +0 -0
  37. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/workflows/ci.yml +0 -0
  38. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/workflows/release.yml +0 -0
  39. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.gitignore +0 -0
  40. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/BACKLOG.md +0 -0
  41. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/CODE_OF_CONDUCT.md +0 -0
  42. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/CONTRIBUTING.md +0 -0
  43. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/LICENSE +0 -0
  44. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/README.md +0 -0
  45. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/README.zh.md +0 -0
  46. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/SECURITY.md +0 -0
  47. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/__init__.py +0 -0
  48. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_docgen.py +0 -0
  49. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_emit/agent.py +0 -0
  50. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_emit/rounds.py +0 -0
  51. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_emit/serve.py +0 -0
  52. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_install.py +0 -0
  53. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_lifecycle.py +0 -0
  54. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_monitor_detectors.py +0 -0
  55. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_monitor_registry.py +0 -0
  56. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_monitor_state.py +0 -0
  57. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_plugin_manifest.py +0 -0
  58. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_plugin_scan.py +0 -0
  59. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_redact.py +0 -0
  60. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_registry.py +0 -0
  61. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_resolve.py +0 -0
  62. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_round_outcome.py +0 -0
  63. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_round_support.py +0 -0
  64. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_serve_policy.py +0 -0
  65. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_substrate.py +0 -0
  66. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_throttle.py +0 -0
  67. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/__init__.py +0 -0
  68. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/_constants.py +0 -0
  69. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  70. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/codewhale.py +0 -0
  71. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
  72. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/gemini.py +0 -0
  73. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/kimi.py +0 -0
  74. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/pi.py +0 -0
  75. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/__init__.py +0 -0
  76. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/__main__.py +0 -0
  77. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/_serve_cgroup.py +0 -0
  78. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/doctor_cmd.py +0 -0
  79. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/events_cmd.py +0 -0
  80. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/init_cmd.py +0 -0
  81. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/install_cmd.py +0 -0
  82. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/migrate_cmd.py +0 -0
  83. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/monitor_cmd.py +0 -0
  84. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/peek_cmd.py +0 -0
  85. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/round_cmd.py +0 -0
  86. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/serve_cmd.py +0 -0
  87. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/service_cmd.py +0 -0
  88. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/upgrade_cmd.py +0 -0
  89. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/clock.py +0 -0
  90. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/config/__init__.py +0 -0
  91. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/config/errors.py +0 -0
  92. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/config/loader.py +0 -0
  93. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/context_store.py +0 -0
  94. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/defenses.py +0 -0
  95. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/hooks.py +0 -0
  96. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/http_progress.py +0 -0
  97. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/lifecycle.py +0 -0
  98. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/metrics.py +0 -0
  99. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/migrations.py +0 -0
  100. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/monitor.py +0 -0
  101. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/phase_select.py +0 -0
  102. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/__init__.py +0 -0
  103. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/aider.toml +0 -0
  104. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/claude.toml +0 -0
  105. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/codewhale.toml +0 -0
  106. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/gemini.toml +0 -0
  107. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/kimi.toml +0 -0
  108. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/pi.toml +0 -0
  109. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/prompt_loader.py +0 -0
  110. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/remote_relay.py +0 -0
  111. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/round_log.py +0 -0
  112. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/round_view.py +0 -0
  113. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/runner.py +0 -0
  114. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/scaffold.py +0 -0
  115. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/schedule.py +0 -0
  116. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/service_unit.py +0 -0
  117. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/startup_check.py +0 -0
  118. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/vcs_state.py +0 -0
  119. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/build.sh +0 -0
  120. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/deploy/Dockerfile +0 -0
  121. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/deploy/docker-compose.yml +0 -0
  122. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/README.md +0 -0
  123. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/commands.md +0 -0
  124. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/events.md +0 -0
  125. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/long-running-agents.md +0 -0
  126. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/migrations/0.2.md +0 -0
  127. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/migrations/0.3.md +0 -0
  128. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/quickstart.md +0 -0
  129. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/recipes/aider.md +0 -0
  130. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/recipes/codewhale.md +0 -0
  131. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/recipes/container-pi.md +0 -0
  132. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/recipes/deploy-all-in-one.md +0 -0
  133. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/recipes/kimi.md +0 -0
  134. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/recipes/pi.md +0 -0
  135. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/runbook.md +0 -0
  136. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/thesis.md +0 -0
  137. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/pyproject.toml +0 -0
  138. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/__init__.py +0 -0
  139. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/_clock.py +0 -0
  140. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/_test_helpers.py +0 -0
  141. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/conftest.py +0 -0
  142. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/contract/__init__.py +0 -0
  143. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/e2e/__init__.py +0 -0
  144. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/e2e/conftest.py +0 -0
  145. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  146. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/e2e/test_e2e_install_systemd.py +0 -0
  147. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  148. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  149. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  150. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  151. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  152. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/generate_vulture_whitelist.py +0 -0
  153. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/__init__.py +0 -0
  154. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_auto_stop_e2e.py +0 -0
  155. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_bounded_run.py +0 -0
  156. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_context_enricher_namespacing.py +0 -0
  157. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_dirty_handler_seam.py +0 -0
  158. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_fresh_eyes_signal.py +0 -0
  159. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_grace_kill_emission.py +0 -0
  160. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_install_dry_run.py +0 -0
  161. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_monitor_seeded.py +0 -0
  162. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_plugin_detector_loaded.py +0 -0
  163. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_plugin_owned_paths.py +0 -0
  164. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_plugin_real_flow.py +0 -0
  165. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  166. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  167. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_scaffold_presets.py +0 -0
  168. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_serve_loop.py +0 -0
  169. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_spawn_round_mem_floor.py +0 -0
  170. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_spawn_round_wedged.py +0 -0
  171. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_substrate_fingerprint.py +0 -0
  172. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_transient_error_backoff.py +0 -0
  173. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/__init__.py +0 -0
  174. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/_docs.py +0 -0
  175. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/_event_scan.py +0 -0
  176. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_atomic_write_enforced.py +0 -0
  177. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_authored_facts_declared.py +0 -0
  178. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  179. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_bdd_test_conventions.py +0 -0
  180. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_catalogs.py +0 -0
  181. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_classification_ssot.py +0 -0
  182. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_config_error_consistency.py +0 -0
  183. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_config_value_set_ssot.py +0 -0
  184. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_container_stop_bound.py +0 -0
  185. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
  186. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  187. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
  188. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_cli_claims.py +0 -0
  189. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_operator_surface.py +0 -0
  190. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
  191. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  192. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_usage_event_fields.py +0 -0
  193. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_docs_generated.py +0 -0
  194. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_docs_index_complete.py +0 -0
  195. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_entry_points_resolve.py +0 -0
  196. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_event_int_coercion.py +0 -0
  197. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_event_kind_registry.py +0 -0
  198. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_event_kinds_ssot.py +0 -0
  199. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_events_doc_contract.py +0 -0
  200. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
  201. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_hook_contract_docs.py +0 -0
  202. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_import_footprint.py +0 -0
  203. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_layer_2_loop_size.py +0 -0
  204. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_migrations_registry_ssot.py +0 -0
  205. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_module_boundaries.py +0 -0
  206. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_module_sizes.py +0 -0
  207. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_monitor_self_exclusion.py +0 -0
  208. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_ai_signatures.py +0 -0
  209. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_internal_refs.py +0 -0
  210. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_price_logic.py +0 -0
  211. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  212. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_raw_time.py +0 -0
  213. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_vacuous_invariants.py +0 -0
  214. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_version_refs.py +0 -0
  215. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_phase_select_stateless.py +0 -0
  216. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_plugins_config_stable.py +0 -0
  217. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  218. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  219. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_round_alloc_growth.py +0 -0
  220. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_round_log_decode_boundary.py +0 -0
  221. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_round_num_single_source.py +0 -0
  222. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_round_result_stable.py +0 -0
  223. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  224. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  225. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_timeout_budget_invariant.py +0 -0
  226. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_upstream_schema_canary.py +0 -0
  227. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  228. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/literate/__init__.py +0 -0
  229. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/literate/parser.py +0 -0
  230. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/literate/test_parser.py +0 -0
  231. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/literate/test_quickstart.py +0 -0
  232. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/__init__.py +0 -0
  233. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime.py +0 -0
  234. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime_container.py +0 -0
  235. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
  236. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime_progress.py +0 -0
  237. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime_reap.py +0 -0
  238. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  239. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_assemble_prompt.py +0 -0
  240. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_events_stream.py +0 -0
  241. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_install.py +0 -0
  242. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_read_round_num.py +0 -0
  243. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_resolve_phase.py +0 -0
  244. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_service.py +0 -0
  245. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_types.py +0 -0
  246. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_auto_stop_gating.py +0 -0
  247. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_catalogs.py +0 -0
  248. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cgroup_probe.py +0 -0
  249. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_claude_error_detector.py +0 -0
  250. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_claude_foreign_schema.py +0 -0
  251. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli.py +0 -0
  252. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli_common.py +0 -0
  253. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli_init_install.py +0 -0
  254. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli_main_boundary.py +0 -0
  255. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli_monitor_http.py +0 -0
  256. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  257. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli_upgrade.py +0 -0
  258. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_codewhale_code_precedence.py +0 -0
  259. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_codewhale_plugin.py +0 -0
  260. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_coerce_int_poison.py +0 -0
  261. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_fresh_eyes.py +0 -0
  262. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_max_rounds.py +0 -0
  263. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_nested_strictness.py +0 -0
  264. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_prompt_delivery.py +0 -0
  265. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_schedule.py +0 -0
  266. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_stop_file.py +0 -0
  267. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_strictness.py +0 -0
  268. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  269. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_transient_error_action.py +0 -0
  270. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_context_store.py +0 -0
  271. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_control_char_config.py +0 -0
  272. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_default_dirty_handler.py +0 -0
  273. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_defenses.py +0 -0
  274. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_detect_hung_latch.py +0 -0
  275. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_detector_isolation.py +0 -0
  276. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_detector_protocol.py +0 -0
  277. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_dirty_handlers.py +0 -0
  278. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_docgen.py +0 -0
  279. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_doctor_cmd.py +0 -0
  280. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_effective_throttle_view.py +0 -0
  281. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_emit_config_migrated.py +0 -0
  282. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_emit_schedule.py +0 -0
  283. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
  284. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_events_cmd.py +0 -0
  285. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_events_cmd_rollover.py +0 -0
  286. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_events_cmd_tail.py +0 -0
  287. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_events_jsonl_decode.py +0 -0
  288. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_example_dirty_handler.py +0 -0
  289. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_example_plugin.py +0 -0
  290. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_exec_prefix.py +0 -0
  291. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_exit_cause.py +0 -0
  292. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_exit_classifier.py +0 -0
  293. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  294. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_gemini_plugin.py +0 -0
  295. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_git_timeout.py +0 -0
  296. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_hook_failure_isolation.py +0 -0
  297. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_hook_redact_order.py +0 -0
  298. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_hooks.py +0 -0
  299. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_http_progress.py +0 -0
  300. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_http_progress_epoch.py +0 -0
  301. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_init_entry_points.py +0 -0
  302. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
  303. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_kimi_plugin.py +0 -0
  304. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_lifecycle.py +0 -0
  305. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_metrics.py +0 -0
  306. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_metrics_cgroup_usage.py +0 -0
  307. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_metrics_swap.py +0 -0
  308. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_migrate_cmd.py +0 -0
  309. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_migrations.py +0 -0
  310. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_assembly.py +0 -0
  311. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_cmd_severity.py +0 -0
  312. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_dedup_identity.py +0 -0
  313. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_dedup_rearm.py +0 -0
  314. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  315. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  316. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  317. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_detectors.py +0 -0
  318. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_event_tail.py +0 -0
  319. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_loop.py +0 -0
  320. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_on_alert.py +0 -0
  321. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_remote.py +0 -0
  322. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
  323. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_schedule_pause.py +0 -0
  324. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_network_blip_signal_guard.py +0 -0
  325. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_open_round_log.py +0 -0
  326. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_orphan_chain_single_pass.py +0 -0
  327. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_outer_round_ceiling.py +0 -0
  328. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_peek_argparse.py +0 -0
  329. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_peek_project_isolation.py +0 -0
  330. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_peek_schedule.py +0 -0
  331. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_peek_select.py +0 -0
  332. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_phase_agent_launch.py +0 -0
  333. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_phase_profiles.py +0 -0
  334. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_phase_select.py +0 -0
  335. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_pi_plugin.py +0 -0
  336. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_plugin_constants.py +0 -0
  337. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_plugin_manifest.py +0 -0
  338. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_plugin_scan_parity.py +0 -0
  339. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_presets.py +0 -0
  340. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_primary_prompt_file_home.py +0 -0
  341. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_prompt_loader.py +0 -0
  342. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_read_json_hardening.py +0 -0
  343. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_redact.py +0 -0
  344. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_resolve.py +0 -0
  345. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_round_cmd.py +0 -0
  346. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_round_cmd_sigterm.py +0 -0
  347. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_round_log_helpers.py +0 -0
  348. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_round_outcome.py +0 -0
  349. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_round_outcome_agent_axis.py +0 -0
  350. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_round_view.py +0 -0
  351. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_runner.py +0 -0
  352. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_runner_sidecar_ordering.py +0 -0
  353. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_runner_throttle.py +0 -0
  354. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_scaffold.py +0 -0
  355. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_schedule.py +0 -0
  356. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_cmd_bounded.py +0 -0
  357. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_config_broken.py +0 -0
  358. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_crash_loop.py +0 -0
  359. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_mem_gate.py +0 -0
  360. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_phase_schedule.py +0 -0
  361. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_policy.py +0 -0
  362. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_round.py +0 -0
  363. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_round_log.py +0 -0
  364. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_round_oom_killed.py +0 -0
  365. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_round_terminate.py +0 -0
  366. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_schedule_gate.py +0 -0
  367. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_sentinel.py +0 -0
  368. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_startup_hooks.py +0 -0
  369. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_service_mode.py +0 -0
  370. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_service_unit.py +0 -0
  371. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_startup_battery_exit.py +0 -0
  372. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_startup_check.py +0 -0
  373. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_substrate.py +0 -0
  374. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
  375. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_throttle_back_off_ladder.py +0 -0
  376. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_throttle_epoch_bound.py +0 -0
  377. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_throttle_plugin_classification.py +0 -0
  378. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_upgrade_migrate.py +0 -0
  379. {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_vcs_state.py +0 -0
@@ -77,6 +77,7 @@ service
77
77
  recent_events
78
78
  recent_hook_failures
79
79
  recent_blips
80
+ recent_cgroup_growth_warnings
80
81
  schedule
81
82
 
82
83
  # agent_runner.api_types.RateLimitState
@@ -248,6 +249,7 @@ full_avg10_critical
248
249
  some_avg10_warning
249
250
  critical_consecutive_samples
250
251
  in_round_terminate
252
+ cgroup_growth_rate_warning_mb_per_min
251
253
 
252
254
  # agent_runner.context_store.OrphanState
253
255
  round_num
@@ -5,6 +5,12 @@ 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.1] - 2026-09-14
9
+
10
+ ### Added
11
+ - `monitor.host_health.pressure.cgroup_growth_rate_warning_mb_per_min` (default 512.0): a mid-round memory-growth-rate detector gives an earlier growth signal than the existing PSI/MemFree floors catch, ahead of a runaway climb reaching them. Observability only — no round action.
12
+ - `cgroup_growth_rate_warning` event, surfaced in `peek --json` as `recent_cgroup_growth_warnings` (schema `2.1`).
13
+
8
14
  ## [0.3.0] - 2026-09-13
9
15
 
10
16
  **The one breaking release of the 0.3 line.** Run `agent-runner migrate` before upgrading — it rewrites everything below in one pass.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.3.0
3
+ Version: 0.3.1
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
@@ -29,6 +29,7 @@ from agent_runner._emit.agent import ( # noqa: F401 — public re-export
29
29
  emit_transient_error_recovered,
30
30
  )
31
31
  from agent_runner._emit.memory import ( # noqa: F401 — public re-export
32
+ emit_cgroup_growth_rate_warning,
32
33
  emit_host_cgroup_memory_limit,
33
34
  emit_mem_pressure_deferred_to_cgroup,
34
35
  emit_round_cgroup_memory,
@@ -69,6 +70,7 @@ __all__ = [
69
70
  "emit_agent_auth_error_detected",
70
71
  "emit_agent_usage_recorded",
71
72
  "emit_anomaly_repetitive_tool",
73
+ "emit_cgroup_growth_rate_warning",
72
74
  "emit_config_broken",
73
75
  "emit_config_migrated",
74
76
  "emit_crash_loop",
@@ -238,3 +238,33 @@ def emit_mem_pressure_deferred_to_cgroup(
238
238
  from agent_runner.events import MEM_PRESSURE_DEFERRED_TO_CGROUP, emit
239
239
 
240
240
  emit(log_dir, MEM_PRESSURE_DEFERRED_TO_CGROUP, pid=pid, signal=signal, message=message)
241
+
242
+
243
+ def emit_cgroup_growth_rate_warning(
244
+ log_dir: Path,
245
+ *,
246
+ round_num: int,
247
+ rate_mb_per_min: float,
248
+ threshold_mb_per_min: float,
249
+ source: str,
250
+ context: dict,
251
+ ) -> None:
252
+ """Emit on a False->True crossing of _spawn_round's mid-round growth-rate
253
+ derivative (host_health.cgroup_growth_rate_pressure) -- observability
254
+ only, no action path. ``source`` names which per-tick reading the
255
+ derivative used: "cgroup" (a finite cgroup memory.max bound exists,
256
+ memory.current is read) or "rss_sum" (no finite bound -- the psutil
257
+ children_rss_sum_bytes tree sum is used instead). Edge-triggered: fires
258
+ once per crossing episode, not once per tick, so a sustained climb does
259
+ not spam the event stream."""
260
+ from agent_runner.events import CGROUP_GROWTH_RATE_WARNING, emit
261
+
262
+ emit(
263
+ log_dir,
264
+ CGROUP_GROWTH_RATE_WARNING,
265
+ round_num=round_num,
266
+ rate_mb_per_min=rate_mb_per_min,
267
+ threshold_mb_per_min=threshold_mb_per_min,
268
+ source=source,
269
+ context=context,
270
+ )
@@ -21,12 +21,14 @@ from agent_runner.clock import SYSTEM_CLOCK
21
21
  from agent_runner.config import load_config
22
22
  from agent_runner.events import (
23
23
  AGENT_NETWORK_BLIP,
24
+ CGROUP_GROWTH_RATE_WARNING,
24
25
  HOOK_FAILED,
25
26
  MONITOR_STARTED,
26
27
  )
27
28
 
28
29
  _RECENT_HOOK_FAILURES_LIMIT = 10
29
30
  _RECENT_BLIPS_LIMIT = 5
31
+ _RECENT_GROWTH_LIMIT = 5
30
32
  _MONITOR_SEEN_CAP = 512
31
33
  """Bound on `_monitor_loop_iter`'s dedup set — an unbounded set of alert-identity
32
34
  keys would grow forever across a long-lived monitor process; oldest-episode
@@ -80,6 +82,9 @@ def peek(
80
82
  parsed_events, HOOK_FAILED, _RECENT_HOOK_FAILURES_LIMIT
81
83
  )
82
84
  recent_blips = _recent_events_of_kind(parsed_events, AGENT_NETWORK_BLIP, _RECENT_BLIPS_LIMIT)
85
+ recent_cgroup_growth_warnings = _recent_events_of_kind(
86
+ parsed_events, CGROUP_GROWTH_RATE_WARNING, _RECENT_GROWTH_LIMIT
87
+ )
83
88
 
84
89
  from agent_runner._throttle import effective_throttle_view
85
90
 
@@ -121,6 +126,7 @@ def peek(
121
126
  recent_events=recent,
122
127
  recent_hook_failures=recent_hook_failures,
123
128
  recent_blips=recent_blips,
129
+ recent_cgroup_growth_warnings=recent_cgroup_growth_warnings,
124
130
  schedule=monitor.latest_schedule_state(parsed_events),
125
131
  )
126
132
  return state if select is None else select_path(state, select)
@@ -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.0'
22
- __version_tuple__ = version_tuple = (0, 3, 0)
21
+ __version__ = version = '0.3.1'
22
+ __version_tuple__ = version_tuple = (0, 3, 1)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -247,6 +247,26 @@ def _live_children(
247
247
  return live, ignored
248
248
 
249
249
 
250
+ def children_rss_sum_bytes(proc: subprocess.Popen) -> int | None:
251
+ """Sum RSS (bytes) across the process tree (self + all recursive children)
252
+ -- the no-finite-cgroup-bound fallback for the mid-round growth-rate
253
+ derivative (see cli/_serve_round.py). Uncapped, unlike _live_children
254
+ (a 5-child cap would undercount a wider tree); fail-open on the same
255
+ PID-race guards _live_children uses: None when the leader itself is gone
256
+ or unreadable, per-child skip (not abort) when a child vanishes mid-walk."""
257
+ try:
258
+ parent = psutil.Process(proc.pid)
259
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
260
+ return None
261
+ total = 0
262
+ for p in [parent, *parent.children(recursive=True)]:
263
+ try:
264
+ total += p.memory_info().rss
265
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
266
+ continue
267
+ return total
268
+
269
+
250
270
  # Decoupled from the 0.2s poll tick: the marker scan only needs to notice the
251
271
  # result within max_grace_after_result_s (seconds, integer), so scanning more
252
272
  # often than this buys nothing but re-read cost on a growing round log.
@@ -31,6 +31,7 @@ from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-ex
31
31
  emit_agent_auth_error_detected,
32
32
  emit_agent_usage_recorded,
33
33
  emit_anomaly_repetitive_tool,
34
+ emit_cgroup_growth_rate_warning,
34
35
  emit_config_broken,
35
36
  emit_config_migrated,
36
37
  emit_crash_loop,
@@ -114,6 +114,7 @@ class ProjectState:
114
114
  recent_events: list[dict[str, Any]] = field(default_factory=list)
115
115
  recent_hook_failures: list[dict[str, Any]] = field(default_factory=list)
116
116
  recent_blips: list[dict[str, Any]] = field(default_factory=list)
117
+ recent_cgroup_growth_warnings: list[dict[str, Any]] = field(default_factory=list)
117
118
  schedule: dict | None = None
118
119
 
119
120
 
@@ -44,8 +44,13 @@ from agent_runner._throttle import (
44
44
  pending_recovered,
45
45
  round_had_no_progress,
46
46
  )
47
- from agent_runner.agent_runtime import _kill_stray_descendants, _snapshot_stray_descendants
47
+ from agent_runner.agent_runtime import (
48
+ _kill_stray_descendants,
49
+ _snapshot_stray_descendants,
50
+ children_rss_sum_bytes,
51
+ )
48
52
  from agent_runner.api import (
53
+ emit_cgroup_growth_rate_warning,
49
54
  emit_config_broken,
50
55
  emit_crash_loop,
51
56
  emit_mem_loop,
@@ -337,7 +342,14 @@ def _spawn_round(
337
342
  run (cgroup-defer, or the off switch) would otherwise write one event
338
343
  per ~10s tick for up to a whole ``round_budget_s``. The streak still
339
344
  resets to 0 on any non-critical tick, so the cap is per streak-episode:
340
- sampling resumes from 1 the next time critical pressure recurs."""
345
+ sampling resumes from 1 the next time critical pressure recurs.
346
+
347
+ A second, independent derivative rides the SAME mem-check tick: growth
348
+ RATE (MB/min) of the round's own memory (cgroup memory.current when a
349
+ finite bound exists, else a psutil RSS-sum over the process tree),
350
+ evaluated by host_health.cgroup_growth_rate_pressure and, on a crossing,
351
+ emitting cgroup_growth_rate_warning once per episode -- observability
352
+ only, no termination action."""
341
353
  log_dir = round_log_path.parent
342
354
  with round_log_path.open("w") as f:
343
355
  proc = subprocess.Popen(
@@ -362,6 +374,9 @@ def _spawn_round(
362
374
  prev_tick_sample: dict | None = None
363
375
  critical_streak = 0
364
376
  cgroup_defer_notified = False
377
+ prev_mem_bytes: int | None = None
378
+ prev_mem_mono: float | None = None
379
+ growth_warned = False
365
380
  # cgroup pressure spine (round_cgroup_memory): baseline read
366
381
  # once at spawn start, empty ({}) when this host has no finite
367
382
  # cgroup bound -- cg_base then stays falsy and _stash_cgroup
@@ -404,6 +419,50 @@ def _spawn_round(
404
419
  cg_now = metrics.cgroup_memory_usage(bounding_cgroup=cg_bounding)
405
420
  cg_peak_current = max(cg_peak_current, cg_now.get("memory_current", 0))
406
421
  cg_peak_swap = max(cg_peak_swap, cg_now.get("memory_swap_current", 0))
422
+ cur_mem_bytes = cg_now.get("memory_current") if cg_now else None
423
+ mem_source = "cgroup"
424
+ else:
425
+ cur_mem_bytes = children_rss_sum_bytes(proc)
426
+ mem_source = "rss_sum"
427
+ now_mono = clock.monotonic()
428
+ if cur_mem_bytes is not None:
429
+ rate_mb_per_min = None
430
+ if prev_mem_bytes is not None and prev_mem_mono is not None:
431
+ elapsed_s = max(now_mono - prev_mem_mono, 0.0)
432
+ rate_mb_per_min = (
433
+ 0.0
434
+ if elapsed_s == 0
435
+ else max(
436
+ (cur_mem_bytes - prev_mem_bytes)
437
+ / 1024
438
+ / 1024
439
+ / (elapsed_s / 60),
440
+ 0.0,
441
+ )
442
+ )
443
+ growth_pressure = host_health.cgroup_growth_rate_pressure(
444
+ rate_mb_per_min, host_health_cfg
445
+ )
446
+ if growth_pressure is not None:
447
+ if not growth_warned:
448
+ growth_warned = True
449
+ emit_cgroup_growth_rate_warning(
450
+ log_dir,
451
+ round_num=round_num,
452
+ rate_mb_per_min=rate_mb_per_min,
453
+ threshold_mb_per_min=(
454
+ host_health_cfg.pressure.cgroup_growth_rate_warning_mb_per_min
455
+ ),
456
+ source=mem_source,
457
+ context=growth_pressure.context,
458
+ )
459
+ else:
460
+ growth_warned = False
461
+ prev_mem_bytes, prev_mem_mono = cur_mem_bytes, now_mono
462
+ # else: fail-open -- cur_mem_bytes unavailable this tick
463
+ # (a {}/None source read). Keep prev_mem_bytes/prev_mem_mono
464
+ # untouched so the NEXT successful tick still diffs against
465
+ # the last real reading, not a spurious reset.
407
466
  if pressure is not None and pressure.severity == "critical":
408
467
  critical_streak += 1
409
468
  if (
@@ -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.0"
22
+ PEEK_SCHEMA_VERSION = "2.1"
23
23
 
24
24
 
25
25
  def cfg_from_args(args) -> Config:
@@ -200,6 +200,10 @@ class _HostHealthPressureConfig:
200
200
  critical_consecutive_samples: int = 3
201
201
  # Off switch for the mid-round hard floor's termination action only.
202
202
  in_round_terminate: bool = True
203
+ # Mid-round growth-RATE floor (MB/min) -- observability only in this
204
+ # release: no critical/terminate action exists for it yet, see
205
+ # host_health.cgroup_growth_rate_pressure.
206
+ cgroup_growth_rate_warning_mb_per_min: float = 512.0
203
207
 
204
208
 
205
209
  @dataclass(frozen=True)
@@ -57,6 +57,7 @@ from agent_runner.config.validators import (
57
57
  _require_bool,
58
58
  _require_non_negative_int,
59
59
  _require_pct,
60
+ _require_positive_float,
60
61
  _require_positive_int,
61
62
  _require_positive_pct,
62
63
  _require_str_list,
@@ -515,6 +516,10 @@ def _parse_monitor(monitor_d: dict) -> MonitorConfig:
515
516
  pres_d.get("in_round_terminate", True),
516
517
  field="monitor.host_health.pressure.in_round_terminate",
517
518
  ),
519
+ cgroup_growth_rate_warning_mb_per_min=_require_positive_float(
520
+ pres_d.get("cgroup_growth_rate_warning_mb_per_min", 512.0),
521
+ field="monitor.host_health.pressure.cgroup_growth_rate_warning_mb_per_min",
522
+ ),
518
523
  ),
519
524
  )
520
525
  monitor = MonitorConfig(
@@ -166,6 +166,19 @@ def _require_positive_pct(value: Any, *, field: str) -> float:
166
166
  return v
167
167
 
168
168
 
169
+ def _require_positive_float(value: Any, *, field: str) -> float:
170
+ """Validate a TOML value is a positive float (int accepted too -- TOML's
171
+ bare integer form). Sibling of ``_require_positive_pct`` for a raw-unit
172
+ threshold (e.g. MB/min) that has no natural [0, 100] percent ceiling.
173
+ Rejects bool (subclass of int, would silently coerce ``true`` -> 1.0)."""
174
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
175
+ raise ConfigError(f"{field}: must be a number, got {type(value).__name__} ({value!r})")
176
+ v = float(value)
177
+ if not v > 0.0:
178
+ raise ConfigError(f"{field}: must be positive, got {v}")
179
+ return v
180
+
181
+
169
182
  def _validate_regex_list(value: Any, *, field: str) -> list[str]:
170
183
  """Validate a list of regex pattern strings (each must compile). Returns the
171
184
  raw strings unchanged; callers compile when they need ``re.Pattern`` objects."""
@@ -36,6 +36,7 @@ ANOMALY_REPETITIVE_TOOL = "anomaly_repetitive_tool"
36
36
  AGENT_NETWORK_BLIP = "agent_network_blip"
37
37
  AGENT_SPAWN = "agent_spawn"
38
38
  AGENT_USAGE_RECORDED = "agent_usage_recorded"
39
+ CGROUP_GROWTH_RATE_WARNING = "cgroup_growth_rate_warning"
39
40
  CONFIG_BROKEN = "config_broken"
40
41
  CONFIG_MIGRATED = "config_migrated"
41
42
  CRASH_LOOP = "crash_loop"
@@ -183,3 +183,25 @@ def configured_gate_inert(sample: dict[str, Any], prev_sample: dict[str, Any], c
183
183
  return False # the gate would fire (or we can't evaluate it) -- not inert
184
184
  pressure = memory_pressure(sample, prev_sample, cfg)
185
185
  return pressure is not None and pressure.signal in ("psi", "swap_out_rate")
186
+
187
+
188
+ def cgroup_growth_rate_pressure(rate_mb_per_min: float | None, cfg: Any) -> Pressure | None:
189
+ """WARNING when the round's memory-growth rate (MB/min) meets/exceeds the
190
+ configured warning floor. Independent of the memory_pressure ladder above
191
+ (NOT a 5th fall-through tier -- PSI's early return there would mask an
192
+ otherwise-live growth-rate signal). Observability only -- never returns
193
+ "critical" in this release; a growth-driven termination path needs
194
+ calibration on a constrained host before it can safely exist.
195
+ ``rate_mb_per_min`` is None when no prior-tick sample pair is available
196
+ yet (the first tick of a round) -- returns None (no signal), not warning."""
197
+ if rate_mb_per_min is None:
198
+ return None
199
+ warn = cfg.pressure.cgroup_growth_rate_warning_mb_per_min
200
+ if rate_mb_per_min >= warn:
201
+ return Pressure(
202
+ severity="warning",
203
+ signal="cgroup_growth_rate",
204
+ message=f"memory growing {rate_mb_per_min:.0f} MB/min (>= {warn:.0f})",
205
+ context={"rate_mb_per_min": rate_mb_per_min, "threshold_mb_per_min": warn},
206
+ )
207
+ return None
@@ -211,6 +211,7 @@ See `docs/plugins.md` for the `DirtyHandler` protocol and override recipe.
211
211
  - `agent_spawn`
212
212
  - `agent_usage_recorded`
213
213
  - `anomaly_repetitive_tool`
214
+ - `cgroup_growth_rate_warning`
214
215
  - `config_broken`
215
216
  - `config_migrated`
216
217
  - `crash_loop`
@@ -81,7 +81,7 @@ running with newly-set `dirty_action = "auto_commit"` is undefined).
81
81
  | `remote_failure_tolerance_s` | `int` | 90 |
82
82
  | `anomaly_repetitive_window` | `int` | 0 |
83
83
  | `anomaly_repetitive_threshold` | `int` | 0 |
84
- | `host_health` | `MonitorHostHealthConfig` | MonitorHostHealthConfig(disk=_HostHealthDiskConfig(warning_pct=90.0, critical_pct=95.0), memory=_HostHealthMemoryConfig(avail_min_mb=200, free_low_mb=16, swap_out_noise_floor_mb=32), pressure=_HostHealthPressureConfig(full_avg10_critical=60.0, some_avg10_warning=5.0, critical_consecutive_samples=3, in_round_terminate=True)) |
84
+ | `host_health` | `MonitorHostHealthConfig` | MonitorHostHealthConfig(disk=_HostHealthDiskConfig(warning_pct=90.0, critical_pct=95.0), memory=_HostHealthMemoryConfig(avail_min_mb=200, free_low_mb=16, swap_out_noise_floor_mb=32), pressure=_HostHealthPressureConfig(full_avg10_critical=60.0, some_avg10_warning=5.0, critical_consecutive_samples=3, in_round_terminate=True, cgroup_growth_rate_warning_mb_per_min=512.0)) |
85
85
  | `round_progress_interval_s` | `int` | 0 |
86
86
  | `supervisor_stale_threshold_s` | `int \| None` | None |
87
87
 
@@ -91,7 +91,7 @@ running with newly-set `dirty_action = "auto_commit"` is undefined).
91
91
  |---|---|---|
92
92
  | `disk` | `_HostHealthDiskConfig` | _HostHealthDiskConfig(warning_pct=90.0, critical_pct=95.0) |
93
93
  | `memory` | `_HostHealthMemoryConfig` | _HostHealthMemoryConfig(avail_min_mb=200, free_low_mb=16, swap_out_noise_floor_mb=32) |
94
- | `pressure` | `_HostHealthPressureConfig` | _HostHealthPressureConfig(full_avg10_critical=60.0, some_avg10_warning=5.0, critical_consecutive_samples=3, in_round_terminate=True) |
94
+ | `pressure` | `_HostHealthPressureConfig` | _HostHealthPressureConfig(full_avg10_critical=60.0, some_avg10_warning=5.0, critical_consecutive_samples=3, in_round_terminate=True, cgroup_growth_rate_warning_mb_per_min=512.0) |
95
95
 
96
96
  #### `[monitor.host_health.disk]`
97
97
 
@@ -116,6 +116,7 @@ running with newly-set `dirty_action = "auto_commit"` is undefined).
116
116
  | `some_avg10_warning` | `float` | 5.0 |
117
117
  | `critical_consecutive_samples` | `int` | 3 |
118
118
  | `in_round_terminate` | `bool` | True |
119
+ | `cgroup_growth_rate_warning_mb_per_min` | `float` | 512.0 |
119
120
 
120
121
  ### `[phases]`
121
122
 
@@ -287,6 +287,17 @@ neutrality argument above (same objects, same import-graph shape, no new
287
287
  per-round allocation), no regression is expected on that host either, but
288
288
  that claim is unverified pending whoever runs the next prerelease build there.
289
289
 
290
+ ## 0.3.0 → 0.3.1
291
+
292
+ Not independently re-measured this release. No import-time work changed:
293
+ `children_rss_sum_bytes` and `cgroup_growth_rate_pressure` are plain
294
+ functions added beside existing ones in already-imported modules, not new
295
+ imports. The mid-round tick loop's existing ~10s cadence gains a few extra
296
+ local-variable holders and, at most, one more psutil (or reused cgroup)
297
+ read on that SAME already-scheduled tick — no new per-round allocation
298
+ growth pattern. `tests/invariants/test_round_alloc_growth.py` stays green
299
+ unmodified.
300
+
290
301
  ## Methodology
291
302
 
292
303
  Machine: macOS 26.6.2, arm64, 16 KB pages. Python 3.11.3 (CPython, pyenv),
@@ -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.0",
191
+ "schema_version": "2.1",
192
192
  "plugins": {
193
193
  "event_kinds": [...],
194
194
  "context_enrichers": ["current_branch"],
@@ -537,7 +537,7 @@ detector. Other plugin detectors and all builtins still run normally.
537
537
 
538
538
  ```json
539
539
  {
540
- "schema_version": "2.0",
540
+ "schema_version": "2.1",
541
541
  "plugins": {
542
542
  "event_kinds": [...],
543
543
  "context_enrichers": [...],
@@ -221,6 +221,7 @@ EXPECTED_API_SURFACE = {
221
221
  "emit_agent_auth_error_detected",
222
222
  "emit_agent_usage_recorded",
223
223
  "emit_anomaly_repetitive_tool",
224
+ "emit_cgroup_growth_rate_warning",
224
225
  "emit_config_broken",
225
226
  "emit_config_migrated",
226
227
  "emit_crash_loop",
@@ -93,7 +93,10 @@ ALLOWED_SERVE_ROUND_IMPORTS = {
93
93
  }
94
94
  ALLOWED_SERVE_ROUND_FROM = [
95
95
  ("agent_runner", {"host_health", "metrics"}),
96
- ("agent_runner.agent_runtime", {"_kill_stray_descendants", "_snapshot_stray_descendants"}),
96
+ (
97
+ "agent_runner.agent_runtime",
98
+ {"_kill_stray_descendants", "_snapshot_stray_descendants", "children_rss_sum_bytes"},
99
+ ),
97
100
  (
98
101
  "agent_runner._serve_policy",
99
102
  {
@@ -118,6 +121,7 @@ ALLOWED_SERVE_ROUND_FROM = [
118
121
  (
119
122
  "agent_runner.api",
120
123
  {
124
+ "emit_cgroup_growth_rate_warning",
121
125
  "emit_config_broken",
122
126
  "emit_crash_loop",
123
127
  "emit_mem_loop",
@@ -72,7 +72,7 @@ def test_peek_json_should_include_schema_version_when_emitted(tmp_path: Path) ->
72
72
  f"schema_version regressed: got {payload['schema_version']!r}, "
73
73
  f"expected >= {PEEK_SCHEMA_VERSION!r}"
74
74
  )
75
- assert payload["schema_version"] == "2.0"
75
+ assert payload["schema_version"] == "2.1"
76
76
  assert "plugins" in payload
77
77
  assert isinstance(payload["plugins"], dict)
78
78
  assert "event_kinds" in payload["plugins"]
@@ -9,6 +9,7 @@ import subprocess
9
9
  import sys
10
10
  from pathlib import Path
11
11
 
12
+ import psutil
12
13
  import pytest
13
14
 
14
15
  from agent_runner import agent_runtime
@@ -284,6 +285,68 @@ def test_live_children_should_return_empty_when_process_already_exited():
284
285
  assert _live_children(p) == ([], []) # NoSuchProcess swallowed
285
286
 
286
287
 
288
+ def test_children_rss_sum_bytes_should_return_none_when_process_already_exited():
289
+ from agent_runner.agent_runtime import children_rss_sum_bytes
290
+
291
+ p = subprocess.Popen(["true"])
292
+ p.wait()
293
+
294
+ assert children_rss_sum_bytes(p) is None # NoSuchProcess swallowed, mirrors _live_children
295
+
296
+
297
+ def test_children_rss_sum_bytes_should_sum_parent_and_child_rss_when_tree_alive():
298
+ from agent_runner.agent_runtime import children_rss_sum_bytes
299
+
300
+ p = subprocess.Popen(["bash", "-c", "sleep 30 & wait"], start_new_session=True)
301
+ box: dict = {}
302
+
303
+ def _ready() -> bool:
304
+ box["total"] = children_rss_sum_bytes(p)
305
+ return box["total"] is not None and box["total"] > 0
306
+
307
+ try:
308
+ assert poll_until(_ready, timeout_s=5.0), "rss sum never became positive"
309
+ assert box["total"] > 0
310
+ finally:
311
+ os.killpg(p.pid, signal.SIGKILL)
312
+ p.wait()
313
+
314
+
315
+ def test_children_rss_sum_bytes_should_skip_a_vanished_child_when_others_remain(monkeypatch):
316
+ """A child that raises NoSuchProcess mid-walk (PID race) must not abort the
317
+ whole sum -- the remaining process(es) still contribute."""
318
+ from agent_runner.agent_runtime import children_rss_sum_bytes
319
+
320
+ p = subprocess.Popen(["sleep", "3"], start_new_session=True)
321
+
322
+ class _FailingChild:
323
+ def memory_info(self):
324
+ raise psutil.NoSuchProcess(pid=99999)
325
+
326
+ real_process = psutil.Process
327
+
328
+ class _FakeParent:
329
+ def __init__(self, pid):
330
+ self._real = real_process(pid)
331
+
332
+ def memory_info(self):
333
+ return self._real.memory_info()
334
+
335
+ def children(self, recursive=True):
336
+ return [_FailingChild()]
337
+
338
+ try:
339
+ monkeypatch.setattr(agent_runtime.psutil, "Process", lambda pid: _FakeParent(pid))
340
+
341
+ total = children_rss_sum_bytes(p)
342
+
343
+ assert total is not None
344
+ assert total > 0 # parent's own RSS still counted
345
+ finally:
346
+ os.killpg(p.pid, signal.SIGKILL)
347
+ p.wait()
348
+
349
+
287
350
  def test_run_should_extend_grace_when_child_still_live_after_result(tmp_path, monkeypatch):
288
351
  """Agent emits result then backgrounds a long child -> live worker -> NOT
289
352
  grace-killed; round_budget_s (wall) reaps it instead; extended fired once.
@@ -392,6 +392,39 @@ def test_peek_should_populate_recent_blips_when_events_contain_blips(
392
392
  assert rounds == [2, 3, 4, 5, 6]
393
393
 
394
394
 
395
+ def test_peek_should_populate_recent_cgroup_growth_warnings_when_events_contain_them(
396
+ tmp_git_repo: Path,
397
+ monkeypatch: pytest.MonkeyPatch,
398
+ ) -> None:
399
+ monkeypatch.setenv("HOME", str(tmp_git_repo))
400
+ api.init(tmp_git_repo, force=False, commit=False)
401
+ _seed_logs(tmp_git_repo)
402
+ cfg = load_config(tmp_git_repo / "agent-runner.toml")
403
+ log_dir = cfg.runtime.log_dir
404
+ warning_lines = [
405
+ json.dumps(
406
+ {
407
+ "ts": f"2026-05-12T11:00:0{i}.000Z",
408
+ "event": "cgroup_growth_rate_warning",
409
+ "round_num": i,
410
+ "rate_mb_per_min": 900.0,
411
+ "threshold_mb_per_min": 512.0,
412
+ "source": "cgroup",
413
+ "context": {},
414
+ }
415
+ )
416
+ for i in range(7)
417
+ ]
418
+ with (log_dir / "events-2026-05.jsonl").open("a", encoding="utf-8") as f:
419
+ f.write("\n".join(warning_lines) + "\n")
420
+
421
+ state = api.peek(tmp_git_repo)
422
+
423
+ assert len(state.recent_cgroup_growth_warnings) == 5, "default limit is 5, most-recent first"
424
+ rounds = [w["round_num"] for w in state.recent_cgroup_growth_warnings]
425
+ assert rounds == [2, 3, 4, 5, 6]
426
+
427
+
395
428
  def test_narrate_events_should_yield_formatted_lines_when_events_seeded(
396
429
  tmp_path: Path,
397
430
  ) -> None:
@@ -2072,6 +2072,44 @@ def test_in_round_terminate_should_reject_non_bool_value(tmp_path: Path, bad: st
2072
2072
  load_config(toml)
2073
2073
 
2074
2074
 
2075
+ @pytest.mark.parametrize("bad", ["0", "-1", '"x"', "true", "nan"])
2076
+ def test_cgroup_growth_rate_warning_mb_per_min_should_reject_non_positive_or_bool_values(
2077
+ tmp_path: Path, bad: str
2078
+ ) -> None:
2079
+
2080
+ toml = _write_toml(
2081
+ tmp_path, _HOST_HEALTH_PRESSURE_BASE + f"cgroup_growth_rate_warning_mb_per_min = {bad}\n"
2082
+ )
2083
+
2084
+ with pytest.raises(
2085
+ ValueError, match="monitor.host_health.pressure.cgroup_growth_rate_warning_mb_per_min"
2086
+ ):
2087
+ load_config(toml)
2088
+
2089
+
2090
+ @pytest.mark.parametrize(("literal", "expected"), [("256", 256.0), ("128.5", 128.5)])
2091
+ def test_cgroup_growth_rate_warning_mb_per_min_should_accept_int_or_float_when_positive(
2092
+ tmp_path: Path, literal: str, expected: float
2093
+ ) -> None:
2094
+
2095
+ toml = _write_toml(
2096
+ tmp_path,
2097
+ _HOST_HEALTH_PRESSURE_BASE + f"cgroup_growth_rate_warning_mb_per_min = {literal}\n",
2098
+ )
2099
+
2100
+ cfg = load_config(toml)
2101
+
2102
+ assert cfg.monitor.host_health.pressure.cgroup_growth_rate_warning_mb_per_min == expected
2103
+
2104
+
2105
+ def test_cgroup_growth_rate_warning_mb_per_min_should_default_to_512() -> None:
2106
+ from agent_runner.config import MonitorHostHealthConfig
2107
+
2108
+ cfg = MonitorHostHealthConfig()
2109
+
2110
+ assert cfg.pressure.cgroup_growth_rate_warning_mb_per_min == 512.0
2111
+
2112
+
2075
2113
  _INJECT_CONTEXT_BASE = """\
2076
2114
  [agent]
2077
2115
  command = ["true"]