cli-agent-runner 0.3.6__tar.gz → 0.3.8__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 (423) hide show
  1. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.vulture-whitelist.py +7 -0
  2. cli_agent_runner-0.3.8/BACKLOG.md +43 -0
  3. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/CHANGELOG.md +16 -0
  4. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/CONTRIBUTING.md +5 -4
  5. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/PKG-INFO +5 -3
  6. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/README.md +4 -2
  7. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_docgen.py +2 -0
  8. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_emit/__init__.py +6 -0
  9. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_emit/memory.py +73 -2
  10. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_monitor_state.py +19 -19
  11. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_throttle.py +6 -5
  12. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_version.py +2 -2
  13. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/api.py +3 -0
  14. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/_serve_cgroup.py +103 -11
  15. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/_serve_round.py +110 -9
  16. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/common.py +5 -1
  17. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/doctor_cmd.py +21 -4
  18. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/events_cmd.py +50 -121
  19. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/serve_cmd.py +2 -1
  20. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/config/__init__.py +1 -0
  21. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/config/models.py +31 -5
  22. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/config/parsers.py +41 -0
  23. cli_agent_runner-0.3.8/agent_runner/event_log.py +198 -0
  24. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/events.py +4 -47
  25. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/host_health.py +6 -3
  26. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/http_progress.py +8 -10
  27. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/metrics.py +125 -6
  28. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/claude.toml +3 -0
  29. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/vcs_state.py +39 -14
  30. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/architecture.md +30 -1
  31. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/commands.md +9 -6
  32. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/configuration.md +55 -2
  33. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/long-running-agents.md +1 -1
  34. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/memory-efficiency.md +51 -0
  35. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/migrations/0.3.md +1 -1
  36. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/plugins.md +5 -2
  37. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/recipes/pi.md +8 -0
  38. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/runbook.md +27 -2
  39. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/_clock.py +6 -1
  40. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/_test_helpers.py +3 -3
  41. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/contract/test_public_api_surface.py +3 -0
  42. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_plugin_owned_paths.py +36 -0
  43. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_spawn_round_mem_floor.py +521 -0
  44. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_architecture.py +16 -2
  45. cli_agent_runner-0.3.8/tests/invariants/test_brake_defaults_off.py +46 -0
  46. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_event_kinds_ssot.py +5 -0
  47. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_import_footprint.py +6 -0
  48. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_layer_2_loop_size.py +1 -1
  49. cli_agent_runner-0.3.8/tests/invariants/test_no_direct_inspect_import.py +47 -0
  50. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_peek_schema_version.py +3 -1
  51. cli_agent_runner-0.3.8/tests/invariants/test_throttle_stateless_imports.py +40 -0
  52. cli_agent_runner-0.3.8/tests/linux/test_memory_high_nudge_e2e.py +180 -0
  53. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cgroup_probe.py +279 -3
  54. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config.py +20 -0
  55. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_nested_strictness.py +61 -0
  56. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_doctor_cmd.py +84 -0
  57. cli_agent_runner-0.3.8/tests/unit/test_event_log.py +174 -0
  58. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_events.py +1 -57
  59. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_events_cmd.py +2 -1
  60. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_events_cmd_rollover.py +16 -11
  61. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_events_cmd_tail.py +81 -1
  62. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_exec_prefix.py +3 -3
  63. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_presets.py +15 -0
  64. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_round_outcome.py +45 -0
  65. cli_agent_runner-0.3.8/tests/unit/test_serve_round.py +81 -0
  66. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_round_oom_killed.py +21 -0
  67. cli_agent_runner-0.3.6/BACKLOG.md +0 -98
  68. cli_agent_runner-0.3.6/tests/unit/test_serve_round.py +0 -34
  69. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.codecov.yml +0 -0
  70. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  71. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  72. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  73. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  74. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/dependabot.yml +0 -0
  75. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/workflows/ci.yml +0 -0
  76. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/workflows/release.yml +0 -0
  77. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.gitignore +0 -0
  78. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/CODE_OF_CONDUCT.md +0 -0
  79. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/LICENSE +0 -0
  80. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/README.zh.md +0 -0
  81. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/SECURITY.md +0 -0
  82. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/__init__.py +0 -0
  83. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_emit/agent.py +0 -0
  84. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_emit/plugins.py +0 -0
  85. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_emit/rounds.py +0 -0
  86. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_emit/serve.py +0 -0
  87. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_install.py +0 -0
  88. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_lifecycle.py +0 -0
  89. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_monitor_detectors.py +0 -0
  90. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_monitor_registry.py +0 -0
  91. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_notify.py +0 -0
  92. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_observe.py +0 -0
  93. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_plugin_checksum.py +0 -0
  94. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_plugin_manifest.py +0 -0
  95. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_plugin_sandbox.py +0 -0
  96. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_plugin_scan.py +0 -0
  97. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_procwait.py +0 -0
  98. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_redact.py +0 -0
  99. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_registry.py +0 -0
  100. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_resolve.py +0 -0
  101. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_round_outcome.py +0 -0
  102. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_round_support.py +0 -0
  103. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_sandbox_probe.py +0 -0
  104. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_serve_policy.py +0 -0
  105. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_substrate.py +0 -0
  106. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/agent_runtime.py +0 -0
  107. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/api_types.py +0 -0
  108. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/__init__.py +0 -0
  109. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/_constants.py +0 -0
  110. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  111. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/codewhale.py +0 -0
  112. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
  113. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/gemini.py +0 -0
  114. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/kimi.py +0 -0
  115. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/pi.py +0 -0
  116. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/__init__.py +0 -0
  117. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/__main__.py +0 -0
  118. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/init_cmd.py +0 -0
  119. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/install_cmd.py +0 -0
  120. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/migrate_cmd.py +0 -0
  121. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/monitor_cmd.py +0 -0
  122. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/peek_cmd.py +0 -0
  123. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/round_cmd.py +0 -0
  124. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/service_cmd.py +0 -0
  125. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/upgrade_cmd.py +0 -0
  126. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/clock.py +0 -0
  127. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/config/errors.py +0 -0
  128. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/config/loader.py +0 -0
  129. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/config/validators.py +0 -0
  130. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/context_store.py +0 -0
  131. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/defenses.py +0 -0
  132. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/hooks.py +0 -0
  133. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/lifecycle.py +0 -0
  134. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/migrations.py +0 -0
  135. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/monitor.py +0 -0
  136. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/phase_select.py +0 -0
  137. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/__init__.py +0 -0
  138. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/aider.toml +0 -0
  139. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/codewhale.toml +0 -0
  140. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/gemini.toml +0 -0
  141. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/kimi.toml +0 -0
  142. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/pi.toml +0 -0
  143. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/prompt_loader.py +0 -0
  144. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/remote_relay.py +0 -0
  145. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/round_log.py +0 -0
  146. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/round_view.py +0 -0
  147. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/runner.py +0 -0
  148. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/scaffold.py +0 -0
  149. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/schedule.py +0 -0
  150. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/service_unit.py +0 -0
  151. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/startup_check.py +0 -0
  152. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/build.sh +0 -0
  153. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/deploy/Dockerfile +0 -0
  154. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/deploy/docker-compose.yml +0 -0
  155. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/README.md +0 -0
  156. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/events.md +0 -0
  157. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/migrations/0.2.md +0 -0
  158. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/quickstart.md +0 -0
  159. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/recipes/aider.md +0 -0
  160. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/recipes/codewhale.md +0 -0
  161. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/recipes/container-pi.md +0 -0
  162. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/recipes/deploy-all-in-one.md +0 -0
  163. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/recipes/kimi.md +0 -0
  164. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/thesis.md +0 -0
  165. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/pyproject.toml +0 -0
  166. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/__init__.py +0 -0
  167. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/conftest.py +0 -0
  168. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/contract/__init__.py +0 -0
  169. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/e2e/__init__.py +0 -0
  170. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/e2e/conftest.py +0 -0
  171. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  172. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/e2e/test_e2e_install_systemd.py +0 -0
  173. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  174. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  175. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  176. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  177. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  178. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/generate_vulture_whitelist.py +0 -0
  179. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/__init__.py +0 -0
  180. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_auto_stop_e2e.py +0 -0
  181. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_bounded_run.py +0 -0
  182. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_context_enricher_namespacing.py +0 -0
  183. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_cooperative_grace_property.py +0 -0
  184. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_dirty_handler_seam.py +0 -0
  185. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_fresh_eyes_signal.py +0 -0
  186. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_grace_kill_emission.py +0 -0
  187. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_install_dry_run.py +0 -0
  188. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_monitor_seeded.py +0 -0
  189. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_plugin_detector_loaded.py +0 -0
  190. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_plugin_real_flow.py +0 -0
  191. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  192. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_reap_property.py +0 -0
  193. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  194. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_scaffold_presets.py +0 -0
  195. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_serve_loop.py +0 -0
  196. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_spawn_round_wedged.py +0 -0
  197. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_substrate_fingerprint.py +0 -0
  198. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_transient_error_backoff.py +0 -0
  199. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/__init__.py +0 -0
  200. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/_docs.py +0 -0
  201. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/_event_scan.py +0 -0
  202. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_atomic_write_enforced.py +0 -0
  203. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_authored_facts_declared.py +0 -0
  204. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  205. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_bdd_test_conventions.py +0 -0
  206. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_builtin_plugin_names_sync.py +0 -0
  207. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_catalogs.py +0 -0
  208. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_classification_ssot.py +0 -0
  209. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_config_error_consistency.py +0 -0
  210. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_config_value_set_ssot.py +0 -0
  211. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_container_stop_bound.py +0 -0
  212. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
  213. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  214. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
  215. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_cli_claims.py +0 -0
  216. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_operator_surface.py +0 -0
  217. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
  218. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  219. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_usage_event_fields.py +0 -0
  220. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_docs_generated.py +0 -0
  221. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_docs_index_complete.py +0 -0
  222. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_entry_points_resolve.py +0 -0
  223. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_event_int_coercion.py +0 -0
  224. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_event_kind_registry.py +0 -0
  225. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_events_doc_contract.py +0 -0
  226. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
  227. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_hook_contract_docs.py +0 -0
  228. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_kill_tests_not_skipped.py +0 -0
  229. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_migrations_registry_ssot.py +0 -0
  230. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_module_boundaries.py +0 -0
  231. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_module_sizes.py +0 -0
  232. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_monitor_self_exclusion.py +0 -0
  233. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_ai_signatures.py +0 -0
  234. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_asyncio_select.py +0 -0
  235. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_internal_refs.py +0 -0
  236. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_price_logic.py +0 -0
  237. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  238. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_raw_time.py +0 -0
  239. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_vacuous_invariants.py +0 -0
  240. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_version_refs.py +0 -0
  241. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_phase_select_stateless.py +0 -0
  242. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_plugins_config_stable.py +0 -0
  243. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_procwait_shield.py +0 -0
  244. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  245. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  246. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_round_alloc_growth.py +0 -0
  247. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_round_log_decode_boundary.py +0 -0
  248. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_round_num_single_source.py +0 -0
  249. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_round_result_stable.py +0 -0
  250. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  251. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  252. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_timeout_budget_invariant.py +0 -0
  253. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_upstream_schema_canary.py +0 -0
  254. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  255. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/linux/__init__.py +0 -0
  256. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/linux/test_plugin_sandbox_kill.py +0 -0
  257. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/literate/__init__.py +0 -0
  258. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/literate/parser.py +0 -0
  259. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/literate/test_parser.py +0 -0
  260. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/literate/test_quickstart.py +0 -0
  261. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/__init__.py +0 -0
  262. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime.py +0 -0
  263. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime_container.py +0 -0
  264. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime_grace.py +0 -0
  265. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
  266. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime_progress.py +0 -0
  267. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime_reap.py +0 -0
  268. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  269. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_assemble_prompt.py +0 -0
  270. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_events_stream.py +0 -0
  271. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_install.py +0 -0
  272. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_observation.py +0 -0
  273. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_read_round_num.py +0 -0
  274. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_resolve_phase.py +0 -0
  275. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_service.py +0 -0
  276. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_types.py +0 -0
  277. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_apply_reap_grace_env.py +0 -0
  278. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_auto_stop_gating.py +0 -0
  279. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_builtin_provenance.py +0 -0
  280. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_catalogs.py +0 -0
  281. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_claude_error_detector.py +0 -0
  282. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_claude_foreign_schema.py +0 -0
  283. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli.py +0 -0
  284. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli_common.py +0 -0
  285. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli_init_install.py +0 -0
  286. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli_main_boundary.py +0 -0
  287. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli_monitor_http.py +0 -0
  288. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  289. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli_upgrade.py +0 -0
  290. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_codewhale_code_precedence.py +0 -0
  291. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_codewhale_plugin.py +0 -0
  292. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_coerce_int_poison.py +0 -0
  293. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_collapse_spawn_decisions.py +0 -0
  294. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_fresh_eyes.py +0 -0
  295. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_max_rounds.py +0 -0
  296. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_prompt_delivery.py +0 -0
  297. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_schedule.py +0 -0
  298. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_stop_file.py +0 -0
  299. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_strictness.py +0 -0
  300. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  301. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_transient_error_action.py +0 -0
  302. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_context_store.py +0 -0
  303. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_control_char_config.py +0 -0
  304. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_default_dirty_handler.py +0 -0
  305. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_defenses.py +0 -0
  306. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_detect_hung_latch.py +0 -0
  307. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_detector_isolation.py +0 -0
  308. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_detector_protocol.py +0 -0
  309. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_dirty_handlers.py +0 -0
  310. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_dispatch_dirty_owner_branch.py +0 -0
  311. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_docgen.py +0 -0
  312. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_doctor_sandbox.py +0 -0
  313. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_effective_throttle_view.py +0 -0
  314. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_emit_config_migrated.py +0 -0
  315. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_emit_schedule.py +0 -0
  316. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
  317. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_events_jsonl_decode.py +0 -0
  318. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_example_dirty_handler.py +0 -0
  319. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_example_plugin.py +0 -0
  320. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_exit_cause.py +0 -0
  321. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_exit_classifier.py +0 -0
  322. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  323. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_gate_serve_boot.py +0 -0
  324. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_gemini_plugin.py +0 -0
  325. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_git_timeout.py +0 -0
  326. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_hook_failure_isolation.py +0 -0
  327. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_hook_redact_order.py +0 -0
  328. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_hooks.py +0 -0
  329. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_host_health.py +0 -0
  330. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_http_progress.py +0 -0
  331. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_http_progress_epoch.py +0 -0
  332. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_init_entry_points.py +0 -0
  333. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
  334. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_kill_pgroup_shielding.py +0 -0
  335. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_kimi_plugin.py +0 -0
  336. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_lifecycle.py +0 -0
  337. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_load_fail_closed.py +0 -0
  338. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_loader_split.py +0 -0
  339. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_manifest_spawn_hooks.py +0 -0
  340. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_metrics.py +0 -0
  341. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_metrics_cgroup_usage.py +0 -0
  342. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_metrics_swap.py +0 -0
  343. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_migrate_cmd.py +0 -0
  344. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_migrations.py +0 -0
  345. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_assembly.py +0 -0
  346. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_cmd_severity.py +0 -0
  347. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_dedup_identity.py +0 -0
  348. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_dedup_rearm.py +0 -0
  349. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  350. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  351. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  352. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_detectors.py +0 -0
  353. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_event_tail.py +0 -0
  354. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_loop.py +0 -0
  355. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_on_alert.py +0 -0
  356. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_remote.py +0 -0
  357. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
  358. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_schedule_pause.py +0 -0
  359. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_network_blip_signal_guard.py +0 -0
  360. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_notify.py +0 -0
  361. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_open_round_log.py +0 -0
  362. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_orphan_chain_single_pass.py +0 -0
  363. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_outer_round_ceiling.py +0 -0
  364. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_peek_argparse.py +0 -0
  365. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_peek_project_isolation.py +0 -0
  366. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_peek_sandbox_block.py +0 -0
  367. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_peek_schedule.py +0 -0
  368. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_peek_select.py +0 -0
  369. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_phase_agent_launch.py +0 -0
  370. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_phase_profiles.py +0 -0
  371. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_phase_select.py +0 -0
  372. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_pi_plugin.py +0 -0
  373. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugin_checksum.py +0 -0
  374. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugin_constants.py +0 -0
  375. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugin_manifest.py +0 -0
  376. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugin_name_mismatch.py +0 -0
  377. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugin_sandbox_wire.py +0 -0
  378. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugin_scan_parity.py +0 -0
  379. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugins_config_fields.py +0 -0
  380. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_primary_prompt_file_home.py +0 -0
  381. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_procwait.py +0 -0
  382. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_prompt_loader.py +0 -0
  383. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_read_json_hardening.py +0 -0
  384. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_redact.py +0 -0
  385. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_resolve.py +0 -0
  386. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_round_cmd.py +0 -0
  387. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_round_cmd_sigterm.py +0 -0
  388. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_round_log_helpers.py +0 -0
  389. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_round_outcome_agent_axis.py +0 -0
  390. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_round_view.py +0 -0
  391. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_runner.py +0 -0
  392. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_runner_sidecar_ordering.py +0 -0
  393. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_runner_throttle.py +0 -0
  394. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_sandbox_probe.py +0 -0
  395. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_scaffold.py +0 -0
  396. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_schedule.py +0 -0
  397. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_cmd_bounded.py +0 -0
  398. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_config_broken.py +0 -0
  399. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_crash_loop.py +0 -0
  400. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_mem_gate.py +0 -0
  401. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_phase_schedule.py +0 -0
  402. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_policy.py +0 -0
  403. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
  404. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_round_log.py +0 -0
  405. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_round_terminate.py +0 -0
  406. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_round_wait_exit.py +0 -0
  407. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_schedule_gate.py +0 -0
  408. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_sentinel.py +0 -0
  409. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_startup_hooks.py +0 -0
  410. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_wakeup.py +0 -0
  411. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_service_mode.py +0 -0
  412. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_service_unit.py +0 -0
  413. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_spawn_seam.py +0 -0
  414. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_startup_battery_exit.py +0 -0
  415. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_startup_check.py +0 -0
  416. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_substrate.py +0 -0
  417. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
  418. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_terminate_round_wait_exit.py +0 -0
  419. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_throttle_back_off_ladder.py +0 -0
  420. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_throttle_epoch_bound.py +0 -0
  421. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_throttle_plugin_classification.py +0 -0
  422. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_upgrade_migrate.py +0 -0
  423. {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_vcs_state.py +0 -0
@@ -195,6 +195,7 @@ supervisor_stale_threshold_s
195
195
  disk
196
196
  memory
197
197
  pressure
198
+ brake
198
199
 
199
200
  # agent_runner.config.models.PhaseOverride
200
201
  round_budget_s
@@ -255,6 +256,11 @@ pause_windows
255
256
  stash_idempotency_s
256
257
  dirty_action
257
258
 
259
+ # agent_runner.config.models._HostHealthBrakeConfig
260
+ memory_high
261
+ memory_high_step_pct
262
+ warning_consecutive_samples
263
+
258
264
  # agent_runner.config.models._HostHealthDiskConfig
259
265
  warning_pct
260
266
  critical_pct
@@ -270,6 +276,7 @@ some_avg10_warning
270
276
  critical_consecutive_samples
271
277
  in_round_terminate
272
278
  cgroup_growth_rate_warning_mb_per_min
279
+ in_round_nudge
273
280
 
274
281
  # agent_runner.context_store.OrphanState
275
282
  round_num
@@ -0,0 +1,43 @@
1
+ # Backlog
2
+
3
+ Known follow-ups that are real design or behavior decisions, not defects. Each
4
+ 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
+
11
+ ## 1. `_tail_events` / `_tail_events_jsonl` duplication
12
+
13
+ Three event-stream tailers now exist at deliberately different scopes, and they
14
+ are not accidental copies:
15
+
16
+ - `cli/events_cmd.py:_tail_events` — current-month-scoped
17
+ (`_current_month_events_file`), resets its offset to `0` on truncation.
18
+ - `_monitor_state.py:_tail_events_jsonl` — globs every `events-*.jsonl`;
19
+ re-exported through the `monitor.py` facade. It moved out of `api.py` in the
20
+ 0.3.0 api split (`_install`/`_lifecycle`/`_observe`).
21
+ - `_throttle.py:_tail_events` — the newest two monthly files
22
+ (`glob("events-*.jsonl")[-2:]`).
23
+
24
+ Collapsing them means first deciding which scope is correct for each caller — a
25
+ structural decision, not a literal swap. (The older "character-identical
26
+ kind-read loop in `_throttle.py` and `monitor.py`" sub-concern has evaporated:
27
+ `monitor.py` no longer carries that loop — it moved to `_monitor_detectors.py`
28
+ and grew its own reset-ladder logic — and `_throttle.py` iterates forward over
29
+ the tailer generator, so the only shared text left is the single line
30
+ `kind = ev.get("event")`.)
31
+
32
+ ## 2. `stash_orphan` loses the ref when the post-push listing fails
33
+
34
+ `vcs_state.stash_orphan` returns `None` in three cases; two are true no-ops. The
35
+ third — `git stash push` succeeded but the follow-up `git stash list` failed —
36
+ means the WIP *is* stashed yet callers read "nothing stashed" and report the
37
+ tree as ignored. It is already documented in-code as a KNOWN GAP and judged
38
+ effectively unreachable (a listing that fails microseconds after a push that
39
+ just succeeded in the same repo).
40
+
41
+ Deferred because closing it is a naming decision, not cleanup: no event kind
42
+ carries the meaning "stashed but ref lost" (`orphan_stash_failed` would be wrong
43
+ — the stash exists). Left as-is until that kind is designed.
@@ -5,6 +5,22 @@ 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.8] - 2026-09-16
9
+
10
+ ### Changed
11
+ - `try_auto_commit` (`vcs.dirty_action = "auto_commit"`) now honors the plugin-owned-paths registry, matching `stash_orphan`: paths registered via `register_plugin_owned_paths` are excluded from the auto-commit, so a plugin's deliverables no longer land in the agent's git history.
12
+ - `round_oom_killed` now also carries `events_oom_kill_delta` — the canonical name, sibling-consistent with `round_cgroup_memory`'s counters; the original bare `oom_kill_delta` is retained as an alias (a future breaking release drops it).
13
+
14
+ ### Added
15
+ - The `claude` preset arms the repetitive-tool-call anomaly detector (`[monitor] anomaly_repetitive_window = 30`, `anomaly_repetitive_threshold = 15`) — trips only on an egregious stuck loop (the same tool+target ≥15× within 30 calls). The global default stays opt-in (disabled).
16
+
17
+ ## [0.3.7] - 2026-09-15
18
+
19
+ ### Added
20
+ - Opt-in cgroup v2 `memory.high` soft-brake (`[monitor.host_health.brake] memory_high`, default off): under sustained warning-level memory pressure the supervisor reversibly writes `memory.high` on its OWN cgroup leaf to throttle a growing round before the host swaps, then restores it on recovery or at round end. `memory_high_step_pct` defaults to `0` = cap-at-current (throttle further growth with no synchronous reclaim burst — safe for SD-backed hosts); `step_pct >= 1` is opt-in aggressive reclaim that dumps ~step% of the leaf to swap at engage. Fail-open; arms only when the leaf is delegated.
21
+ - Opt-in early cooperative SIGTERM (`[monitor.host_health.pressure] in_round_nudge`, default off): fires the hard floor's SIGTERM two samples earlier so a cooperative agent gets its wrap-up grace; recorded as `round_mem_terminated` with a new `tier` field so the mem-loop give-up still converges.
22
+ - New events `memory_high_engaged` / `memory_high_released` / `memory_high_write_failed`; `doctor` and `peek --json` (schema `2.5`) report the brake as `off | armed | inert(<reason>)`.
23
+
8
24
  ## [0.3.6] - 2026-09-15
9
25
 
10
26
  ### Changed
@@ -75,10 +75,11 @@ into `docs/commands.md` from the registry) plus `CHANGELOG.md`. Between them
75
75
  they carry a config rewrite and per-version notes; a doc that only restates the
76
76
  changelog is upkeep with no reader.
77
77
 
78
- `docs/migrations/0.2.md` is the one surviving guide: it collects the few manual
79
- steps a config rewrite can't do (recovery procedures, a systemd regen, a mode
80
- switch). Keep the relevant CHANGELOG entries terse and link it
81
- (`See docs/migrations/0.2.md.`).
78
+ `docs/migrations/0.2.md` and `docs/migrations/0.3.md` are the two surviving
79
+ guides: they collect the few manual steps a config rewrite can't do (recovery
80
+ procedures, a systemd regen, a mode switch, the 0.3.0 breaking-release port).
81
+ Keep the relevant CHANGELOG entries terse and link the matching one
82
+ (`See docs/migrations/0.3.md.`).
82
83
 
83
84
  Migration guides are **not** append-only. A per-version guide is pruned once its
84
85
  content becomes executable (`migrate`), generated (the transform list),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.3.6
3
+ Version: 0.3.8
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
@@ -93,7 +93,7 @@ Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
93
93
  | `start` / `stop` / `kill` | `watch` — peek in a refresh loop |
94
94
  | `restart` / `status` | `monitor` — 13 detectors, alerts, auto-stop |
95
95
  | `round` / `serve` | `events` — query / stream events.jsonl |
96
- | `upgrade` / `migrate` | |
96
+ | `upgrade` / `migrate` | `doctor` — read-only pre-flight |
97
97
 
98
98
  Verb reference: [`docs/commands.md`](docs/commands.md).
99
99
 
@@ -123,7 +123,9 @@ Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
123
123
  `network_fail`, `rate_limit_active`, `anomaly_repetitive_active`,
124
124
  `supervisor_stale`. `mem_pressure` also drives a separate serve-loop
125
125
  admission gate that defers or terminates rounds under real memory pressure —
126
- see `docs/architecture.md`.
126
+ see `docs/architecture.md`. An opt-in soft-brake (`[monitor.host_health.brake]`,
127
+ default off) can additionally cap the serve leaf's `memory.high` under sustained
128
+ pressure on a delegated cgroup.
127
129
 
128
130
  **Auto-stop the service** (continuing is harmful):
129
131
  - `oauth_fail` — burning API quota on auth-rejected rounds
@@ -52,7 +52,7 @@ Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
52
52
  | `start` / `stop` / `kill` | `watch` — peek in a refresh loop |
53
53
  | `restart` / `status` | `monitor` — 13 detectors, alerts, auto-stop |
54
54
  | `round` / `serve` | `events` — query / stream events.jsonl |
55
- | `upgrade` / `migrate` | |
55
+ | `upgrade` / `migrate` | `doctor` — read-only pre-flight |
56
56
 
57
57
  Verb reference: [`docs/commands.md`](docs/commands.md).
58
58
 
@@ -82,7 +82,9 @@ Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
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 —
85
- see `docs/architecture.md`.
85
+ see `docs/architecture.md`. An opt-in soft-brake (`[monitor.host_health.brake]`,
86
+ default off) can additionally cap the serve leaf's `memory.high` under sustained
87
+ pressure on a delegated cgroup.
86
88
 
87
89
  **Auto-stop the service** (continuing is harmful):
88
90
  - `oauth_fail` — burning API quota on auth-rejected rounds
@@ -29,6 +29,7 @@ from agent_runner.config import (
29
29
  RuntimeConfig,
30
30
  ScheduleConfig,
31
31
  VcsConfig,
32
+ _HostHealthBrakeConfig,
32
33
  _HostHealthDiskConfig,
33
34
  _HostHealthMemoryConfig,
34
35
  _HostHealthPressureConfig,
@@ -66,6 +67,7 @@ _SECTIONS: list[tuple[str, type, list[tuple[str, type]]]] = [
66
67
  ("monitor.host_health.disk", _HostHealthDiskConfig),
67
68
  ("monitor.host_health.memory", _HostHealthMemoryConfig),
68
69
  ("monitor.host_health.pressure", _HostHealthPressureConfig),
70
+ ("monitor.host_health.brake", _HostHealthBrakeConfig),
69
71
  ],
70
72
  ),
71
73
  ("phases", PhasesConfig, []),
@@ -32,6 +32,9 @@ from agent_runner._emit.memory import ( # noqa: F401 — public re-export
32
32
  emit_cgroup_growth_rate_warning,
33
33
  emit_host_cgroup_memory_limit,
34
34
  emit_mem_pressure_deferred_to_cgroup,
35
+ emit_memory_high_engaged,
36
+ emit_memory_high_released,
37
+ emit_memory_high_write_failed,
35
38
  emit_round_cgroup_memory,
36
39
  emit_round_deferred,
37
40
  emit_round_mem_critical_sample,
@@ -88,6 +91,9 @@ __all__ = [
88
91
  "emit_mem_loop",
89
92
  "emit_mem_loop_persistent",
90
93
  "emit_mem_pressure_deferred_to_cgroup",
94
+ "emit_memory_high_engaged",
95
+ "emit_memory_high_released",
96
+ "emit_memory_high_write_failed",
91
97
  "emit_plugin_builtin_name_squat",
92
98
  "emit_plugin_checksum_mismatch",
93
99
  "emit_plugin_sandbox_degraded",
@@ -36,6 +36,7 @@ def emit_round_mem_terminated(
36
36
  message: str,
37
37
  consecutive: int,
38
38
  context: dict,
39
+ tier: str = "terminate",
39
40
  ) -> None:
40
41
  """Emit when _spawn_round's mid-round hard floor terminated a ballooning
41
42
  round on critical host_health pressure -- the actual coma-preventer (a
@@ -45,7 +46,15 @@ def emit_round_mem_terminated(
45
46
  ``consecutive`` (the critical_streak that crossed the threshold)
46
47
  and ``context`` (Pressure.context -- the actual psi/mem numbers, e.g.
47
48
  psi_full_avg10) make the kill legible from the event stream alone, so an
48
- operator can retune host_health thresholds without SSH."""
49
+ operator can retune host_health thresholds without SSH.
50
+
51
+ ``tier`` (``"terminate"`` | ``"nudge"``) distinguishes the hard floor's
52
+ ``_terminate_round`` (TERM -> grace -> killpg) from the early cooperative
53
+ nudge (a bare ``proc.terminate()`` two samples earlier, no wait, no
54
+ killpg) -- both fire this SAME event kind so ``round_was_mem_terminated``
55
+ and the ``mem_loop`` give-up breaker count a nudged round exactly like a
56
+ hard-terminated one; the default keeps every existing caller byte-
57
+ compatible."""
49
58
  from agent_runner.events import ROUND_MEM_TERMINATED, emit
50
59
 
51
60
  emit(
@@ -57,6 +66,7 @@ def emit_round_mem_terminated(
57
66
  message=message,
58
67
  consecutive=consecutive,
59
68
  context=context,
69
+ tier=tier,
60
70
  )
61
71
 
62
72
 
@@ -207,13 +217,20 @@ def emit_round_oom_killed(
207
217
  took, no second sysfs read). Symmetric with ``round_mem_terminated``
208
218
  (supervisor-killed) -- this is the kernel-killed sibling.
209
219
 
210
- POINTER-ONLY: ``log_path`` + ``log_bytes`` + ``oom_kill_delta`` -- NEVER the
220
+ POINTER-ONLY: ``log_path`` + ``log_bytes`` + the oom-kill delta -- NEVER the
211
221
  round-log/transcript content itself (durability, secret-redaction, size all
212
222
  argue against embedding it in events.jsonl). ``partial_log`` reports whether
213
223
  the supervisor's own truncation trailer (see ``_mark_partial_log``) was
214
224
  written onto the round log, so a later reader can tell the log's tail is
215
225
  supervisor residue, not the agent's own output.
216
226
 
227
+ The oom-kill delta rides under TWO field names with the identical value:
228
+ ``events_oom_kill_delta`` is the canonical name (sibling-consistent with
229
+ ``round_cgroup_memory``'s ``events_oom_kill_delta``); ``oom_kill_delta`` is
230
+ the retained legacy alias. The append-only event contract keeps both until a
231
+ future breaking release drops the bare alias -- so a single ``oom_kill_delta``
232
+ argument single-sources both fields, which can never diverge.
233
+
217
234
  Classification is UNCHANGED by this event: the round still exits 137 and
218
235
  counts toward the crash streak exactly as before -- this is pure
219
236
  observability layered on top, computed and emitted after that decision is
@@ -226,6 +243,7 @@ def emit_round_oom_killed(
226
243
  round_num=round_num,
227
244
  log_path=str(log_path),
228
245
  log_bytes=log_bytes,
246
+ events_oom_kill_delta=oom_kill_delta,
229
247
  oom_kill_delta=oom_kill_delta,
230
248
  partial_log=partial_log,
231
249
  )
@@ -246,6 +264,59 @@ def emit_mem_pressure_deferred_to_cgroup(
246
264
  emit(log_dir, MEM_PRESSURE_DEFERRED_TO_CGROUP, pid=pid, signal=signal, message=message)
247
265
 
248
266
 
267
+ def emit_memory_high_engaged(
268
+ log_dir: Path,
269
+ *,
270
+ round_num: int,
271
+ previous: str,
272
+ written: int,
273
+ memory_current: int,
274
+ write_ms: int,
275
+ signal: str,
276
+ context: dict,
277
+ rate_mb_per_min: float | None,
278
+ ) -> None:
279
+ """Emit when the reversible cgroup memory.high soft-brake engaged on serve's
280
+ own leaf under sustained warning pressure. ``previous``/``written`` bracket
281
+ the reversible write; ``signal``/``context`` are the triggering Pressure;
282
+ ``rate_mb_per_min`` rides along as growth-rate context for Pi calibration."""
283
+ from agent_runner.events import MEMORY_HIGH_ENGAGED, emit
284
+
285
+ emit(
286
+ log_dir,
287
+ MEMORY_HIGH_ENGAGED,
288
+ round_num=round_num,
289
+ previous=previous,
290
+ written=written,
291
+ memory_current=memory_current,
292
+ write_ms=write_ms,
293
+ signal=signal,
294
+ context=context,
295
+ rate_mb_per_min=rate_mb_per_min,
296
+ )
297
+
298
+
299
+ def emit_memory_high_released(log_dir: Path, *, round_num: int, reason: str) -> None:
300
+ """Emit when the soft-brake was restored to its stashed value. ``reason`` is
301
+ ``recovered`` (N healthy ticks) or ``round_end`` (the finally restore, which
302
+ covers every exit path out of the round incl. wedged/exception)."""
303
+ from agent_runner.events import MEMORY_HIGH_RELEASED, emit
304
+
305
+ emit(log_dir, MEMORY_HIGH_RELEASED, round_num=round_num, reason=reason)
306
+
307
+
308
+ def emit_memory_high_write_failed(log_dir: Path, *, round_num: int, errno: int | None) -> None:
309
+ """Emit once when a memory.high write (engage or restore) hit an OSError --
310
+ fail-open: the brake disarms for the round and serve continues unthrottled,
311
+ exactly as when the brake is configured off. ``errno`` is the real OSError
312
+ code from the engage path; the restore path (which only sees a bool from
313
+ :func:`agent_runner.metrics.restore_leaf_memory_high`) passes ``None`` --
314
+ never ``0``, which conventionally reads as POSIX success."""
315
+ from agent_runner.events import MEMORY_HIGH_WRITE_FAILED, emit
316
+
317
+ emit(log_dir, MEMORY_HIGH_WRITE_FAILED, round_num=round_num, errno=errno)
318
+
319
+
249
320
  def emit_cgroup_growth_rate_warning(
250
321
  log_dir: Path,
251
322
  *,
@@ -14,11 +14,12 @@ from dataclasses import dataclass, field
14
14
  from pathlib import Path
15
15
  from typing import Any
16
16
 
17
+ from agent_runner import event_log
18
+ from agent_runner._notify import NULL_LISTENER
17
19
  from agent_runner.api_types import ProjectState, ServiceMode, ServiceStatus, SystemMetrics
18
20
  from agent_runner.builtin_plugins._constants import _TAIL_LINES
19
- from agent_runner.clock import SYSTEM_CLOCK
20
21
  from agent_runner.context_store import read_json
21
- from agent_runner.events import iter_event_dicts, read_new
22
+ from agent_runner.events import iter_event_dicts
22
23
 
23
24
 
24
25
  @dataclass(frozen=True)
@@ -29,7 +30,7 @@ class LocalSource:
29
30
  log_dir: Path
30
31
 
31
32
  def events_files(self) -> list[Path]:
32
- return sorted(self.log_dir.glob("events-*.jsonl"))
33
+ return event_log.all_month_files(self.log_dir)
33
34
 
34
35
  def metrics_files(self) -> list[Path]:
35
36
  return sorted(self.log_dir.glob("metrics-*.jsonl"))
@@ -75,7 +76,7 @@ class _EventTail:
75
76
  )
76
77
 
77
78
  def read(self, files: list[Path]) -> list[dict[str, Any]]:
78
- new_events, self.offsets = read_new(files, self.offsets)
79
+ new_events, self.offsets = event_log.read_new(files, self.offsets)
79
80
  self.buffer.extend(new_events)
80
81
  return list(self.buffer)
81
82
 
@@ -93,23 +94,22 @@ def _tail_events_jsonl(
93
94
  byte 0 of every file present at start (human-narrate use case).
94
95
 
95
96
  Follows file rotation transparently — when a new events-YYYY-MM.jsonl
96
- appears, it is picked up from byte 0.
97
+ appears, it is picked up from byte 0. A thin client over the shared
98
+ :func:`event_log.follow` loop (also used by ``events --tail``); polls
99
+ (``NULL_LISTENER`` -- no doorbell) rather than a live FIFO listener.
97
100
  """
98
- offsets: dict[Path, int] = {}
101
+ seed: dict[Path, int] = {}
99
102
  if start_at_now:
100
- for path in sorted(log_dir.glob("events-*.jsonl")):
101
- try:
102
- offsets[path] = path.stat().st_size
103
- except FileNotFoundError:
104
- continue
105
-
106
- while True:
107
- files = sorted(log_dir.glob("events-*.jsonl"))
108
- new_events, offsets = read_new(files, offsets)
109
- if new_events:
110
- yield from new_events
111
- else:
112
- SYSTEM_CLOCK.sleep(poll_interval_s)
103
+ seed = event_log.seed_at_eof(event_log.all_month_files(log_dir))
104
+
105
+ for _line, obj in event_log.follow(
106
+ log_dir,
107
+ event_log.all_month_files,
108
+ wake=NULL_LISTENER,
109
+ timeout_s=poll_interval_s,
110
+ offsets=seed,
111
+ ):
112
+ yield obj
113
113
 
114
114
 
115
115
  _MAX_TAIL_FILES = 20
@@ -18,6 +18,7 @@ from datetime import UTC, datetime
18
18
  from pathlib import Path
19
19
  from typing import Any
20
20
 
21
+ from agent_runner import event_log
21
22
  from agent_runner._notify import NULL_LISTENER, Listener, NullListener
22
23
  from agent_runner.api_types import TransientErrorState
23
24
  from agent_runner.clock import SYSTEM_CLOCK, Clock
@@ -152,9 +153,10 @@ def _tail_events(log_dir: Path) -> Iterator[dict[str, Any]]:
152
153
 
153
154
  Forward, OLD-then-NEW order matters (INVARIANT 2 for callers that merge a
154
155
  per-key "latest wins" map: iterating oldest file first means a later event
155
- for the same key naturally overwrites an earlier one)."""
156
- for path in sorted(log_dir.glob("events-*.jsonl"))[-2:]:
157
- yield from _iter_events(path)
156
+ for the same key naturally overwrites an earlier one). STATELESS full
157
+ re-scan by design (see :func:`event_log.scan`'s docstring) -- never the
158
+ offset-carrying ``follow``/``read_new`` path."""
159
+ yield from event_log.scan(log_dir, event_log.newest_scope(2))
158
160
 
159
161
 
160
162
  def _scan_events_for_transient(path: Path):
@@ -185,8 +187,7 @@ def _latest_unrecovered_detected(log_dir: Path) -> dict[str, Any] | None:
185
187
  Scans the newest monthly ``events-*.jsonl`` and, only if it holds no transient
186
188
  event yet, the previous month's — so a throttle that spans a month boundary
187
189
  (detected in the old file, cleared in the new) is not orphaned."""
188
- candidates = sorted(log_dir.glob("events-*.jsonl"))
189
- for path in reversed(candidates[-2:]):
190
+ for path in reversed(event_log.newest_month_files(log_dir, 2)):
190
191
  result = _scan_events_for_transient(path)
191
192
  if result is not _NO_TRANSIENT:
192
193
  return result # a detected dict, or None (latest transient was recovered)
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.3.6'
22
- __version_tuple__ = version_tuple = (0, 3, 6)
21
+ __version__ = version = '0.3.8'
22
+ __version_tuple__ = version_tuple = (0, 3, 8)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -41,6 +41,9 @@ from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-ex
41
41
  emit_mem_loop,
42
42
  emit_mem_loop_persistent,
43
43
  emit_mem_pressure_deferred_to_cgroup,
44
+ emit_memory_high_engaged,
45
+ emit_memory_high_released,
46
+ emit_memory_high_write_failed,
44
47
  emit_plugin_builtin_name_squat,
45
48
  emit_plugin_checksum_mismatch,
46
49
  emit_plugin_sandbox_degraded,
@@ -22,6 +22,8 @@ from agent_runner import metrics
22
22
  from agent_runner._serve_policy import _ROUND_UNREAPED_RC
23
23
  from agent_runner.api import (
24
24
  emit_host_cgroup_memory_limit,
25
+ emit_memory_high_released,
26
+ emit_memory_high_write_failed,
25
27
  emit_round_cgroup_memory,
26
28
  emit_round_oom_killed,
27
29
  )
@@ -37,6 +39,27 @@ from agent_runner.api import (
37
39
  # round.
38
40
  _ROUND_CGROUP_STATE_BY_LOG_DIR: dict[Path, dict] = {}
39
41
 
42
+ # Boot-armed soft-brake gate: True iff the brake is configured on AND this
43
+ # process's own cgroup leaf is delegated (memory.high writable) -- probed ONCE
44
+ # at serve boot (_probe_and_emit_cgroup_defer already reads cgroup_delegated),
45
+ # read every mid-round tick by _serve_round via _brake_step_for. Same
46
+ # per-log_dir stable-default pattern as _ROUND_CGROUP_STATE_BY_LOG_DIR (one
47
+ # entry per serve process in production); cmd() cannot grow to thread it (145
48
+ # budget), so it rides in a module dict, not an argument.
49
+ _BRAKE_ARMED_BY_LOG_DIR: dict[Path, bool] = {}
50
+
51
+
52
+ def _brake_step_for(log_dir: Path, host_health_cfg) -> int | None:
53
+ """The armed soft-brake step_pct for this log_dir, or None when the brake is
54
+ disarmed: config off, or the boot probe found the leaf undelegated / no
55
+ cgroup v2. The step itself is single-sourced from config; the dict only
56
+ carries the boot-time delegated-and-on decision."""
57
+ if not host_health_cfg.brake.memory_high:
58
+ return None
59
+ if not _BRAKE_ARMED_BY_LOG_DIR.get(log_dir, False):
60
+ return None
61
+ return host_health_cfg.brake.memory_high_step_pct
62
+
40
63
 
41
64
  def _stash_round_cgroup_state(
42
65
  log_dir: Path, cg_base: dict, peak_current: int, peak_swap: int
@@ -190,18 +213,69 @@ def _maybe_emit_oom_killed(
190
213
  # report ask #3). Advisory only -- never changes the operator's cgroup/unit.
191
214
  _SWAP_CAP_ADVISORY_PCT = 25.0
192
215
 
193
- # The delegation-readiness advisory: memory.max/memory.high are bound on this
194
- # cgroup, but metrics.cgroup_delegated found the leaf is NOT delegated
195
- # (systemd Delegate=yes) -- a future release that wants to write memory.high
196
- # on a round-scoped nested cgroup needs delegation first. Advisory only; this
197
- # probe and its hint never write to the cgroup or the unit.
216
+ # The delegation-readiness / brake-inert advisory. Fires in two DISTINCT
217
+ # cases, both meaning "memory.high can't be written from here":
218
+ # (1) delegated is False (CONFIRMED not delegated -- systemd Delegate=yes
219
+ # is absent) AND memory.max/memory.high are already bound (memory_high
220
+ # set, or the own_scope "add memory.high" hint above just fired) --
221
+ # the existing config is informational-only without delegation.
222
+ # (2) the operator explicitly turned the soft-brake on
223
+ # ([monitor.host_health.brake] memory_high=true, threaded in here as
224
+ # brake_memory_high) but delegated is not True (False OR None) -- the
225
+ # brake will silently do nothing, worth announcing at boot even with
226
+ # no memory.max/memory.high configured yet.
227
+ # Deliberately NOT ``delegated is not True`` across the board: cgroup_delegated
228
+ # returns None for two different reasons -- cgroup v2 unavailable (harmless)
229
+ # AND the leaf's own os.stat() raising OSError (delegation status genuinely
230
+ # UNKNOWN, even though memory_high/own_scope's ancestor-walk resolved fine).
231
+ # Broadening case (1) to None would assert "not writable" where we actually
232
+ # don't know -- an overclaim this release can't afford. Case (2) is the one
233
+ # exception: the operator asked for the brake, so ANY non-confirmed-delegated
234
+ # state (False or unknown) is worth flagging as "may be inert" up front.
235
+ # Advisory only; this probe and its hint never write to the cgroup or the unit.
198
236
  _UNDELEGATED_HINT = (
199
- "this cgroup is not delegated (systemd Delegate=yes) -- memory.high can be "
200
- "read but not managed here until it is"
237
+ "memory.high on this cgroup is not writable by this process; the soft-brake "
238
+ "is inert. Run serve as a user-mode unit (`systemctl --user` + `loginctl "
239
+ "enable-linger`) or as a root system unit."
201
240
  )
202
241
 
203
242
 
204
- def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
243
+ def brake_report_state(enabled: bool, delegated: bool | None) -> str:
244
+ """The operator-facing soft-brake state for doctor/peek: ``off`` (not
245
+ configured), ``armed`` (on AND the leaf is delegated so memory.high is
246
+ writable), or ``inert(<reason>)`` (on but the write path can't arm)."""
247
+ if not enabled:
248
+ return "off"
249
+ if delegated is True:
250
+ return "armed"
251
+ return "inert(undelegated)" if delegated is False else "inert(no cgroup v2)"
252
+
253
+
254
+ def current_brake_state(cfg) -> str:
255
+ """Resolve the soft-brake state string for `cfg` from live cgroup facts --
256
+ the one-shot ``cgroup_memory_limits() -> cgroup_delegated(self_cgroup=...) ->
257
+ brake_report_state`` sequence, shared by peek (cli.common.emit) and doctor
258
+ so neither re-derives it inline."""
259
+ limits = metrics.cgroup_memory_limits()
260
+ delegated = metrics.cgroup_delegated(self_cgroup=limits["cgroup_path"])
261
+ return brake_report_state(cfg.monitor.host_health.brake.memory_high, delegated)
262
+
263
+
264
+ def _release_brake(log_dir: Path, round_num: int, previous: str, *, reason: str) -> bool:
265
+ """Restore memory.high on serve's own leaf to `previous` and emit the
266
+ matching event: ``memory_high_released`` on success, ``memory_high_write_failed``
267
+ (errno None -- restore only yields a bool) on OSError. Returns whether the
268
+ restore succeeded; callers own their own brake_engaged/brake_previous/
269
+ brake_restore_failed transitions off that result. Shared by _spawn_round's
270
+ mid-round recovery-release site and its unconditional round-end finally."""
271
+ if metrics.restore_leaf_memory_high(previous):
272
+ emit_memory_high_released(log_dir, round_num=round_num, reason=reason)
273
+ return True
274
+ emit_memory_high_write_failed(log_dir, round_num=round_num, errno=None)
275
+ return False
276
+
277
+
278
+ def _probe_and_emit_cgroup_defer(log_dir: Path, *, brake_memory_high: bool = False) -> bool:
205
279
  """Probe this process's cgroup v2 memory budget once at serve startup,
206
280
  emit host_cgroup_memory_limit for observability, and return whether the
207
281
  mid-round hard floor should defer to kernel cgroup-OOM: True only when
@@ -249,8 +323,22 @@ def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
249
323
  the bounding ancestor's ``memory.high`` soft-throttle threshold
250
324
  (``metrics.cgroup_memory_high``), ``None`` when unset -- for the same
251
325
  reason: an operator asking "is MemoryHigh even set" is exactly this
252
- release's field ask. Advisory only -- this never changes the operator's
253
- cgroup or systemd unit."""
326
+ release's field ask.
327
+
328
+ A THIRD advisory, ``_UNDELEGATED_HINT``, reads ``brake_memory_high`` --
329
+ the caller-supplied ``[monitor.host_health.brake] memory_high`` config
330
+ switch -- alongside ``delegated``: when the operator turned that brake
331
+ on but this process's own cgroup leaf isn't confirmed delegated
332
+ (``delegated is not True``), the brake would silently write nothing, so
333
+ boot announces it up front even with no memory.max/memory.high
334
+ configured. Independently of the brake, the SAME hint also covers the
335
+ pre-existing "config is informational without delegation" case
336
+ (``delegated is False`` -- CONFIRMED not delegated, never the merely
337
+ unresolved ``None``) when memory_high is already set or the own_scope
338
+ hint above just fired. See the comment block above ``_UNDELEGATED_HINT``
339
+ for why those two triggers use different ``delegated`` strictness.
340
+ Advisory only -- this never changes the operator's cgroup or systemd
341
+ unit."""
254
342
  limits = metrics.cgroup_memory_limits()
255
343
  swap_total = metrics.swap_total_bytes()
256
344
  # Reuse the leaf cgroup_memory_limits already resolved above -- skips
@@ -263,6 +351,8 @@ def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
263
351
  # way as calling with no override would.
264
352
  memory_high = metrics.cgroup_memory_high(self_cgroup=limits["cgroup_path"])
265
353
  delegated = metrics.cgroup_delegated(self_cgroup=limits["cgroup_path"])
354
+ if brake_memory_high:
355
+ _BRAKE_ARMED_BY_LOG_DIR[log_dir] = delegated is True
266
356
  swap_max = limits["memory_swap_max"]
267
357
  swap_cap_pct = (
268
358
  round(100.0 * swap_max / swap_total, 1) if swap_max is not None and swap_total > 0 else None
@@ -299,7 +389,9 @@ def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
299
389
  "throttle's PSI-full rise doesn't trip the mid-round floor"
300
390
  )
301
391
  advisories.append(hint)
302
- if delegated is False and (memory_high is not None or own_scope):
392
+ if (brake_memory_high and delegated is not True) or (
393
+ delegated is False and (memory_high is not None or own_scope)
394
+ ):
303
395
  advisories.append(_UNDELEGATED_HINT)
304
396
  advisory = "; ".join(advisories) or None
305
397
  if advisory is not None: