cli-agent-runner 0.2.18__tar.gz → 0.2.19__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 (363) hide show
  1. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/BACKLOG.md +35 -0
  2. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/CHANGELOG.md +15 -0
  3. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/PKG-INFO +2 -1
  4. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/README.md +1 -0
  5. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/__init__.py +6 -1
  6. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_docgen.py +97 -1
  7. cli_agent_runner-0.2.19/agent_runner/_emit/__init__.py +103 -0
  8. cli_agent_runner-0.2.19/agent_runner/_emit/agent.py +231 -0
  9. cli_agent_runner-0.2.19/agent_runner/_emit/memory.py +236 -0
  10. cli_agent_runner-0.2.19/agent_runner/_emit/rounds.py +189 -0
  11. cli_agent_runner-0.2.19/agent_runner/_emit/serve.py +193 -0
  12. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_monitor_detectors.py +8 -8
  13. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_monitor_state.py +9 -17
  14. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_plugin_scan.py +22 -3
  15. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_resolve.py +1 -1
  16. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_round_outcome.py +13 -13
  17. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_round_support.py +1 -1
  18. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_serve_policy.py +32 -10
  19. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_throttle.py +7 -7
  20. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_version.py +2 -2
  21. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/agent_runtime.py +23 -7
  22. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/api.py +60 -24
  23. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/api_types.py +1 -1
  24. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/builtin_plugins/claude_rate_limit.py +5 -5
  25. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/builtin_plugins/default_dirty_handler.py +1 -1
  26. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/builtin_plugins/gemini.py +1 -1
  27. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/builtin_plugins/kimi.py +1 -1
  28. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/builtin_plugins/pi.py +3 -3
  29. cli_agent_runner-0.2.19/agent_runner/cli/_serve_cgroup.py +273 -0
  30. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/_serve_round.py +37 -253
  31. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/common.py +23 -0
  32. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/events_cmd.py +1 -1
  33. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/monitor_cmd.py +16 -1
  34. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/round_cmd.py +7 -10
  35. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/serve_cmd.py +14 -14
  36. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/config/errors.py +1 -1
  37. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/config/models.py +10 -10
  38. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/config/validators.py +1 -1
  39. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/hooks.py +6 -7
  40. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/metrics.py +44 -12
  41. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/migrations.py +3 -3
  42. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/monitor.py +1 -2
  43. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/remote_relay.py +18 -2
  44. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/runner.py +3 -3
  45. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/service_unit.py +5 -10
  46. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/startup_check.py +1 -1
  47. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/vcs_state.py +3 -3
  48. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/README.md +1 -0
  49. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/long-running-agents.md +9 -4
  50. cli_agent_runner-0.2.19/docs/memory-efficiency.md +244 -0
  51. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/runbook.md +31 -17
  52. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_dirty_handler_seam.py +13 -1
  53. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_grace_kill_emission.py +5 -2
  54. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_architecture.py +107 -5
  55. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_doc_claims_match_ssot.py +20 -0
  56. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_import_footprint.py +5 -0
  57. cli_agent_runner-0.2.19/tests/invariants/test_no_version_refs.py +173 -0
  58. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_timeout_budget_invariant.py +10 -1
  59. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_agent_runtime.py +25 -17
  60. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_agent_runtime_grace.py +82 -28
  61. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_agent_runtime_marker_scan.py +8 -4
  62. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_agent_runtime_progress.py +19 -6
  63. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_agent_runtime_reap.py +47 -5
  64. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_cgroup_probe.py +6 -6
  65. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_cli_service_peek_monitor.py +9 -2
  66. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_docgen.py +87 -0
  67. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_init_entry_points.py +19 -0
  68. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_metrics_cgroup_usage.py +29 -0
  69. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_loop.py +58 -13
  70. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_remote.py +128 -9
  71. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_plugin_scan_parity.py +32 -0
  72. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_mem_gate.py +1 -1
  73. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_round_cgroup_spine.py +19 -19
  74. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_round_oom_killed.py +22 -22
  75. cli_agent_runner-0.2.18/agent_runner/_emit.py +0 -865
  76. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/.codecov.yml +0 -0
  77. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  78. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  79. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  80. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  81. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/.github/workflows/ci.yml +0 -0
  82. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/.github/workflows/release.yml +0 -0
  83. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/.gitignore +0 -0
  84. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/.vulture-whitelist.py +0 -0
  85. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/CODE_OF_CONDUCT.md +0 -0
  86. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/CONTRIBUTING.md +0 -0
  87. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/LICENSE +0 -0
  88. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/README.zh.md +0 -0
  89. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/SECURITY.md +0 -0
  90. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_monitor_registry.py +0 -0
  91. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_redact.py +0 -0
  92. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_registry.py +0 -0
  93. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/_substrate.py +0 -0
  94. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/builtin_plugins/__init__.py +0 -0
  95. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/builtin_plugins/_constants.py +0 -0
  96. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/builtin_plugins/codewhale.py +0 -0
  97. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/__init__.py +0 -0
  98. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/__main__.py +0 -0
  99. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/init_cmd.py +0 -0
  100. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/install_cmd.py +0 -0
  101. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/migrate_cmd.py +0 -0
  102. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/peek_cmd.py +0 -0
  103. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/service_cmd.py +0 -0
  104. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/cli/upgrade_cmd.py +0 -0
  105. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/clock.py +0 -0
  106. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/config/__init__.py +0 -0
  107. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/config/loader.py +0 -0
  108. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/config/parsers.py +0 -0
  109. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/context_store.py +0 -0
  110. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/defenses.py +0 -0
  111. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/events.py +0 -0
  112. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/host_health.py +0 -0
  113. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/http_progress.py +0 -0
  114. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/lifecycle.py +0 -0
  115. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/phase_select.py +0 -0
  116. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/presets/__init__.py +0 -0
  117. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/presets/aider.toml +0 -0
  118. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/presets/claude.toml +0 -0
  119. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/presets/codewhale.toml +0 -0
  120. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/presets/gemini.toml +0 -0
  121. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/presets/kimi.toml +0 -0
  122. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/presets/pi.toml +0 -0
  123. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/prompt_loader.py +0 -0
  124. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/round_log.py +0 -0
  125. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/round_view.py +0 -0
  126. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/scaffold.py +0 -0
  127. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/agent_runner/schedule.py +0 -0
  128. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/build.sh +0 -0
  129. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/architecture.md +0 -0
  130. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/commands.md +0 -0
  131. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/configuration.md +0 -0
  132. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/events.md +0 -0
  133. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/migrations/0.2.md +0 -0
  134. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/plugins.md +0 -0
  135. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/quickstart.md +0 -0
  136. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/recipes/aider.md +0 -0
  137. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/recipes/codewhale.md +0 -0
  138. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/recipes/kimi.md +0 -0
  139. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/recipes/pi.md +0 -0
  140. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/docs/thesis.md +0 -0
  141. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/pyproject.toml +0 -0
  142. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/__init__.py +0 -0
  143. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/_clock.py +0 -0
  144. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/_test_helpers.py +0 -0
  145. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/conftest.py +0 -0
  146. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/contract/__init__.py +0 -0
  147. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/contract/test_public_api_surface.py +0 -0
  148. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/e2e/__init__.py +0 -0
  149. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/e2e/conftest.py +0 -0
  150. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  151. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/e2e/test_e2e_install_systemd.py +0 -0
  152. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  153. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  154. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  155. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  156. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  157. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/generate_vulture_whitelist.py +0 -0
  158. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/__init__.py +0 -0
  159. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_auto_stop_e2e.py +0 -0
  160. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_bounded_run.py +0 -0
  161. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_context_enricher_namespacing.py +0 -0
  162. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_fresh_eyes_signal.py +0 -0
  163. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_install_dry_run.py +0 -0
  164. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_monitor_seeded.py +0 -0
  165. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_plugin_detector_loaded.py +0 -0
  166. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_plugin_owned_paths.py +0 -0
  167. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_plugin_real_flow.py +0 -0
  168. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  169. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  170. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_scaffold_presets.py +0 -0
  171. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_serve_loop.py +0 -0
  172. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_spawn_round_mem_floor.py +0 -0
  173. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_spawn_round_wedged.py +0 -0
  174. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_substrate_fingerprint.py +0 -0
  175. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/integration/test_transient_error_backoff.py +0 -0
  176. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/__init__.py +0 -0
  177. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/_docs.py +0 -0
  178. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/_event_scan.py +0 -0
  179. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_atomic_write_enforced.py +0 -0
  180. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_authored_facts_declared.py +0 -0
  181. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  182. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_catalogs.py +0 -0
  183. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_classification_ssot.py +0 -0
  184. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_config_error_consistency.py +0 -0
  185. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_config_value_set_ssot.py +0 -0
  186. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
  187. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  188. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_doc_cli_claims.py +0 -0
  189. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_doc_operator_surface.py +0 -0
  190. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
  191. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  192. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_doc_usage_event_fields.py +0 -0
  193. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_docs_generated.py +0 -0
  194. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_docs_index_complete.py +0 -0
  195. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_entry_points_resolve.py +0 -0
  196. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_event_int_coercion.py +0 -0
  197. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_event_kind_registry.py +0 -0
  198. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_event_kinds_ssot.py +0 -0
  199. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_events_doc_contract.py +0 -0
  200. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
  201. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_hook_contract_docs.py +0 -0
  202. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_layer_2_loop_size.py +0 -0
  203. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_migrations_registry_ssot.py +0 -0
  204. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_module_boundaries.py +0 -0
  205. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_module_sizes.py +0 -0
  206. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_monitor_self_exclusion.py +0 -0
  207. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_no_ai_signatures.py +0 -0
  208. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_no_internal_refs.py +0 -0
  209. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_no_price_logic.py +0 -0
  210. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  211. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_no_raw_time.py +0 -0
  212. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_no_vacuous_invariants.py +0 -0
  213. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_peek_schema_version.py +0 -0
  214. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_phase_select_stateless.py +0 -0
  215. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_plugins_config_stable.py +0 -0
  216. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  217. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  218. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_round_alloc_growth.py +0 -0
  219. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_round_log_decode_boundary.py +0 -0
  220. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_round_num_single_source.py +0 -0
  221. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_round_result_stable.py +0 -0
  222. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  223. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  224. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_upstream_schema_canary.py +0 -0
  225. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  226. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/literate/__init__.py +0 -0
  227. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/literate/parser.py +0 -0
  228. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/literate/test_parser.py +0 -0
  229. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/literate/test_quickstart.py +0 -0
  230. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/__init__.py +0 -0
  231. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  232. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_api_assemble_prompt.py +0 -0
  233. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_api_events_stream.py +0 -0
  234. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_api_install.py +0 -0
  235. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_api_observation.py +0 -0
  236. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_api_read_round_num.py +0 -0
  237. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_api_resolve_phase.py +0 -0
  238. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_api_service.py +0 -0
  239. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_api_types.py +0 -0
  240. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_auto_stop_gating.py +0 -0
  241. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_catalogs.py +0 -0
  242. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_claude_error_detector.py +0 -0
  243. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_claude_foreign_schema.py +0 -0
  244. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_cli.py +0 -0
  245. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_cli_common.py +0 -0
  246. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_cli_init_install.py +0 -0
  247. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_cli_main_boundary.py +0 -0
  248. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_cli_monitor_http.py +0 -0
  249. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_cli_upgrade.py +0 -0
  250. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_codewhale_code_precedence.py +0 -0
  251. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_codewhale_plugin.py +0 -0
  252. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_coerce_int_poison.py +0 -0
  253. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_config.py +0 -0
  254. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_config_fresh_eyes.py +0 -0
  255. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_config_max_rounds.py +0 -0
  256. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_config_nested_strictness.py +0 -0
  257. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_config_prompt_delivery.py +0 -0
  258. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_config_schedule.py +0 -0
  259. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_config_stop_file.py +0 -0
  260. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_config_strictness.py +0 -0
  261. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  262. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_config_transient_error_action.py +0 -0
  263. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_context_store.py +0 -0
  264. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_control_char_config.py +0 -0
  265. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_default_dirty_handler.py +0 -0
  266. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_defenses.py +0 -0
  267. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_detect_hung_latch.py +0 -0
  268. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_detector_isolation.py +0 -0
  269. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_detector_protocol.py +0 -0
  270. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_dirty_handlers.py +0 -0
  271. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_effective_throttle_view.py +0 -0
  272. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_emit_config_migrated.py +0 -0
  273. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_emit_schedule.py +0 -0
  274. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
  275. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_events.py +0 -0
  276. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_events_cmd.py +0 -0
  277. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_events_cmd_rollover.py +0 -0
  278. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_events_cmd_tail.py +0 -0
  279. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_events_jsonl_decode.py +0 -0
  280. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_example_dirty_handler.py +0 -0
  281. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_example_plugin.py +0 -0
  282. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_exit_cause.py +0 -0
  283. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_exit_classifier.py +0 -0
  284. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  285. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_gemini_plugin.py +0 -0
  286. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_git_timeout.py +0 -0
  287. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_hook_failure_isolation.py +0 -0
  288. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_hook_redact_order.py +0 -0
  289. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_hooks.py +0 -0
  290. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_host_health.py +0 -0
  291. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_http_progress.py +0 -0
  292. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_http_progress_epoch.py +0 -0
  293. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
  294. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_kimi_plugin.py +0 -0
  295. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_lifecycle.py +0 -0
  296. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_metrics.py +0 -0
  297. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_metrics_swap.py +0 -0
  298. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_migrate_cmd.py +0 -0
  299. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_migrations.py +0 -0
  300. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_assembly.py +0 -0
  301. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_cmd_severity.py +0 -0
  302. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_dedup_identity.py +0 -0
  303. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_dedup_rearm.py +0 -0
  304. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  305. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  306. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  307. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_detectors.py +0 -0
  308. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_event_tail.py +0 -0
  309. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_on_alert.py +0 -0
  310. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
  311. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_monitor_schedule_pause.py +0 -0
  312. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_network_blip_signal_guard.py +0 -0
  313. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_open_round_log.py +0 -0
  314. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_orphan_chain_single_pass.py +0 -0
  315. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_outer_round_ceiling.py +0 -0
  316. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_peek_argparse.py +0 -0
  317. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_peek_project_isolation.py +0 -0
  318. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_peek_schedule.py +0 -0
  319. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_peek_select.py +0 -0
  320. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_phase_agent_launch.py +0 -0
  321. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_phase_profiles.py +0 -0
  322. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_phase_select.py +0 -0
  323. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_pi_plugin.py +0 -0
  324. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_plugin_constants.py +0 -0
  325. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_presets.py +0 -0
  326. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_primary_prompt_file_home.py +0 -0
  327. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_prompt_loader.py +0 -0
  328. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_read_json_hardening.py +0 -0
  329. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_redact.py +0 -0
  330. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_resolve.py +0 -0
  331. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_round_cmd.py +0 -0
  332. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_round_cmd_sigterm.py +0 -0
  333. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_round_log_helpers.py +0 -0
  334. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_round_outcome.py +0 -0
  335. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_round_outcome_agent_axis.py +0 -0
  336. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_round_view.py +0 -0
  337. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_runner.py +0 -0
  338. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_runner_sidecar_ordering.py +0 -0
  339. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_runner_throttle.py +0 -0
  340. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_scaffold.py +0 -0
  341. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_schedule.py +0 -0
  342. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_cmd_bounded.py +0 -0
  343. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_config_broken.py +0 -0
  344. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_crash_loop.py +0 -0
  345. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_phase_schedule.py +0 -0
  346. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_policy.py +0 -0
  347. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_round.py +0 -0
  348. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_round_log.py +0 -0
  349. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_round_terminate.py +0 -0
  350. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_schedule_gate.py +0 -0
  351. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_sentinel.py +0 -0
  352. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_serve_startup_hooks.py +0 -0
  353. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_service_mode.py +0 -0
  354. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_service_unit.py +0 -0
  355. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_startup_battery_exit.py +0 -0
  356. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_startup_check.py +0 -0
  357. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_substrate.py +0 -0
  358. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
  359. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_throttle_back_off_ladder.py +0 -0
  360. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_throttle_epoch_bound.py +0 -0
  361. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_throttle_plugin_classification.py +0 -0
  362. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_upgrade_migrate.py +0 -0
  363. {cli_agent_runner-0.2.18 → cli_agent_runner-0.2.19}/tests/unit/test_vcs_state.py +0 -0
@@ -88,3 +88,38 @@ just succeeded in the same repo).
88
88
  Deferred because closing it is a naming decision, not cleanup: no event kind
89
89
  carries the meaning "stashed but ref lost" (`orphan_stash_failed` would be wrong
90
90
  — the stash exists). Left as-is until that kind is designed.
91
+
92
+ ## `_monitor_loop_iter`'s three fail-open guards are hand-duplicated
93
+
94
+ `api._monitor_loop_iter` wraps the startup `monitor_started` emit, the
95
+ per-tick poll, and `on_alert`'s per-alert dispatch each in its own
96
+ `try/except Exception: warnings.warn(...)` block so a crash in any one of the
97
+ three never ends monitor supervision. The three guards are structurally
98
+ identical (catch, format a `type(e).__name__: {e}` warning, keep the loop
99
+ alive) but hand-copied rather than sharing one implementation; `on_alert`'s
100
+ own `emit()` closure already shows the pattern for factoring a shared guard.
101
+
102
+ Collapsing them to one shared guard is behavior-preserving — it must not
103
+ change which failures warn vs which propagate, nor the specific fallback
104
+ each site takes on failure (sleep-and-retry after a poll failure,
105
+ `verdict = "failed"` after an `on_alert` failure). Slated for 0.2.19.
106
+
107
+ ## `oom_kill_delta` is named differently on its two events — NEEDS_DESIGN, DEFERRED to 0.3
108
+
109
+ The same cgroup `memory.events.oom_kill` delta is emitted under two
110
+ different field names depending on which event carries it: `round_cgroup_memory`
111
+ reports it as `events_oom_kill_delta` (matching its sibling counters
112
+ `events_high_delta`/`events_max_delta`/`events_oom_delta`), while
113
+ `round_oom_killed` — folded from the same scan, no second sysfs read —
114
+ reports the identical value as bare `oom_kill_delta`. A reader correlating
115
+ the two events for the same round has to know the same quantity carries two
116
+ names.
117
+
118
+ Renaming either side is a change to a published event field name — part of
119
+ the peek/event JSON contract — so which name becomes canonical (and whether
120
+ it lands as a rename or an additive alias) needs a decision, not a silent
121
+ swap. Tagged NEEDS_DESIGN. Deferred to 0.3's event-model canonicalization
122
+ (see [[project-03-direction]]) rather than reconciled standalone in 0.2.19 —
123
+ 0.3 is already renaming/unifying event fields wholesale, and settling this
124
+ name now risks a double-rename (once here, again when 0.3's canonical model
125
+ lands). No field name changes in code until that decision is made.
@@ -5,6 +5,21 @@ 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.2.19] - 2026-09-09
9
+
10
+ ### Changed
11
+ - `monitor --host` relay now drains cleanly on SIGTERM — tears down its ssh child group and exits 0 — instead of dying immediately, matching `serve`'s stop-signal behavior.
12
+ - Internal structure, behavior-identical: `_emit.py` split into an `_emit/` package (domain submodules behind a facade); the cgroup-pressure spine carved out of `cli/_serve_round.py` into `cli/_serve_cgroup.py`.
13
+ - The give-up exit-code table in the runbook is now generated from code instead of hand-copied; detector/defense counts and the `peek` schema version are now invariant-pinned against the same source.
14
+ - Internal hardening: the monitor's fail-open guards are unified into one implementation; `kill`'s systemd-user SIGKILL escalation shares the same bounded poll `serve` uses; the cgroup pressure spine resolves its bounding ancestor once per round instead of on every tick; a single-implementation `Protocol` was collapsed to its concrete type; a process's stored child-name no longer passes an attacker-rewritable `argv[0]` through unbounded; plugin discovery now scans legacy egg-info installs and handles entry points with an extras suffix.
15
+
16
+ ### Fixed
17
+ - Docs: the `agent_usage_recorded` plugin list corrected to claude/gemini/codewhale/pi (kimi's built-in plugin emits no token counters).
18
+
19
+ ### Notes
20
+ - Not breaking: no `agent-runner migrate` step, no systemd unit change. The relay's SIGTERM-drain above is the one behavior change in this release — anyone relying on the relay's old immediate-exit-on-SIGTERM will now see it drain instead.
21
+ - `oom_kill_delta`'s two-different-field-names inconsistency is deferred to 0.3's event-model work rather than reconciled here, to avoid a double-rename.
22
+
8
23
  ## [0.2.18] - 2026-09-08
9
24
 
10
25
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.2.18
3
+ Version: 0.2.19
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
@@ -152,6 +152,7 @@ your laptop closed. Full relay + SSH-trust mechanics:
152
152
  - [`docs/configuration.md`](docs/configuration.md) — `agent-runner.toml` schema
153
153
  - [`docs/runbook.md`](docs/runbook.md) — operator troubleshooting (OAuth, disk, orphan)
154
154
  - [`docs/architecture.md`](docs/architecture.md) — 3-layer model, defenses-as-data
155
+ - [`docs/memory-efficiency.md`](docs/memory-efficiency.md) — supervisor memory footprint, measured release over release
155
156
 
156
157
  ## Development
157
158
 
@@ -114,6 +114,7 @@ your laptop closed. Full relay + SSH-trust mechanics:
114
114
  - [`docs/configuration.md`](docs/configuration.md) — `agent-runner.toml` schema
115
115
  - [`docs/runbook.md`](docs/runbook.md) — operator troubleshooting (OAuth, disk, orphan)
116
116
  - [`docs/architecture.md`](docs/architecture.md) — 3-layer model, defenses-as-data
117
+ - [`docs/memory-efficiency.md`](docs/memory-efficiency.md) — supervisor memory footprint, measured release over release
117
118
 
118
119
  ## Development
119
120
 
@@ -44,7 +44,12 @@ def _load_plugins_from_group(group: str) -> None:
44
44
 
45
45
  for name, value in scan_entry_points(sys.path, group):
46
46
  try:
47
- module_path, _, attr_path = value.partition(":")
47
+ # An entry-point value may carry a trailing extras marker
48
+ # (``module:attr [extra1,extra2]``); importlib.metadata.EntryPoint's
49
+ # own module/attr grammar excludes "[", so anything from the first
50
+ # "[" onward is always extras, never part of the path — strip it
51
+ # before resolving, or it glues onto attr_path and breaks getattr().
52
+ module_path, _, attr_path = value.partition("[")[0].rstrip().partition(":")
48
53
  mod = importlib.import_module(module_path)
49
54
  target = mod
50
55
  for attr in filter(None, attr_path.split(".")):
@@ -16,6 +16,7 @@ import typing
16
16
  from collections.abc import Callable
17
17
  from pathlib import Path
18
18
 
19
+ from agent_runner import _serve_policy
19
20
  from agent_runner.cli.init_cmd import _preset_names
20
21
  from agent_runner.config import (
21
22
  AgentConfig,
@@ -30,7 +31,14 @@ from agent_runner.config import (
30
31
  VcsConfig,
31
32
  )
32
33
  from agent_runner.defenses import catalog
33
- from agent_runner.events import KNOWN_EVENT_KINDS
34
+ from agent_runner.events import (
35
+ CONFIG_BROKEN,
36
+ CRASH_LOOP,
37
+ KNOWN_EVENT_KINDS,
38
+ MEM_LOOP,
39
+ MEM_LOOP_PERSISTENT,
40
+ STALLED_NO_PROGRESS,
41
+ )
34
42
  from agent_runner.monitor import AUTO_STOP_ALERTS, KNOWN_ALERT_KINDS
35
43
 
36
44
  # (toml_section_name, dataclass, nested_sub_sections). Nesting is an EXPLICIT
@@ -156,6 +164,84 @@ def render_event_kinds_list() -> str:
156
164
  return "\n".join(f"- `{k}`" for k in sorted(KNOWN_EVENT_KINDS))
157
165
 
158
166
 
167
+ def _giveup_verdict_rows() -> list[tuple[str, int, bool]]:
168
+ """``(verdict, exit_code, in_restart_prevent)`` rows for serve's five
169
+ give-up verdicts, in the order the runbook's Symptom section lists them.
170
+
171
+ The verdict->code mapping itself is CONTROL FLOW in
172
+ ``cli/_serve_round.post_round_verdicts`` (a chain of ``if``/``return``,
173
+ not a dict), so it is mirrored here by hand rather than imported — the
174
+ four exit-code integers are imported from ``_serve_policy``, the actual
175
+ code SSOT, so a constant change here is caught by ``./build.sh docs``
176
+ producing a diff. The third column is DERIVED from
177
+ ``_serve_policy.RESTART_PREVENT_EXIT_CODES`` (the same single source
178
+ ``service_unit.py`` renders from) rather than hand-typed per row.
179
+ """
180
+ verdict_codes = [
181
+ (CONFIG_BROKEN, _serve_policy.PERMANENT_CONFIG_EXIT),
182
+ (CRASH_LOOP, _serve_policy.CRASH_LOOP_EXIT),
183
+ (STALLED_NO_PROGRESS, _serve_policy.CRASH_LOOP_EXIT),
184
+ (MEM_LOOP_PERSISTENT, _serve_policy.MEM_LOOP_PERSISTENT_EXIT),
185
+ (MEM_LOOP, _serve_policy.MEM_LOOP_EXIT),
186
+ ]
187
+ return [
188
+ (verdict, code, code in _serve_policy.RESTART_PREVENT_EXIT_CODES)
189
+ for verdict, code in verdict_codes
190
+ ]
191
+
192
+
193
+ def render_giveup_exit_codes_table() -> str:
194
+ """Verdict -> exit-code -> ``RestartPreventExitStatus`` membership table
195
+ for serve's give-up breakers. 5 verdicts map to only 4 distinct codes:
196
+ ``stalled_no_progress`` reuses ``crash_loop``'s 75 rather than minting its
197
+ own (same give-up verdict — "an unknown failure kept recurring, stop for
198
+ real" — reached via a different signal)."""
199
+ lines = ["| Verdict | Exit code | `RestartPreventExitStatus`? |", "|---|---|---|"]
200
+ seen_codes: dict[int, str] = {}
201
+ for verdict, code, stays_stopped in _giveup_verdict_rows():
202
+ shares = f" (shares `{seen_codes[code]}`'s exit code)" if code in seen_codes else ""
203
+ seen_codes.setdefault(code, verdict)
204
+ behavior = "yes — stays stopped" if stays_stopped else "no — restarts (break-then-restart)"
205
+ lines.append(f"| `{verdict}` | `{code}`{shares} | {behavior} |")
206
+ return "\n".join(lines)
207
+
208
+
209
+ def render_giveup_systemd_example() -> str:
210
+ """The ``systemd unit pattern recommendations`` fenced example.
211
+
212
+ ``RestartPreventExitStatus`` lists the three DELIBERATE give-up codes
213
+ (config_broken/crash_loop/mem_loop_persistent), derived from
214
+ ``_serve_policy.RESTART_PREVENT_EXIT_CODES`` — the same single source
215
+ ``service_unit.py`` renders from; ``mem_loop``'s 71 is deliberately
216
+ excluded so systemd respawns to retry. The second comment line's
217
+ leading padding is derived from the first line's own length (not
218
+ hand-counted) so the two ``#`` columns always line up even if a future
219
+ exit code changes ``prevent``'s width."""
220
+ prevent = " ".join(str(c) for c in _serve_policy.RESTART_PREVENT_EXIT_CODES)
221
+ header = f"RestartPreventExitStatus={prevent} "
222
+ return (
223
+ "```ini\n"
224
+ "# Prod (infinite supervisor) — current default\n"
225
+ "[Service]\n"
226
+ "ExecStart=... serve --config /etc/agent-runner.toml\n"
227
+ "Restart=on-failure\n"
228
+ f"{header}"
229
+ f"# config_broken ({_serve_policy.PERMANENT_CONFIG_EXIT}) / "
230
+ f"crash_loop ({_serve_policy.CRASH_LOOP_EXIT}) / "
231
+ f"mem_loop_persistent ({_serve_policy.MEM_LOOP_PERSISTENT_EXIT}) stay stopped\n"
232
+ f"{' ' * len(header)}"
233
+ f"# mem_loop ({_serve_policy.MEM_LOOP_EXIT}) is NOT listed here — it restarts\n"
234
+ "RestartSec=3\n"
235
+ "\n"
236
+ "# Bounded job\n"
237
+ "[Service]\n"
238
+ "ExecStart=... serve --config /etc/test.toml --max-rounds 10\n"
239
+ "Restart=on-failure\n"
240
+ "RestartSec=5\n"
241
+ "```"
242
+ )
243
+
244
+
159
245
  def _render_migrate_transforms() -> str:
160
246
  """Bullet list of the transforms `agent-runner migrate` applies, from the registry.
161
247
  A few entries carry a `parsed -> str` describe (so the manual report can name the
@@ -265,6 +351,16 @@ RENDERERS: dict[str, Renderer] = {
265
351
  "migrate-transforms": Renderer(
266
352
  _render_migrate_transforms, "agent_runner/migrations.py MIGRATIONS"
267
353
  ),
354
+ "giveup-exit-codes": Renderer(
355
+ render_giveup_exit_codes_table,
356
+ "agent_runner/_serve_policy.py exit-code constants + "
357
+ "cli/_serve_round.py post_round_verdicts's verdict->code map",
358
+ ),
359
+ "giveup-systemd-example": Renderer(
360
+ render_giveup_systemd_example,
361
+ "agent_runner/_serve_policy.py exit-code constants "
362
+ "(service_unit.py's RestartPreventExitStatus mirrors these same three)",
363
+ ),
268
364
  }
269
365
  RENDERERS.update(
270
366
  {
@@ -0,0 +1,103 @@
1
+ """Event emission wrappers — extracted from api.py for module-size hygiene.
2
+
3
+ Public facade over the ``_emit`` package: every sibling module in
4
+ ``agent_runner`` (and every test) imports from ``agent_runner._emit``
5
+ directly, never from ``agent_runner._emit.<submodule>`` — this file is the
6
+ one place a name crosses that boundary. All wrappers are re-exported from
7
+ agent_runner.api for backward compatibility; plugins and supervisor should
8
+ continue importing from agent_runner.api (e.g.
9
+ ``from agent_runner.api import emit_transient_error_detected``).
10
+
11
+ Each wrapper exists to keep cli/serve_cmd.py from importing agent_runner.events
12
+ directly — preserves the module-boundary architecture invariant. Local-import pattern
13
+ inside each wrapper body keeps agent_runner.api import-cheap.
14
+
15
+ Submodules are organised by domain: ``serve`` (supervisor control plane +
16
+ config/schedule lifecycle), ``rounds`` (one round's own lifecycle),
17
+ ``memory`` (host/cgroup memory pressure), ``agent`` (the agent axis — plugin
18
+ reports + throttle response).
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from agent_runner._emit.agent import ( # noqa: F401 — public re-export
24
+ emit_agent_auth_error_detected,
25
+ emit_agent_usage_recorded,
26
+ emit_anomaly_repetitive_tool,
27
+ emit_transient_error_backoff_capped,
28
+ emit_transient_error_detected,
29
+ emit_transient_error_recovered,
30
+ )
31
+ from agent_runner._emit.memory import ( # noqa: F401 — public re-export
32
+ emit_host_cgroup_memory_limit,
33
+ emit_mem_pressure_deferred_to_cgroup,
34
+ emit_round_cgroup_memory,
35
+ emit_round_deferred,
36
+ emit_round_mem_critical_sample,
37
+ emit_round_mem_terminated,
38
+ emit_round_oom_killed,
39
+ emit_round_resumed,
40
+ )
41
+ from agent_runner._emit.rounds import ( # noqa: F401 — public re-export
42
+ emit_fresh_eyes_round_triggered,
43
+ emit_round_grace_extended,
44
+ emit_round_grace_kill,
45
+ emit_round_logs_prune_deferred,
46
+ emit_round_progress,
47
+ emit_round_substrate_after,
48
+ emit_round_substrate_before,
49
+ emit_round_supervisor_wedged,
50
+ emit_stale_index_lock_cleared,
51
+ )
52
+ from agent_runner._emit.serve import ( # noqa: F401 — public re-export
53
+ emit_config_broken,
54
+ emit_config_migrated,
55
+ emit_crash_loop,
56
+ emit_max_rounds_reached,
57
+ emit_mem_loop,
58
+ emit_mem_loop_persistent,
59
+ emit_rate_limit_stop,
60
+ emit_schedule_paused,
61
+ emit_schedule_phase_skipped,
62
+ emit_schedule_resumed,
63
+ emit_stalled_no_progress,
64
+ emit_stop_file_detected,
65
+ )
66
+
67
+ __all__ = [
68
+ "emit_agent_auth_error_detected",
69
+ "emit_agent_usage_recorded",
70
+ "emit_anomaly_repetitive_tool",
71
+ "emit_config_broken",
72
+ "emit_config_migrated",
73
+ "emit_crash_loop",
74
+ "emit_fresh_eyes_round_triggered",
75
+ "emit_host_cgroup_memory_limit",
76
+ "emit_max_rounds_reached",
77
+ "emit_mem_loop",
78
+ "emit_mem_loop_persistent",
79
+ "emit_mem_pressure_deferred_to_cgroup",
80
+ "emit_rate_limit_stop",
81
+ "emit_round_cgroup_memory",
82
+ "emit_round_deferred",
83
+ "emit_round_grace_extended",
84
+ "emit_round_grace_kill",
85
+ "emit_round_logs_prune_deferred",
86
+ "emit_round_mem_critical_sample",
87
+ "emit_round_mem_terminated",
88
+ "emit_round_oom_killed",
89
+ "emit_round_progress",
90
+ "emit_round_resumed",
91
+ "emit_round_substrate_after",
92
+ "emit_round_substrate_before",
93
+ "emit_round_supervisor_wedged",
94
+ "emit_schedule_paused",
95
+ "emit_schedule_phase_skipped",
96
+ "emit_schedule_resumed",
97
+ "emit_stale_index_lock_cleared",
98
+ "emit_stalled_no_progress",
99
+ "emit_stop_file_detected",
100
+ "emit_transient_error_backoff_capped",
101
+ "emit_transient_error_detected",
102
+ "emit_transient_error_recovered",
103
+ ]
@@ -0,0 +1,231 @@
1
+ """Event emission wrappers — the agent axis: what a per-CLI plugin reports
2
+ (transient errors, auth failures, usage, anomalies) and the throttle
3
+ response. Re-exported from ``agent_runner._emit`` (the package facade) —
4
+ see its docstring.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+
11
+
12
+ def emit_transient_error_detected(
13
+ log_dir: Path,
14
+ *,
15
+ classification: str,
16
+ agent: str,
17
+ reset_at_epoch: int,
18
+ round_num: int,
19
+ raw: str,
20
+ phase: str = "",
21
+ ) -> None:
22
+ """Emit detection of a transient agent error (rate limit / 5xx / timeout).
23
+
24
+ ``phase`` is the rotation phase the failing round ran under ("" when the
25
+ config has no ``[phases]``). It lets a ``phase_policy = "skip"`` serve loop
26
+ step over *this* phase while a healthy sibling keeps running."""
27
+ from agent_runner._redact import redact_secrets
28
+ from agent_runner.events import TRANSIENT_ERROR_DETECTED, emit
29
+
30
+ raw = redact_secrets(raw)
31
+ emit(
32
+ log_dir,
33
+ TRANSIENT_ERROR_DETECTED,
34
+ classification=classification,
35
+ agent=agent,
36
+ reset_at_epoch=reset_at_epoch,
37
+ round_num=round_num,
38
+ raw=raw,
39
+ phase=phase,
40
+ )
41
+
42
+
43
+ def emit_agent_auth_error_detected(
44
+ log_dir: Path,
45
+ *,
46
+ round_num: int,
47
+ agent: str,
48
+ raw: str,
49
+ ) -> None:
50
+ """Emit an authentication/authorization failure the agent itself reported.
51
+
52
+ Contract: emitted by a per-CLI plugin only when the agent's OWN structured
53
+ output names the failure (e.g. an HTTP 401 in its JSON event stream). That
54
+ is certain evidence, unlike the monitor's ``oauth_fail`` text heuristic,
55
+ which scans free-text log tails and therefore needs a nonzero-exit shield
56
+ against prose that merely mentions "401". The monitor counts a round
57
+ carrying this event without that shield — which is what makes an auth loop
58
+ visible for a CLI that exits 0 on provider failure.
59
+
60
+ No back-off partner event: an auth failure is permanent until an operator
61
+ fixes the credential, so it is deliberately not a transient classification.
62
+ """
63
+ from agent_runner._redact import redact_secrets
64
+ from agent_runner.events import AGENT_AUTH_ERROR_DETECTED, emit
65
+
66
+ emit(
67
+ log_dir,
68
+ AGENT_AUTH_ERROR_DETECTED,
69
+ round_num=round_num,
70
+ agent=agent,
71
+ raw=redact_secrets(raw),
72
+ )
73
+
74
+
75
+ def emit_transient_error_recovered(
76
+ log_dir: Path,
77
+ *,
78
+ classification: str,
79
+ agent: str,
80
+ throttled_for_s: int,
81
+ ) -> None:
82
+ """Emit recovery from a transient error back-off (right before resuming)."""
83
+ from agent_runner.events import TRANSIENT_ERROR_RECOVERED, emit
84
+
85
+ emit(
86
+ log_dir,
87
+ TRANSIENT_ERROR_RECOVERED,
88
+ classification=classification,
89
+ agent=agent,
90
+ throttled_for_s=throttled_for_s,
91
+ )
92
+
93
+
94
+ def emit_agent_usage_recorded(
95
+ log_dir: Path,
96
+ *,
97
+ agent: str,
98
+ model: str,
99
+ round_num: int,
100
+ input_tokens: int,
101
+ output_tokens: int,
102
+ cached_tokens: int,
103
+ cost_usd: float | None,
104
+ duration_ms: int,
105
+ models_breakdown: dict[str, dict[str, int]] | None = None,
106
+ cache_creation_tokens: int = 0,
107
+ tool_call_count: int = 0,
108
+ phase: str = "",
109
+ success: bool = True,
110
+ ) -> None:
111
+ """Emit per-round usage record from a CLI plugin.
112
+
113
+ Raw data only — aggregation (totals, projections, budget warnings) is
114
+ deferred to consumers and the capability layer.
115
+
116
+ Field semantics:
117
+ - ``input_tokens``: net non-cached input (both claude and gemini emit net;
118
+ total throughput = input_tokens + cached_tokens).
119
+ - ``cost_usd``: USD cost for the round, or None when CLI doesn't expose it
120
+ (gemini has no cost field; claude exposes total_cost_usd).
121
+ - ``models_breakdown``: only populated when a round used multiple models
122
+ (gemini multi-model rounds). None for claude (always single-model).
123
+ - ``cache_creation_tokens``: claude only — ``usage.cache_creation_input_tokens``,
124
+ independent count from ``cached_tokens`` (cache_read). Priced differently
125
+ from fresh input by the provider; the framework records the count only and
126
+ never interprets cost (no price tables here — see ``cost_usd`` above for the
127
+ one cost figure the framework passes through verbatim). Gemini has no
128
+ equivalent → 0.
129
+ - ``tool_call_count``: number of tool invocations the agent made in the round.
130
+ Claude: count of ``tool_use`` content blocks across all assistant events.
131
+ Gemini: ``stats.tool_calls``.
132
+ - ``phase``: phase label from HookContext (e.g. "planning"); empty string when None.
133
+ - ``success``: the supervisor's clean-exit predicate (``RoundResult.ok``).
134
+ A plugin MAY additionally fold in the agent's own terminal verdict when
135
+ its CLI's exit code is unreliable — pi does, because pi exits 0 on
136
+ provider failure.
137
+ """
138
+ from agent_runner.events import AGENT_USAGE_RECORDED, emit
139
+
140
+ emit(
141
+ log_dir,
142
+ AGENT_USAGE_RECORDED,
143
+ agent=agent,
144
+ model=model,
145
+ round_num=round_num,
146
+ input_tokens=input_tokens,
147
+ output_tokens=output_tokens,
148
+ cached_tokens=cached_tokens,
149
+ cost_usd=cost_usd,
150
+ duration_ms=duration_ms,
151
+ models_breakdown=models_breakdown,
152
+ cache_creation_tokens=cache_creation_tokens,
153
+ tool_call_count=tool_call_count,
154
+ phase=phase,
155
+ success=success,
156
+ )
157
+
158
+
159
+ def emit_anomaly_repetitive_tool(
160
+ log_dir: Path,
161
+ *,
162
+ round_num: int,
163
+ tool_name: str,
164
+ target: str | None,
165
+ count: int,
166
+ window: int,
167
+ ) -> None:
168
+ """Emit when claude plugin detects the same (tool, target) tuple repeated
169
+ >= threshold times in a sliding window of tool-call events.
170
+
171
+ Claude-only (gemini JSONL stats summary does not expose per-tool events).
172
+ Default OFF: both anomaly_repetitive_window and anomaly_repetitive_threshold
173
+ must be > 0 in [monitor] config to activate.
174
+ """
175
+ from agent_runner.events import ANOMALY_REPETITIVE_TOOL, emit
176
+
177
+ emit(
178
+ log_dir,
179
+ ANOMALY_REPETITIVE_TOOL,
180
+ round_num=round_num,
181
+ tool_name=tool_name,
182
+ target=target,
183
+ count=count,
184
+ window=window,
185
+ )
186
+
187
+
188
+ def emit_transient_error_backoff_capped(
189
+ log_dir: Path,
190
+ *,
191
+ classification: str,
192
+ agent: str,
193
+ requested_sleep_s: int,
194
+ applied_sleep_s: int,
195
+ original_reset_at_epoch: int | None = None,
196
+ applied_reset_at_epoch: int | None = None,
197
+ consecutive_count: int | None = None,
198
+ capped_by_absolute_max: bool | None = None,
199
+ ) -> None:
200
+ """Emit when supervisor adjusts the plugin-emitted transient back-off.
201
+
202
+ Fires in two cases:
203
+ 1. **Exp backoff applied**: estimated-class transient errors
204
+ (`rate_limit_model` / `api_transient_5xx` / `api_timeout`) doubled
205
+ on consecutive failures. ``consecutive_count`` > 1, multiplier > 1×.
206
+ 2. **Defensive cap hit**: malformed `reset_at_epoch` or the
207
+ 30-min absolute cap clipped the wait. ``capped_by_absolute_max`` True.
208
+
209
+ Fields ``original_reset_at_epoch`` / ``applied_reset_at_epoch`` /
210
+ ``consecutive_count`` / ``capped_by_absolute_max`` are newer fields. Older
211
+ callers that pass only the first 4 kwargs continue to work; the new
212
+ fields are omitted from the payload when None.
213
+ """
214
+ from agent_runner.events import TRANSIENT_ERROR_BACKOFF_CAPPED, emit
215
+
216
+ kwargs: dict = {
217
+ "classification": classification,
218
+ "agent": agent,
219
+ "requested_sleep_s": requested_sleep_s,
220
+ "applied_sleep_s": applied_sleep_s,
221
+ }
222
+ if original_reset_at_epoch is not None:
223
+ kwargs["original_reset_at_epoch"] = original_reset_at_epoch
224
+ if applied_reset_at_epoch is not None:
225
+ kwargs["applied_reset_at_epoch"] = applied_reset_at_epoch
226
+ if consecutive_count is not None:
227
+ kwargs["consecutive_count"] = consecutive_count
228
+ if capped_by_absolute_max is not None:
229
+ kwargs["capped_by_absolute_max"] = capped_by_absolute_max
230
+
231
+ emit(log_dir, TRANSIENT_ERROR_BACKOFF_CAPPED, **kwargs)