cli-agent-runner 0.3.10__tar.gz → 0.3.11__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 (399) hide show
  1. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/.vulture-whitelist.py +3 -0
  2. cli_agent_runner-0.3.11/BACKLOG.md +195 -0
  3. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/CHANGELOG.md +14 -0
  4. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/PKG-INFO +5 -5
  5. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/README.md +4 -4
  6. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_monitor_detectors.py +88 -1
  7. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_monitor_registry.py +1 -0
  8. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_version.py +2 -2
  9. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/config/models.py +7 -0
  10. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/config/parsers.py +12 -0
  11. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/events.py +65 -13
  12. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/metrics.py +5 -0
  13. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/monitor.py +12 -2
  14. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/architecture.md +4 -3
  15. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/commands.md +2 -2
  16. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/configuration.md +5 -2
  17. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/memory-efficiency.md +74 -0
  18. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/migrations/0.3.md +41 -15
  19. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/contract/test_public_api_surface.py +1 -0
  20. cli_agent_runner-0.3.11/tests/e2e/conftest.py +376 -0
  21. cli_agent_runner-0.3.11/tests/e2e/test_e2e_pre_oom.py +282 -0
  22. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_spawn_round_mem_floor.py +149 -0
  23. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_architecture.py +2 -2
  24. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_events.py +57 -1
  25. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_host_health.py +93 -0
  26. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_metrics.py +32 -0
  27. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_detectors.py +61 -1
  28. cli_agent_runner-0.3.10/BACKLOG.md +0 -43
  29. cli_agent_runner-0.3.10/tests/e2e/conftest.py +0 -162
  30. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/.codecov.yml +0 -0
  31. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  32. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  33. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  34. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  35. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/.github/dependabot.yml +0 -0
  36. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/.github/workflows/ci.yml +0 -0
  37. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/.github/workflows/release.yml +0 -0
  38. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/.gitignore +0 -0
  39. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/CODE_OF_CONDUCT.md +0 -0
  40. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/CONTRIBUTING.md +0 -0
  41. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/LICENSE +0 -0
  42. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/README.zh.md +0 -0
  43. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/SECURITY.md +0 -0
  44. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/__init__.py +0 -0
  45. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_docgen.py +0 -0
  46. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_emit/__init__.py +0 -0
  47. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_emit/agent.py +0 -0
  48. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_emit/memory.py +0 -0
  49. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_emit/rounds.py +0 -0
  50. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_emit/serve.py +0 -0
  51. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_install.py +0 -0
  52. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_lifecycle.py +0 -0
  53. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_monitor_state.py +0 -0
  54. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_notify.py +0 -0
  55. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_observe.py +0 -0
  56. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_plugin_manifest.py +0 -0
  57. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_plugin_scan.py +0 -0
  58. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_procwait.py +0 -0
  59. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_redact.py +0 -0
  60. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_registry.py +0 -0
  61. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_resolve.py +0 -0
  62. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_round_outcome.py +0 -0
  63. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_round_support.py +0 -0
  64. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_serve_policy.py +0 -0
  65. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_substrate.py +0 -0
  66. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/_throttle.py +0 -0
  67. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/agent_runtime.py +0 -0
  68. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/api.py +0 -0
  69. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/api_types.py +0 -0
  70. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/builtin_plugins/__init__.py +0 -0
  71. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/builtin_plugins/_constants.py +0 -0
  72. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  73. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/builtin_plugins/codewhale.py +0 -0
  74. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/builtin_plugins/gemini.py +0 -0
  75. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/builtin_plugins/kimi.py +0 -0
  76. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/builtin_plugins/pi.py +0 -0
  77. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/__init__.py +0 -0
  78. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/__main__.py +0 -0
  79. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/_serve_cgroup.py +0 -0
  80. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/_serve_round.py +0 -0
  81. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/common.py +0 -0
  82. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/doctor_cmd.py +0 -0
  83. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/events_cmd.py +0 -0
  84. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/init_cmd.py +0 -0
  85. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/install_cmd.py +0 -0
  86. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/migrate_cmd.py +0 -0
  87. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/monitor_cmd.py +0 -0
  88. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/peek_cmd.py +0 -0
  89. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/round_cmd.py +0 -0
  90. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/serve_cmd.py +0 -0
  91. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/service_cmd.py +0 -0
  92. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/cli/upgrade_cmd.py +0 -0
  93. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/clock.py +0 -0
  94. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/config/__init__.py +0 -0
  95. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/config/errors.py +0 -0
  96. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/config/loader.py +0 -0
  97. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/config/validators.py +0 -0
  98. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/context_store.py +0 -0
  99. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/defenses.py +0 -0
  100. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/event_log.py +0 -0
  101. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/hooks.py +0 -0
  102. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/host_health.py +0 -0
  103. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/http_progress.py +0 -0
  104. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/lifecycle.py +0 -0
  105. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/migrations.py +0 -0
  106. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/phase_select.py +0 -0
  107. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/presets/__init__.py +0 -0
  108. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/presets/aider.toml +0 -0
  109. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/presets/claude.toml +0 -0
  110. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/presets/codewhale.toml +0 -0
  111. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/presets/gemini.toml +0 -0
  112. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/presets/kimi.toml +0 -0
  113. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/presets/pi.toml +0 -0
  114. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/prompt_loader.py +0 -0
  115. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/remote_relay.py +0 -0
  116. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/round_log.py +0 -0
  117. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/round_view.py +0 -0
  118. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/runner.py +0 -0
  119. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/scaffold.py +0 -0
  120. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/schedule.py +0 -0
  121. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/service_unit.py +0 -0
  122. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/startup_check.py +0 -0
  123. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/agent_runner/vcs_state.py +0 -0
  124. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/build.sh +0 -0
  125. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/deploy/Dockerfile +0 -0
  126. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/deploy/docker-compose.yml +0 -0
  127. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/README.md +0 -0
  128. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/events.md +0 -0
  129. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/long-running-agents.md +0 -0
  130. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/migrations/0.2.md +0 -0
  131. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/plugins.md +0 -0
  132. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/quickstart.md +0 -0
  133. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/recipes/aider.md +0 -0
  134. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/recipes/codewhale.md +0 -0
  135. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/recipes/container-pi.md +0 -0
  136. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/recipes/deploy-all-in-one.md +0 -0
  137. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/recipes/kimi.md +0 -0
  138. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/recipes/pi.md +0 -0
  139. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/runbook.md +0 -0
  140. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/docs/thesis.md +0 -0
  141. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/pyproject.toml +0 -0
  142. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/__init__.py +0 -0
  143. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/_clock.py +0 -0
  144. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/_test_helpers.py +0 -0
  145. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/conftest.py +0 -0
  146. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/contract/__init__.py +0 -0
  147. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/e2e/__init__.py +0 -0
  148. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  149. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/e2e/test_e2e_install_systemd.py +0 -0
  150. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  151. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/e2e/test_e2e_pi_resume.py +0 -0
  152. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  153. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  154. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  155. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  156. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/generate_vulture_whitelist.py +0 -0
  157. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/__init__.py +0 -0
  158. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_auto_stop_e2e.py +0 -0
  159. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_bounded_run.py +0 -0
  160. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_cooperative_grace_property.py +0 -0
  161. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_cooperative_stop_signal.py +0 -0
  162. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_fresh_eyes_signal.py +0 -0
  163. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_grace_kill_emission.py +0 -0
  164. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_install_dry_run.py +0 -0
  165. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_monitor_seeded.py +0 -0
  166. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_plugin_real_flow.py +0 -0
  167. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  168. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_reap_property.py +0 -0
  169. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  170. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_scaffold_presets.py +0 -0
  171. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_serve_loop.py +0 -0
  172. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_spawn_round_wedged.py +0 -0
  173. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_substrate_fingerprint.py +0 -0
  174. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/integration/test_transient_error_backoff.py +0 -0
  175. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/__init__.py +0 -0
  176. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/_docs.py +0 -0
  177. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/_event_scan.py +0 -0
  178. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_atomic_write_enforced.py +0 -0
  179. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_authored_facts_declared.py +0 -0
  180. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  181. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_bdd_test_conventions.py +0 -0
  182. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_brake_defaults_off.py +0 -0
  183. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_catalogs.py +0 -0
  184. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_classification_ssot.py +0 -0
  185. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_config_error_consistency.py +0 -0
  186. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_config_value_set_ssot.py +0 -0
  187. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_container_stop_bound.py +0 -0
  188. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
  189. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  190. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
  191. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_doc_cli_claims.py +0 -0
  192. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_doc_operator_surface.py +0 -0
  193. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
  194. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  195. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_doc_usage_event_fields.py +0 -0
  196. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_docs_generated.py +0 -0
  197. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_docs_index_complete.py +0 -0
  198. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_entry_points_resolve.py +0 -0
  199. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_event_int_coercion.py +0 -0
  200. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_event_kind_registry.py +0 -0
  201. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_event_kinds_ssot.py +0 -0
  202. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_events_doc_contract.py +0 -0
  203. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
  204. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_hard_wall_signal.py +0 -0
  205. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_hook_contract_docs.py +0 -0
  206. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_import_footprint.py +0 -0
  207. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_layer_2_loop_size.py +0 -0
  208. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_migrations_registry_ssot.py +0 -0
  209. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_module_boundaries.py +0 -0
  210. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_module_sizes.py +0 -0
  211. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_monitor_self_exclusion.py +0 -0
  212. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_no_ai_signatures.py +0 -0
  213. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_no_asyncio_select.py +0 -0
  214. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_no_direct_inspect_import.py +0 -0
  215. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_no_internal_refs.py +0 -0
  216. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_no_price_logic.py +0 -0
  217. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  218. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_no_raw_time.py +0 -0
  219. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_no_vacuous_invariants.py +0 -0
  220. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_no_version_refs.py +0 -0
  221. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_peek_schema_version.py +0 -0
  222. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_phase_select_stateless.py +0 -0
  223. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_plugins_config_stable.py +0 -0
  224. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_procwait_shield.py +0 -0
  225. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  226. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  227. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_round_alloc_growth.py +0 -0
  228. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_round_log_decode_boundary.py +0 -0
  229. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_round_num_single_source.py +0 -0
  230. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_round_result_stable.py +0 -0
  231. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  232. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  233. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_throttle_stateless_imports.py +0 -0
  234. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_timeout_budget_invariant.py +0 -0
  235. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_upstream_schema_canary.py +0 -0
  236. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  237. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/linux/__init__.py +0 -0
  238. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/linux/test_memory_high_nudge_e2e.py +0 -0
  239. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/literate/__init__.py +0 -0
  240. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/literate/parser.py +0 -0
  241. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/literate/test_parser.py +0 -0
  242. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/literate/test_quickstart.py +0 -0
  243. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/__init__.py +0 -0
  244. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_agent_runtime.py +0 -0
  245. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_agent_runtime_container.py +0 -0
  246. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_agent_runtime_grace.py +0 -0
  247. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
  248. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_agent_runtime_progress.py +0 -0
  249. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_agent_runtime_reap.py +0 -0
  250. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  251. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_api_assemble_prompt.py +0 -0
  252. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_api_events_stream.py +0 -0
  253. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_api_install.py +0 -0
  254. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_api_observation.py +0 -0
  255. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_api_read_round_num.py +0 -0
  256. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_api_resolve_phase.py +0 -0
  257. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_api_service.py +0 -0
  258. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_api_types.py +0 -0
  259. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_apply_reap_grace_env.py +0 -0
  260. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_apply_resume_env.py +0 -0
  261. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_auto_stop_gating.py +0 -0
  262. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_catalogs.py +0 -0
  263. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_cgroup_probe.py +0 -0
  264. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_claude_error_detector.py +0 -0
  265. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_claude_foreign_schema.py +0 -0
  266. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_cli.py +0 -0
  267. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_cli_common.py +0 -0
  268. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_cli_init_install.py +0 -0
  269. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_cli_main_boundary.py +0 -0
  270. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_cli_monitor_http.py +0 -0
  271. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  272. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_cli_upgrade.py +0 -0
  273. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_codewhale_code_precedence.py +0 -0
  274. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_codewhale_plugin.py +0 -0
  275. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_coerce_int_poison.py +0 -0
  276. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_config.py +0 -0
  277. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_config_fresh_eyes.py +0 -0
  278. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_config_max_rounds.py +0 -0
  279. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_config_nested_strictness.py +0 -0
  280. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_config_prompt_delivery.py +0 -0
  281. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_config_resume_guard.py +0 -0
  282. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_config_schedule.py +0 -0
  283. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_config_stop_file.py +0 -0
  284. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_config_strictness.py +0 -0
  285. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  286. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_config_transient_error_action.py +0 -0
  287. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_context_store.py +0 -0
  288. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_control_char_config.py +0 -0
  289. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_cooperative_signal_env.py +0 -0
  290. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_defenses.py +0 -0
  291. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_detect_hung_latch.py +0 -0
  292. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_detector_isolation.py +0 -0
  293. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_docgen.py +0 -0
  294. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_doctor_cmd.py +0 -0
  295. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_effective_throttle_view.py +0 -0
  296. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_emit_config_migrated.py +0 -0
  297. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_emit_schedule.py +0 -0
  298. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
  299. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_event_log.py +0 -0
  300. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_events_cmd.py +0 -0
  301. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_events_cmd_rollover.py +0 -0
  302. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_events_cmd_tail.py +0 -0
  303. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_events_jsonl_decode.py +0 -0
  304. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_example_plugin.py +0 -0
  305. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_exec_prefix.py +0 -0
  306. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_exit_cause.py +0 -0
  307. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_exit_classifier.py +0 -0
  308. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  309. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_gemini_plugin.py +0 -0
  310. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_git_timeout.py +0 -0
  311. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_hook_redact_order.py +0 -0
  312. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_hooks.py +0 -0
  313. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_http_progress.py +0 -0
  314. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_http_progress_epoch.py +0 -0
  315. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_init_entry_points.py +0 -0
  316. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
  317. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_kill_pgroup_shielding.py +0 -0
  318. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_kimi_plugin.py +0 -0
  319. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_lifecycle.py +0 -0
  320. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_loader_split.py +0 -0
  321. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_metrics_cgroup_usage.py +0 -0
  322. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_metrics_swap.py +0 -0
  323. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_migrate_cmd.py +0 -0
  324. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_migrations.py +0 -0
  325. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_assembly.py +0 -0
  326. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_cmd_severity.py +0 -0
  327. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_dedup_identity.py +0 -0
  328. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_dedup_rearm.py +0 -0
  329. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  330. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  331. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  332. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_event_tail.py +0 -0
  333. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_loop.py +0 -0
  334. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_on_alert.py +0 -0
  335. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_remote.py +0 -0
  336. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
  337. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_monitor_schedule_pause.py +0 -0
  338. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_network_blip_signal_guard.py +0 -0
  339. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_notify.py +0 -0
  340. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_open_round_log.py +0 -0
  341. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_orphan_chain_single_pass.py +0 -0
  342. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_outer_round_ceiling.py +0 -0
  343. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_peek_argparse.py +0 -0
  344. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_peek_project_isolation.py +0 -0
  345. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_peek_schedule.py +0 -0
  346. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_peek_select.py +0 -0
  347. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_phase_agent_launch.py +0 -0
  348. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_phase_profiles.py +0 -0
  349. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_phase_select.py +0 -0
  350. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_pi_plugin.py +0 -0
  351. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_plugin_constants.py +0 -0
  352. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_plugin_manifest.py +0 -0
  353. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_plugin_scan_parity.py +0 -0
  354. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_plugins_config_fields.py +0 -0
  355. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_presets.py +0 -0
  356. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_procwait.py +0 -0
  357. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_prompt_loader.py +0 -0
  358. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_read_json_hardening.py +0 -0
  359. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_redact.py +0 -0
  360. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_resolve.py +0 -0
  361. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_resolve_dirty_tree.py +0 -0
  362. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_round_cmd.py +0 -0
  363. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_round_cmd_sigterm.py +0 -0
  364. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_round_log_helpers.py +0 -0
  365. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_round_outcome.py +0 -0
  366. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_round_outcome_agent_axis.py +0 -0
  367. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_round_view.py +0 -0
  368. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_runner.py +0 -0
  369. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_runner_sidecar_ordering.py +0 -0
  370. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_runner_throttle.py +0 -0
  371. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_scaffold.py +0 -0
  372. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_schedule.py +0 -0
  373. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_cmd_bounded.py +0 -0
  374. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_config_broken.py +0 -0
  375. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_crash_loop.py +0 -0
  376. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_mem_gate.py +0 -0
  377. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_phase_schedule.py +0 -0
  378. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_policy.py +0 -0
  379. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_round.py +0 -0
  380. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
  381. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_round_log.py +0 -0
  382. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_round_oom_killed.py +0 -0
  383. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_round_terminate.py +0 -0
  384. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_round_wait_exit.py +0 -0
  385. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_schedule_gate.py +0 -0
  386. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_sentinel.py +0 -0
  387. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_serve_wakeup.py +0 -0
  388. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_service_mode.py +0 -0
  389. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_service_unit.py +0 -0
  390. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_startup_battery_exit.py +0 -0
  391. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_startup_check.py +0 -0
  392. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_substrate.py +0 -0
  393. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
  394. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_terminate_round_wait_exit.py +0 -0
  395. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_throttle_back_off_ladder.py +0 -0
  396. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_throttle_epoch_bound.py +0 -0
  397. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_throttle_plugin_classification.py +0 -0
  398. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_upgrade_migrate.py +0 -0
  399. {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.11}/tests/unit/test_vcs_state.py +0 -0
@@ -240,6 +240,9 @@ warning_consecutive_samples
240
240
  # agent_runner.config.models._HostHealthDiskConfig
241
241
  warning_pct
242
242
  critical_pct
243
+ growth_window_s
244
+ disk_growth_pct_per_hr_warning
245
+ inode_growth_pct_per_hr_warning
243
246
 
244
247
  # agent_runner.config.models._HostHealthMemoryConfig
245
248
  avail_min_mb
@@ -0,0 +1,195 @@
1
+ # Backlog
2
+
3
+ Known follow-ups and candidate work — real design or behavior decisions, not
4
+ defects. Each entry carries enough context to act on without re-deriving it.
5
+
6
+ Items marked **NEEDS_DESIGN** change user-visible behavior and are held for an
7
+ explicit decision — they are not shipped silently in a maintenance release.
8
+
9
+ **Ordered by priority** (highest first). The number is a rank, not a stable id.
10
+ Priority follows the sharpest differentiator: prove and deepen host-health /
11
+ cooperative pre-OOM supervision on constrained hosts first (items 1–2); the
12
+ cognitive / goal-adherence layer (item 3) builds on that same control loop;
13
+ cross-CLI resume breadth and reach are lower.
14
+
15
+ ## 1. Prove cooperative pre-OOM drain end-to-end on a constrained cgroup — NEEDS_DESIGN
16
+
17
+ The host-health mechanisms built across the 0.3.x line — the cgroup
18
+ memory-growth-rate detector (0.3.1), the `memory.high` soft-brake (0.3.7), and
19
+ the typed `cooperative_stop` signal (0.3.9) — have never been demonstrated
20
+ *together*, end-to-end, under real memory pressure on a small (~462 MB) cgroup.
21
+ The load-bearing property is: the agent is cooperatively drained **before** the
22
+ kernel OOM killer fires. It is provable with a synthetic memory-growing child
23
+ under a constrained cgroup — no live model needed. Build that harness, verify
24
+ the full detect → brake → drain loop, and pin the small-host calibration
25
+ (swap-floor + PSI thresholds) and reap-safety (PID-reuse, orphan reap) as a
26
+ durable, tested execution guarantee. Making this claim demonstrable is the
27
+ highest-value work on the list; everything below is secondary to it.
28
+
29
+ ## 2. Runaway session-footprint host-health signal — NEEDS_DESIGN
30
+
31
+ A resumed CLI session file (e.g. pi's session `.jsonl`) grows unbounded across
32
+ rounds — a disk / inode pressure surface only the supervisor can see, and one
33
+ that cross-round resume (0.3.10) amplifies. Add a host disk/inode pressure
34
+ detector as a leading indicator so the pre-OOM brake can fire earlier and more
35
+ specifically. Frame it as a *host* signal (any growing per-agent footprint),
36
+ not a single-CLI feature.
37
+
38
+ ## 3. Cognitive / goal-adherence supervision loop (observe → assess → steer → persist) — NEEDS_DESIGN
39
+
40
+ Today the supervisor watches *resource* health (memory / host) and *mechanical*
41
+ progress (no-usage, substrate fingerprint, repetitive-tool). It cannot tell when
42
+ an agent is "on a treadmill" — busy (produces output, edits files, exits 0) but
43
+ not actually advancing toward its goal. Add a second, **advisory** layer to the
44
+ same round-loop: a plugin observes each round, assesses progress toward a
45
+ declared goal, and — only when warranted — injects a short, grounded advisory
46
+ into the *next* round's context (through the existing `inject_context` path) that
47
+ the agent may heed or dismiss.
48
+
49
+ Design spine (composes as a fold over the durable event bus + `inject_context`,
50
+ **not** a new live callback — the round is a fresh subprocess, so cross-round
51
+ state is file/event-based; this re-adds a feedback seam an earlier release
52
+ removed for lack of a producer, but strictly narrower: a data-return / fold, not
53
+ arbitrary context mutation):
54
+
55
+ - A first-class `[goal]` config (the "goal command"). PRIMARY verification is
56
+ **objective**: operator-declared `goal.checks` (a command the supervisor runs
57
+ each round — exit 0 = satisfied, an optional numeric target) and milestones,
58
+ run through the reap-safe / timeout / killpg machinery so a check can never
59
+ orphan or blow the cgroup. An optional, objective-gated LLM assessor is a last
60
+ resort, never in the base install.
61
+ - A zero-cost default assessor: an objective **"treadmill signature"**
62
+ (activity-high + net-convergence-low, from signals already emitted; requires
63
+ persistence over K rounds + affirmative evidence, so false positives stay near
64
+ zero).
65
+ - **Hard invariant — the advisory / kill firewall:** the advisory path is
66
+ strictly separate from the give-up / kill decision. A possibly-wrong assessor
67
+ can steer but can NEVER influence a kill (a distinct event kind the kill path
68
+ never reads, plus an import firewall; the assessment type structurally has no
69
+ kill field). Killing stays on the conservative mechanical path.
70
+ - Steering is advisory only: a fixed schema that cannot express a command
71
+ (grounded observation + a question + confidence + a respected one-line
72
+ dismissal), edge-triggered, one concern at a time, dismiss-and-respect so it
73
+ never nags.
74
+ - CLI-agnostic: reads the agent's merged log + the goal; works around any CLI.
75
+ - Persistence rides a supervisor-owned, bounded, append-only lessons ledger
76
+ re-injected each round (subordinate to the disk host-health signal in item 2).
77
+ The supervisor never edits the agent's own memory files — it may *suggest* the
78
+ agent record a lesson.
79
+
80
+ Sequence this after items 1–2: it is a second consumer of the same
81
+ observe → assess → act control loop (mechanical actuator kills, advisory
82
+ actuator steers), and it earns its slot only once the resource edge is proven.
83
+
84
+ ## 4. SECURITY containment guidance
85
+
86
+ agent-runner is lifecycle-safety + observability, not containment — a
87
+ deliberate boundary that is currently undocumented, so it reads as a gap. Add a
88
+ SECURITY section that states the division plainly and gives the operator
89
+ concrete containment guidance: a dedicated unprivileged user, a container/VM via
90
+ `exec_prefix`, egress limits, no passwordless sudo, read-only mounts. Docs-only.
91
+
92
+ ## 5. claude cross-round resume — NEEDS_DESIGN
93
+
94
+ 0.3.10 shipped resume for pi only (its `--session-id` is idempotent —
95
+ create-or-resume). claude's `--session-id` is create-only and `--resume` is a
96
+ separate flag, so it needs a create-vs-resume state machine gated on the prior
97
+ round's outcome (a crashed round re-creates rather than `--resume`-errors), plus
98
+ an empirical check that `claude -p --resume <id>` with the prompt on stdin
99
+ actually resumes **and** runs the new turn (rather than silently no-opping).
100
+ Keep it a thin per-CLI declaration; if it forces heavy session-state ownership,
101
+ that is the signal it belongs at the CLI layer, not here.
102
+
103
+ ## 6. Surface resume in the preflight (doctor / peek) — NEEDS_DESIGN
104
+
105
+ resume is the one manifest-declared capability not shown by `doctor` / `peek`
106
+ (unlike `cooperative_stop` and the SIGTERM grace). An operator on a
107
+ resume-capable preset has no preflight way to confirm resume will engage for
108
+ their phase config — in particular the deliberate cold-start on an ambiguous
109
+ phase is silent until they notice no `session_resumed` events. Add a preflight
110
+ line (with a "resume will not engage — ambiguous phase" note). Requires a `peek`
111
+ schema bump.
112
+
113
+ ## 7. Resume for gemini / codex / kimi — NEEDS_DESIGN
114
+
115
+ Each has a different resume shape (gemini resumes by index/latest, not a chosen
116
+ id; codex uses a subcommand plus a captured id). Extending resume means widening
117
+ the manifest `resume_flag` into a small sum type with a per-variant injection at
118
+ the argv-build site — never optional fields bolted onto one struct. Ship only
119
+ the variants whose resume is verified to work end-to-end.
120
+
121
+ ## 8. Fleet fan-in view — NEEDS_DESIGN
122
+
123
+ Aggregate several supervised hosts into one place to watch, built on the
124
+ existing remote-relay primitives — the density story (many cheap / constrained
125
+ boxes under one supervisor). A larger effort; sequence it after the pre-OOM edge
126
+ (item 1) is proven, so it fans in verified hosts rather than claimed ones.
127
+
128
+ ## 9. Runtime & distribution: single-binary edge install (language) — longer-horizon, gated
129
+
130
+ Python is the right foundation for the *plugin* surface — dynamic per-CLI
131
+ plugins and the declared-capability manifest are a Python strength, and "cover
132
+ every CLI via a plugin" is a core stance; a wholesale Rust/Go rewrite would
133
+ regress exactly that. The one place another language would genuinely help is
134
+ **distribution**: a static single binary ("copy one file, run it", no
135
+ interpreter or install step) suits constrained / edge hosts better than a Python
136
+ runtime plus a dependency. Footprint and speed are *not* reasons — the
137
+ supervisor's ~24 MB RSS is negligible against a GB-scale agent, and all internal
138
+ scheduling is already event-based (run time is model-latency-bound, not
139
+ CPU-bound in our code).
140
+
141
+ Held for an explicit decision, gated on two triggers: (a) the host-health /
142
+ pre-OOM edge (item 1) is proven and stable — you do not rewrite a moving,
143
+ unproven core; and (b) a distribution benchmark shows single-binary install is
144
+ the binding constraint. Even then, try Python-native single-binary packaging
145
+ first (a self-contained bundle) before any rewrite. If a rewrite is ever
146
+ warranted, the realistic shape is a small, stable core of the host-health / reap
147
+ logic in Rust or Go with the plugin layer staying Python or a declarative
148
+ format — a hybrid whose language boundary is the hard part, not a full port.
149
+
150
+ ## 10. Per-CLI session-directory resolution
151
+
152
+ Resolve each CLI's session directory via its own environment precedence rather
153
+ than a hardcoded default (never assume `~/.pi` / `~/.claude` / etc.). Ties to
154
+ the existing work_dir-resolution work.
155
+
156
+ ## 11. Test the resume boot-guard's per-phase branch
157
+
158
+ The boot guard that rejects a static resume flag already present in a command is
159
+ tested for the base agent but not the per-phase `[phases.<name>.agent]` override
160
+ branch. The override loop is correct by construction but untested — one small
161
+ test.
162
+
163
+ ## 12. `_tail_events` / `_tail_events_jsonl` duplication
164
+
165
+ Three event-stream tailers now exist at deliberately different scopes, and they
166
+ are not accidental copies:
167
+
168
+ - `cli/events_cmd.py:_tail_events` — current-month-scoped
169
+ (`_current_month_events_file`), resets its offset to `0` on truncation.
170
+ - `_monitor_state.py:_tail_events_jsonl` — globs every `events-*.jsonl`;
171
+ re-exported through the `monitor.py` facade. It moved out of `api.py` in the
172
+ 0.3.0 api split (`_install`/`_lifecycle`/`_observe`).
173
+ - `_throttle.py:_tail_events` — the newest two monthly files
174
+ (`glob("events-*.jsonl")[-2:]`).
175
+
176
+ Collapsing them means first deciding which scope is correct for each caller — a
177
+ structural decision, not a literal swap. (The older "character-identical
178
+ kind-read loop in `_throttle.py` and `monitor.py`" sub-concern has evaporated:
179
+ `monitor.py` no longer carries that loop — it moved to `_monitor_detectors.py`
180
+ and grew its own reset-ladder logic — and `_throttle.py` iterates forward over
181
+ the tailer generator, so the only shared text left is the single line
182
+ `kind = ev.get("event")`.)
183
+
184
+ ## 13. `stash_orphan` loses the ref when the post-push listing fails
185
+
186
+ `vcs_state.stash_orphan` returns `None` in three cases; two are true no-ops. The
187
+ third — `git stash push` succeeded but the follow-up `git stash list` failed —
188
+ means the WIP *is* stashed yet callers read "nothing stashed" and report the
189
+ tree as ignored. It is already documented in-code as a KNOWN GAP and judged
190
+ effectively unreachable (a listing that fails microseconds after a push that
191
+ just succeeded in the same repo).
192
+
193
+ Deferred because closing it is a naming decision, not cleanup: no event kind
194
+ carries the meaning "stashed but ref lost" (`orphan_stash_failed` would be wrong
195
+ — the stash exists). Left as-is until that kind is designed.
@@ -5,6 +5,20 @@ 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.11] - 2026-09-17
9
+
10
+ ### Added
11
+ - Host disk/inode-growth WARNING detector: `[monitor.host_health.disk]` gains `disk_growth_pct_per_hr_warning`/`inode_growth_pct_per_hr_warning` (default 5.0%/hr each); a new `disk_growth` monitor alert kind (observability-only, no action taken).
12
+ - `agent_runner.events.register_plugin_kind(name)` restores a namespaced custom-event-kind affordance for out-of-tree plugins, validated against the builtin-kind namespace.
13
+
14
+ ### Verified
15
+ - Pre-OOM host-pressure terminate: small-host PSI/swap calibration pinned as always-on tests-of-record, plus a gated real-cgroup property test (`AGENT_RUNNER_E2E_PI`) run live on a ~0.5 GB host — sustained host PSI-full ≥ 60 (measured 69–77 over 3 samples) terminated and reaped the agent with the host still responsive and no cgroup OOM kill; reproduced across two clean runs.
16
+
17
+ ### Docs
18
+ - `docs/migrations/0.3.md` clarifies the 0.3.9 seam-subtraction adaptation contract (fold-in via `post_round_hooks`, `event_kinds` replacement) and documents `register_plugin_kind`.
19
+
20
+ No config migration required. `peek --json` schema stays `2.6`.
21
+
8
22
  ## [0.3.10] - 2026-09-17
9
23
 
10
24
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.3.10
3
+ Version: 0.3.11
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
@@ -51,7 +51,7 @@ full disks, runaway memory.
51
51
 
52
52
  ```
53
53
  ┌──────────────────────────────────────────┐
54
- │ Layer 3: The Witness (monitor) │ 13 detectors + auto-stop
54
+ │ Layer 3: The Witness (monitor) │ 14 detectors + auto-stop
55
55
  ├──────────────────────────────────────────┤
56
56
  │ Layer 2: The Loop (serve) │ signal-trapping restart loop
57
57
  ├──────────────────────────────────────────┤
@@ -88,7 +88,7 @@ Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
88
88
  |---|---|
89
89
  | `init` / `install` / `uninstall` | `peek` — state snapshot |
90
90
  | `start` / `stop` / `kill` | `watch` — peek in a refresh loop |
91
- | `restart` / `status` | `monitor` — 13 detectors, alerts, auto-stop |
91
+ | `restart` / `status` | `monitor` — 14 detectors, alerts, auto-stop |
92
92
  | `round` / `serve` | `events` — query / stream events.jsonl |
93
93
  | `upgrade` / `migrate` | `doctor` — read-only pre-flight |
94
94
 
@@ -113,10 +113,10 @@ Optionally pause the loop during off-hours: `[schedule]` run/pause windows gate
113
113
  `serve` (override with `serve --ignore-schedule`) — see the `[schedule]` section
114
114
  of [`docs/configuration.md`](docs/configuration.md).
115
115
 
116
- ## Monitor: 13 detectors
116
+ ## Monitor: 14 detectors
117
117
 
118
118
  Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
119
- `mem_pressure`, `mem_pressure_gate_inert`, `mem_signal_unavailable`,
119
+ `disk_growth`, `mem_pressure`, `mem_pressure_gate_inert`, `mem_signal_unavailable`,
120
120
  `network_fail`, `rate_limit_active`, `anomaly_repetitive_active`,
121
121
  `supervisor_stale`. `mem_pressure` also drives a separate serve-loop
122
122
  admission gate that defers or terminates rounds under real memory pressure —
@@ -13,7 +13,7 @@ full disks, runaway memory.
13
13
 
14
14
  ```
15
15
  ┌──────────────────────────────────────────┐
16
- │ Layer 3: The Witness (monitor) │ 13 detectors + auto-stop
16
+ │ Layer 3: The Witness (monitor) │ 14 detectors + auto-stop
17
17
  ├──────────────────────────────────────────┤
18
18
  │ Layer 2: The Loop (serve) │ signal-trapping restart loop
19
19
  ├──────────────────────────────────────────┤
@@ -50,7 +50,7 @@ Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
50
50
  |---|---|
51
51
  | `init` / `install` / `uninstall` | `peek` — state snapshot |
52
52
  | `start` / `stop` / `kill` | `watch` — peek in a refresh loop |
53
- | `restart` / `status` | `monitor` — 13 detectors, alerts, auto-stop |
53
+ | `restart` / `status` | `monitor` — 14 detectors, alerts, auto-stop |
54
54
  | `round` / `serve` | `events` — query / stream events.jsonl |
55
55
  | `upgrade` / `migrate` | `doctor` — read-only pre-flight |
56
56
 
@@ -75,10 +75,10 @@ Optionally pause the loop during off-hours: `[schedule]` run/pause windows gate
75
75
  `serve` (override with `serve --ignore-schedule`) — see the `[schedule]` section
76
76
  of [`docs/configuration.md`](docs/configuration.md).
77
77
 
78
- ## Monitor: 13 detectors
78
+ ## Monitor: 14 detectors
79
79
 
80
80
  Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
81
- `mem_pressure`, `mem_pressure_gate_inert`, `mem_signal_unavailable`,
81
+ `disk_growth`, `mem_pressure`, `mem_pressure_gate_inert`, `mem_signal_unavailable`,
82
82
  `network_fail`, `rate_limit_active`, `anomaly_repetitive_active`,
83
83
  `supervisor_stale`. `mem_pressure` also drives a separate serve-loop
84
84
  admission gate that defers or terminates rounds under real memory pressure —
@@ -1,6 +1,6 @@
1
1
  """Pure anomaly detectors over events + metrics + log tails.
2
2
 
3
- 13 built-in detectors. Two trigger ``auto_action="stop_service"``:
3
+ 14 built-in detectors. Two trigger ``auto_action="stop_service"``:
4
4
  * oauth_fail — agent-reported auth failures, or an auth pattern in
5
5
  short-exit logs (retrying burns API quota)
6
6
  * disk_critical — disk_used_pct > 95% (writing more risks corruption)
@@ -218,6 +218,93 @@ def detect_disk_critical(
218
218
  )
219
219
 
220
220
 
221
+ def detect_disk_growth(
222
+ metrics: list[dict[str, Any]],
223
+ *,
224
+ growth_window_s: int = 1800,
225
+ disk_growth_pct_per_hr_warning: float = 5.0,
226
+ inode_growth_pct_per_hr_warning: float = 5.0,
227
+ ) -> Alert | None:
228
+ """WARNING when disk_used_pct and/or inode_used_pct is climbing faster than
229
+ its configured %/hr floor -- a generalized host-disk/inode-pressure signal
230
+ (pi's cross-round resume amplifies session-log growth; not pi-specific).
231
+
232
+ Rate is Δpct over the OLDEST metrics sample landing within
233
+ ``growth_window_s`` of the LATEST sample's ``ts`` (``agent_runner``'s
234
+ ``parse_iso_ms``), divided by the elapsed hours between them -- real
235
+ metrics land at variable wall-clock gaps, so a fixed-interval
236
+ ``(cur - prev)`` delta would misjudge the rate whenever polls drift.
237
+ Fewer than two samples inside the window (a cold start, or every prior
238
+ sample already aged out) returns ``None`` -- no baseline, no rate.
239
+
240
+ Observability only BY DESIGN, mirroring
241
+ :func:`host_health.cgroup_growth_rate_pressure`: unlike
242
+ :func:`detect_disk_critical`, this NEVER sets ``auto_action`` -- a
243
+ growth-rate signal is for an operator/plugin to act on, not a kill
244
+ trigger. Carries BOTH the disk and inode rate dims on one alert (not two
245
+ separate alert kinds) even when only one crosses its floor, so a reader
246
+ always sees the other dimension's current rate for context.
247
+ """
248
+ latest_ts_raw = _latest(metrics, "ts")
249
+ latest_disk = _latest(metrics, "disk_used_pct")
250
+ if latest_ts_raw is None or latest_disk is None:
251
+ return None
252
+ latest_ts = parse_iso_ms(latest_ts_raw)
253
+ latest_inode = _latest(metrics, "inode_used_pct")
254
+
255
+ baseline: dict[str, Any] | None = None
256
+ baseline_ts: datetime | None = None
257
+ for m in metrics:
258
+ ts_raw = m.get("ts")
259
+ if ts_raw is None or "disk_used_pct" not in m:
260
+ continue
261
+ ts = parse_iso_ms(ts_raw)
262
+ age_s = (latest_ts - ts).total_seconds()
263
+ if age_s < 0 or age_s > growth_window_s:
264
+ continue # future-dated, or aged out of the growth window
265
+ # metrics is chronologically ascending, so the first in-window sample
266
+ # is the oldest -- the widest baseline; no later sample can be older.
267
+ baseline, baseline_ts = m, ts
268
+ break
269
+
270
+ if baseline is None or baseline_ts is None or baseline_ts >= latest_ts:
271
+ return None # no earlier in-window sample to diff against
272
+ elapsed_hr = (latest_ts - baseline_ts).total_seconds() / 3600.0
273
+ if elapsed_hr <= 0:
274
+ return None
275
+
276
+ disk_rate = (latest_disk - baseline["disk_used_pct"]) / elapsed_hr
277
+ baseline_inode = baseline.get("inode_used_pct")
278
+ inode_rate = (
279
+ (latest_inode - baseline_inode) / elapsed_hr
280
+ if latest_inode is not None and baseline_inode is not None
281
+ else None
282
+ )
283
+
284
+ disk_trip = disk_rate >= disk_growth_pct_per_hr_warning
285
+ inode_trip = inode_rate is not None and inode_rate >= inode_growth_pct_per_hr_warning
286
+ if not disk_trip and not inode_trip:
287
+ return None
288
+
289
+ bits = []
290
+ if disk_trip:
291
+ bits.append(f"disk +{disk_rate:.1f}%/hr (>= {disk_growth_pct_per_hr_warning})")
292
+ if inode_trip:
293
+ bits.append(f"inode +{inode_rate:.1f}%/hr (>= {inode_growth_pct_per_hr_warning})")
294
+ return _alert(
295
+ "disk_growth",
296
+ "warning",
297
+ f"{' and '.join(bits)} over the last {growth_window_s}s",
298
+ {
299
+ "disk_pct_per_hr": round(disk_rate, 2),
300
+ "disk_threshold_pct_per_hr": disk_growth_pct_per_hr_warning,
301
+ "inode_pct_per_hr": round(inode_rate, 2) if inode_rate is not None else None,
302
+ "inode_threshold_pct_per_hr": inode_growth_pct_per_hr_warning,
303
+ "window_s": growth_window_s,
304
+ },
305
+ )
306
+
307
+
221
308
  def _latest_two(metrics: list[dict[str, Any]]) -> tuple[dict[str, Any], dict[str, Any]]:
222
309
  """Return (current, previous) full metric dicts -- the last two history
223
310
  entries (or ``{}`` when shorter). The mem-pressure ladder needs a delta
@@ -26,6 +26,7 @@ KNOWN_ALERT_KINDS: frozenset[str] = frozenset(
26
26
  "orphan_chain",
27
27
  "disk_warning",
28
28
  "disk_critical",
29
+ "disk_growth",
29
30
  "mem_pressure",
30
31
  "mem_signal_unavailable",
31
32
  "mem_pressure_gate_inert",
@@ -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.10'
22
- __version_tuple__ = version_tuple = (0, 3, 10)
21
+ __version__ = version = '0.3.11'
22
+ __version_tuple__ = version_tuple = (0, 3, 11)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -204,6 +204,13 @@ class PluginsConfig:
204
204
  class _HostHealthDiskConfig:
205
205
  warning_pct: float = 90.0 # disk_warning fires when disk_used_pct >= this
206
206
  critical_pct: float = 95.0 # disk_critical fires when disk_used_pct >= this
207
+ # disk_growth: observability-only WARNING when disk_used_pct/inode_used_pct
208
+ # climbs faster than a %/hr floor, measured over the oldest metrics sample
209
+ # landing within this many seconds of the latest one -- never a kill (see
210
+ # detect_disk_growth).
211
+ growth_window_s: int = 1800
212
+ disk_growth_pct_per_hr_warning: float = 5.0
213
+ inode_growth_pct_per_hr_warning: float = 5.0
207
214
 
208
215
 
209
216
  @dataclass(frozen=True)
@@ -517,6 +517,18 @@ def _parse_monitor(monitor_d: dict) -> MonitorConfig:
517
517
  critical_pct=_require_pct(
518
518
  disk_d.get("critical_pct", 95.0), field="monitor.host_health.disk.critical_pct"
519
519
  ),
520
+ growth_window_s=_require_positive_int(
521
+ disk_d.get("growth_window_s", 1800),
522
+ field="monitor.host_health.disk.growth_window_s",
523
+ ),
524
+ disk_growth_pct_per_hr_warning=_require_positive_float(
525
+ disk_d.get("disk_growth_pct_per_hr_warning", 5.0),
526
+ field="monitor.host_health.disk.disk_growth_pct_per_hr_warning",
527
+ ),
528
+ inode_growth_pct_per_hr_warning=_require_positive_float(
529
+ disk_d.get("inode_growth_pct_per_hr_warning", 5.0),
530
+ field="monitor.host_health.disk.inode_growth_pct_per_hr_warning",
531
+ ),
520
532
  ),
521
533
  memory=_HostHealthMemoryConfig(
522
534
  avail_min_mb=_require_non_negative_int(
@@ -4,8 +4,11 @@
4
4
  supervisor code, collected by reflection (see ``_collect_builtin_kinds``).
5
5
 
6
6
  Public API:
7
- - ``KNOWN_EVENT_KINDS`` — the set of known kinds; supports ``in`` and iteration.
8
- Preserved so ``from agent_runner.events import KNOWN_EVENT_KINDS`` still works.
7
+ - ``KNOWN_EVENT_KINDS`` — the set of known kinds (builtin + plugin-registered);
8
+ supports ``in`` and iteration. Preserved so
9
+ ``from agent_runner.events import KNOWN_EVENT_KINDS`` still works.
10
+ - ``register_plugin_kind(name)`` — an out-of-tree plugin registers a
11
+ namespaced custom event kind so ``emit()`` accepts it.
9
12
  - ``emit(log_dir, kind, /, **fields)`` — append a structured event line.
10
13
  ``log_dir`` and ``kind`` are positional-only so callers can pass
11
14
  ``log_dir=...`` as a payload field name without parameter shadowing.
@@ -96,6 +99,16 @@ TRANSIENT_ERROR_RECOVERED = "transient_error_recovered"
96
99
  UPGRADE_START_FAILED = "upgrade_start_failed"
97
100
 
98
101
 
102
+ def _is_snake_case_kind(s: str) -> bool:
103
+ """The legal event-kind name rule: lowercase snake_case, no leading '_'.
104
+
105
+ Single source shared by _collect_builtin_kinds (filtering this module's
106
+ constants) and register_plugin_kind (validating plugin-supplied names) so
107
+ the two cannot drift if the naming rule ever changes.
108
+ """
109
+ return s.islower() and not s.startswith("_") and s.replace("_", "").isalnum()
110
+
111
+
99
112
  def _collect_builtin_kinds() -> frozenset[str]:
100
113
  """Single-source: every module-level UPPER_CASE str constant whose value
101
114
  is a snake_case kind name is a builtin event kind. Drift between the
@@ -107,39 +120,78 @@ def _collect_builtin_kinds() -> frozenset[str]:
107
120
  return frozenset(
108
121
  v
109
122
  for k, v in vars(mod).items()
110
- if k.isupper()
111
- and isinstance(v, str)
112
- and v.islower()
113
- and not v.startswith("_")
114
- and v.replace("_", "").isalnum()
123
+ if k.isupper() and isinstance(v, str) and _is_snake_case_kind(v)
115
124
  )
116
125
 
117
126
 
118
127
  _BUILTIN_KINDS: frozenset[str] = _collect_builtin_kinds()
119
128
 
129
+ # Runtime-registered plugin event kinds (out-of-tree producers). Unlike
130
+ # _BUILTIN_KINDS this is mutable and populated at import/registration time by
131
+ # plugin code via register_plugin_kind(), not by reflection over this module.
132
+ _PLUGIN_KINDS: set[str] = set()
133
+
120
134
 
121
135
  def _is_known(name: str) -> bool:
122
- return name in _BUILTIN_KINDS
136
+ return name in _BUILTIN_KINDS or name in _PLUGIN_KINDS
137
+
138
+
139
+ def register_plugin_kind(name: str) -> None:
140
+ """Register a namespaced custom event kind for an out-of-tree plugin.
141
+
142
+ Restores a minimal affordance a prior release's subtraction of the
143
+ ``event_kinds`` manifest field removed: a plugin installed on a target
144
+ host still needs a validated way to emit its own event kinds through the
145
+ same ``emit()``/events-*.jsonl stream core uses, rather than
146
+ hand-appending JSON lines (unvalidated, and liable to collide with a
147
+ builtin kind).
148
+
149
+ ``name`` must be lowercase snake_case, contain at least one ``_`` (so it
150
+ is namespaced — e.g. ``myplugin_ok``), and its first underscore-segment
151
+ must not be a prefix any builtin kind uses (derived programmatically from
152
+ _BUILTIN_KINDS, not hand-maintained — e.g. ``round_myplugin`` collides
153
+ with the ``round_*`` builtin namespace even though ``round_myplugin``
154
+ itself isn't a builtin kind). Raises ValueError on any violation.
155
+ """
156
+ if not isinstance(name, str) or not name or not _is_snake_case_kind(name):
157
+ raise ValueError(f"invalid plugin event kind: {name!r}")
158
+ if "_" not in name:
159
+ raise ValueError(f"plugin event kind must be namespaced (contain '_'): {name!r}")
160
+ # An exact builtin collision is also caught by the prefix check below (a
161
+ # builtin's own first-segment is always a builtin prefix), but flag it here
162
+ # for a clearer, more specific error message.
163
+ if name in _BUILTIN_KINDS:
164
+ raise ValueError(f"plugin event kind collides with a builtin kind: {name!r}")
165
+ prefix = name.split("_", 1)[0]
166
+ builtin_prefixes = {k.split("_", 1)[0] for k in _BUILTIN_KINDS}
167
+ if prefix in builtin_prefixes:
168
+ raise ValueError(f"plugin event kind {name!r} uses builtin-owned prefix {prefix!r}")
169
+ _PLUGIN_KINDS.add(name)
123
170
 
124
171
 
125
172
  class _KnownEventKindsView:
126
- """Read-only view of built-in event kinds.
173
+ """Read-only view of known event kinds — builtin plus plugin-registered.
127
174
 
128
175
  Backward compat for ``from agent_runner.events import KNOWN_EVENT_KINDS``.
129
- Supports ``in`` and ``iter``; intentionally does NOT support mutation.
176
+ Supports ``in`` and ``iter``; intentionally does NOT support mutation
177
+ (use ``register_plugin_kind()`` to add a plugin kind).
130
178
  """
131
179
 
132
180
  def __contains__(self, item: object) -> bool:
133
181
  return isinstance(item, str) and _is_known(item)
134
182
 
135
183
  def __iter__(self) -> Iterator[str]:
136
- yield from sorted(_BUILTIN_KINDS)
184
+ yield from sorted(_BUILTIN_KINDS | _PLUGIN_KINDS)
137
185
 
138
186
  def __len__(self) -> int:
139
- return len(_BUILTIN_KINDS)
187
+ return len(_BUILTIN_KINDS | _PLUGIN_KINDS)
140
188
 
141
189
  def __repr__(self) -> str:
142
- return f"<KNOWN_EVENT_KINDS: {len(_BUILTIN_KINDS)} built-in>"
190
+ total = len(_BUILTIN_KINDS | _PLUGIN_KINDS)
191
+ return (
192
+ f"<KNOWN_EVENT_KINDS: {total} known "
193
+ f"({len(_BUILTIN_KINDS)} built-in, {len(_PLUGIN_KINDS)} plugin)>"
194
+ )
143
195
 
144
196
 
145
197
  KNOWN_EVENT_KINDS = _KnownEventKindsView()
@@ -80,6 +80,10 @@ def sample() -> dict[str, Any]:
80
80
  def collect(disk_path: Path, *, agent_binary: str | None = None) -> dict[str, Any]:
81
81
  vm = psutil.virtual_memory()
82
82
  du = psutil.disk_usage(str(disk_path))
83
+ st = os.statvfs(str(disk_path))
84
+ # f_files==0 means the filesystem does not track inode counts at all (some
85
+ # network/overlay filesystems) -- None signals "no signal", not "0% used".
86
+ inode_used_pct = round(100 * (1 - st.f_ffree / st.f_files), 1) if st.f_files else None
83
87
  out: dict[str, Any] = {
84
88
  "mem_total_mb": vm.total // (1024 * 1024),
85
89
  "mem_used_pct": round(vm.percent, 1),
@@ -87,6 +91,7 @@ def collect(disk_path: Path, *, agent_binary: str | None = None) -> dict[str, An
87
91
  "disk_total_gb": round(du.total / (1024**3), 1),
88
92
  "disk_free_gb": round(du.free / (1024**3), 1),
89
93
  "disk_used_pct": round(du.percent, 1),
94
+ "inode_used_pct": inode_used_pct,
90
95
  }
91
96
  try:
92
97
  load = os.getloadavg()
@@ -1,6 +1,6 @@
1
1
  """Monitor — the cycle-edge: running detectors and dispatching on their alerts.
2
2
 
3
- 13 built-in detectors run every poll (see ``_monitor_detectors``); two trigger
3
+ 14 built-in detectors run every poll (see ``_monitor_detectors``); two trigger
4
4
  ``auto_action="stop_service"``:
5
5
  * oauth_fail — agent-reported auth failures, or an auth pattern in
6
6
  short-exit logs (retrying burns API quota)
@@ -36,6 +36,7 @@ from typing import Any, Literal
36
36
  from agent_runner._monitor_detectors import (
37
37
  detect_anomaly_repetitive_active,
38
38
  detect_disk_critical,
39
+ detect_disk_growth,
39
40
  detect_disk_warning,
40
41
  detect_hung,
41
42
  detect_mem_pressure,
@@ -102,7 +103,7 @@ def run_all_detectors(
102
103
  host_health_cfg: MonitorHostHealthConfig = _DEFAULT_HOST_HEALTH_CFG,
103
104
  log_dir: Path | None = None,
104
105
  ) -> list[Alert]:
105
- """Run all 13 detectors; returns alerts (empty = healthy).
106
+ """Run all 14 detectors; returns alerts (empty = healthy).
106
107
 
107
108
  Each detector is isolated via ``_run_detector``: a crash in one emits
108
109
  ``detector_error`` (when ``log_dir`` is given) and does not stop the rest.
@@ -138,6 +139,15 @@ def run_all_detectors(
138
139
  "disk_critical",
139
140
  lambda: detect_disk_critical(metrics, threshold_pct=host_health_cfg.disk.critical_pct),
140
141
  ),
142
+ (
143
+ "disk_growth",
144
+ lambda: detect_disk_growth(
145
+ metrics,
146
+ growth_window_s=host_health_cfg.disk.growth_window_s,
147
+ disk_growth_pct_per_hr_warning=host_health_cfg.disk.disk_growth_pct_per_hr_warning,
148
+ inode_growth_pct_per_hr_warning=host_health_cfg.disk.inode_growth_pct_per_hr_warning,
149
+ ),
150
+ ),
141
151
  ("mem_pressure", lambda: detect_mem_pressure(metrics, cfg=host_health_cfg)),
142
152
  (
143
153
  "mem_pressure_gate_inert",