cli-agent-runner 0.3.12__tar.gz → 0.3.13__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 (411) hide show
  1. cli_agent_runner-0.3.13/.ralph/items.json +86 -0
  2. cli_agent_runner-0.3.13/.ralph/progress.md +47 -0
  3. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/CHANGELOG.md +13 -0
  4. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/PKG-INFO +1 -1
  5. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/SECURITY.md +6 -0
  6. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_emit/memory.py +27 -3
  7. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_version.py +2 -2
  8. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/_serve_cgroup.py +9 -2
  9. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/metrics.py +28 -12
  10. cli_agent_runner-0.3.13/tests/_clock.py +95 -0
  11. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/conftest.py +295 -175
  12. cli_agent_runner-0.3.13/tests/e2e/growth_child.py +28 -0
  13. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/test_e2e_pre_oom.py +179 -19
  14. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_spawn_round_mem_floor.py +26 -50
  15. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/linux/test_memory_high_nudge_e2e.py +4 -4
  16. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cgroup_probe.py +30 -0
  17. cli_agent_runner-0.3.13/tests/unit/test_growth_child_constants.py +8 -0
  18. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_host_health.py +20 -0
  19. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_metrics.py +5 -2
  20. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_round_cgroup_spine.py +60 -32
  21. cli_agent_runner-0.3.12/tests/_clock.py +0 -53
  22. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.codecov.yml +0 -0
  23. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  24. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  25. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  26. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  27. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/dependabot.yml +0 -0
  28. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/workflows/ci.yml +0 -0
  29. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/workflows/release.yml +0 -0
  30. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.gitignore +0 -0
  31. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.vulture-whitelist.py +0 -0
  32. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/BACKLOG.md +0 -0
  33. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/CODE_OF_CONDUCT.md +0 -0
  34. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/CONTRIBUTING.md +0 -0
  35. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/LICENSE +0 -0
  36. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/README.md +0 -0
  37. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/README.zh.md +0 -0
  38. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/__init__.py +0 -0
  39. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_bounded.py +0 -0
  40. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_docgen.py +0 -0
  41. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_emit/__init__.py +0 -0
  42. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_emit/agent.py +0 -0
  43. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_emit/rounds.py +0 -0
  44. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_emit/serve.py +0 -0
  45. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_install.py +0 -0
  46. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_lifecycle.py +0 -0
  47. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_monitor_detectors.py +0 -0
  48. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_monitor_registry.py +0 -0
  49. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_monitor_state.py +0 -0
  50. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_notify.py +0 -0
  51. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_observe.py +0 -0
  52. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_plugin_manifest.py +0 -0
  53. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_plugin_scan.py +0 -0
  54. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_procwait.py +0 -0
  55. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_redact.py +0 -0
  56. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_registry.py +0 -0
  57. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_resolve.py +0 -0
  58. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_round_outcome.py +0 -0
  59. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_round_support.py +0 -0
  60. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_serve_policy.py +0 -0
  61. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_substrate.py +0 -0
  62. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_throttle.py +0 -0
  63. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/agent_runtime.py +0 -0
  64. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/api.py +0 -0
  65. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/api_types.py +0 -0
  66. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/__init__.py +0 -0
  67. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/_constants.py +0 -0
  68. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  69. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/codewhale.py +0 -0
  70. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/gemini.py +0 -0
  71. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/kimi.py +0 -0
  72. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/pi.py +0 -0
  73. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/__init__.py +0 -0
  74. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/__main__.py +0 -0
  75. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/_serve_round.py +0 -0
  76. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/common.py +0 -0
  77. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/doctor_cmd.py +0 -0
  78. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/events_cmd.py +0 -0
  79. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/init_cmd.py +0 -0
  80. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/install_cmd.py +0 -0
  81. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/migrate_cmd.py +0 -0
  82. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/monitor_cmd.py +0 -0
  83. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/peek_cmd.py +0 -0
  84. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/round_cmd.py +0 -0
  85. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/serve_cmd.py +0 -0
  86. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/service_cmd.py +0 -0
  87. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/upgrade_cmd.py +0 -0
  88. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/clock.py +0 -0
  89. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/config/__init__.py +0 -0
  90. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/config/errors.py +0 -0
  91. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/config/loader.py +0 -0
  92. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/config/models.py +0 -0
  93. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/config/parsers.py +0 -0
  94. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/config/validators.py +0 -0
  95. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/context_store.py +0 -0
  96. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/defenses.py +0 -0
  97. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/event_log.py +0 -0
  98. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/events.py +0 -0
  99. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/goal.py +0 -0
  100. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/hooks.py +0 -0
  101. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/host_health.py +0 -0
  102. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/http_progress.py +0 -0
  103. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/lifecycle.py +0 -0
  104. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/migrations.py +0 -0
  105. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/monitor.py +0 -0
  106. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/phase_select.py +0 -0
  107. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/__init__.py +0 -0
  108. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/aider.toml +0 -0
  109. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/claude.toml +0 -0
  110. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/codewhale.toml +0 -0
  111. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/gemini.toml +0 -0
  112. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/kimi.toml +0 -0
  113. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/pi.toml +0 -0
  114. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/prompt_loader.py +0 -0
  115. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/remote_relay.py +0 -0
  116. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/round_log.py +0 -0
  117. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/round_view.py +0 -0
  118. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/runner.py +0 -0
  119. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/scaffold.py +0 -0
  120. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/schedule.py +0 -0
  121. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/service_unit.py +0 -0
  122. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/startup_check.py +0 -0
  123. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/vcs_state.py +0 -0
  124. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/build.sh +0 -0
  125. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/deploy/Dockerfile +0 -0
  126. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/deploy/docker-compose.yml +0 -0
  127. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/README.md +0 -0
  128. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/architecture.md +0 -0
  129. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/commands.md +0 -0
  130. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/configuration.md +0 -0
  131. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/events.md +0 -0
  132. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/long-running-agents.md +0 -0
  133. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/memory-efficiency.md +0 -0
  134. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/migrations/0.2.md +0 -0
  135. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/migrations/0.3.md +0 -0
  136. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/plugins.md +0 -0
  137. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/quickstart.md +0 -0
  138. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/recipes/aider.md +0 -0
  139. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/recipes/codewhale.md +0 -0
  140. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/recipes/container-pi.md +0 -0
  141. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/recipes/deploy-all-in-one.md +0 -0
  142. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/recipes/kimi.md +0 -0
  143. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/recipes/pi.md +0 -0
  144. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/runbook.md +0 -0
  145. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/thesis.md +0 -0
  146. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/pyproject.toml +0 -0
  147. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/__init__.py +0 -0
  148. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/_test_helpers.py +0 -0
  149. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/conftest.py +0 -0
  150. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/contract/__init__.py +0 -0
  151. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/contract/test_public_api_surface.py +0 -0
  152. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/__init__.py +0 -0
  153. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  154. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/test_e2e_install_systemd.py +0 -0
  155. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  156. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/test_e2e_pi_resume.py +0 -0
  157. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  158. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  159. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  160. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  161. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/generate_vulture_whitelist.py +0 -0
  162. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/__init__.py +0 -0
  163. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_auto_stop_e2e.py +0 -0
  164. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_bounded_run.py +0 -0
  165. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_cooperative_grace_property.py +0 -0
  166. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_cooperative_stop_signal.py +0 -0
  167. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_fresh_eyes_signal.py +0 -0
  168. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_goal_steering_e2e.py +0 -0
  169. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_grace_kill_emission.py +0 -0
  170. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_install_dry_run.py +0 -0
  171. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_monitor_seeded.py +0 -0
  172. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_plugin_real_flow.py +0 -0
  173. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  174. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_reap_property.py +0 -0
  175. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  176. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_scaffold_presets.py +0 -0
  177. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_serve_loop.py +0 -0
  178. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_spawn_round_wedged.py +0 -0
  179. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_substrate_fingerprint.py +0 -0
  180. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_transient_error_backoff.py +0 -0
  181. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/__init__.py +0 -0
  182. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/_docs.py +0 -0
  183. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/_event_scan.py +0 -0
  184. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_architecture.py +0 -0
  185. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_atomic_write_enforced.py +0 -0
  186. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_authored_facts_declared.py +0 -0
  187. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  188. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_bdd_test_conventions.py +0 -0
  189. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_brake_defaults_off.py +0 -0
  190. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_catalogs.py +0 -0
  191. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_classification_ssot.py +0 -0
  192. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_config_error_consistency.py +0 -0
  193. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_config_value_set_ssot.py +0 -0
  194. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_container_stop_bound.py +0 -0
  195. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
  196. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  197. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
  198. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_cli_claims.py +0 -0
  199. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_operator_surface.py +0 -0
  200. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
  201. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  202. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_usage_event_fields.py +0 -0
  203. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_docs_generated.py +0 -0
  204. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_docs_index_complete.py +0 -0
  205. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_entry_points_resolve.py +0 -0
  206. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_event_int_coercion.py +0 -0
  207. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_event_kind_registry.py +0 -0
  208. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_event_kinds_ssot.py +0 -0
  209. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_events_doc_contract.py +0 -0
  210. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
  211. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_goal_firewall.py +0 -0
  212. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_hard_wall_signal.py +0 -0
  213. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_hook_contract_docs.py +0 -0
  214. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_import_footprint.py +0 -0
  215. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_layer_2_loop_size.py +0 -0
  216. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_migrations_registry_ssot.py +0 -0
  217. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_module_boundaries.py +0 -0
  218. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_module_sizes.py +0 -0
  219. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_monitor_self_exclusion.py +0 -0
  220. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_ai_signatures.py +0 -0
  221. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_asyncio_select.py +0 -0
  222. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_direct_inspect_import.py +0 -0
  223. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_internal_refs.py +0 -0
  224. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_price_logic.py +0 -0
  225. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  226. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_raw_time.py +0 -0
  227. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_vacuous_invariants.py +0 -0
  228. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_version_refs.py +0 -0
  229. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_peek_schema_version.py +0 -0
  230. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_phase_select_stateless.py +0 -0
  231. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_plugins_config_stable.py +0 -0
  232. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_procwait_shield.py +0 -0
  233. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  234. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  235. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_round_alloc_growth.py +0 -0
  236. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_round_log_decode_boundary.py +0 -0
  237. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_round_num_single_source.py +0 -0
  238. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_round_result_stable.py +0 -0
  239. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  240. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  241. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_throttle_stateless_imports.py +0 -0
  242. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_timeout_budget_invariant.py +0 -0
  243. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_upstream_schema_canary.py +0 -0
  244. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  245. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/linux/__init__.py +0 -0
  246. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/literate/__init__.py +0 -0
  247. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/literate/parser.py +0 -0
  248. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/literate/test_parser.py +0 -0
  249. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/literate/test_quickstart.py +0 -0
  250. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/__init__.py +0 -0
  251. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime.py +0 -0
  252. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime_container.py +0 -0
  253. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime_grace.py +0 -0
  254. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
  255. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime_progress.py +0 -0
  256. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime_reap.py +0 -0
  257. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  258. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_assemble_prompt.py +0 -0
  259. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_events_stream.py +0 -0
  260. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_install.py +0 -0
  261. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_observation.py +0 -0
  262. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_read_round_num.py +0 -0
  263. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_resolve_phase.py +0 -0
  264. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_service.py +0 -0
  265. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_types.py +0 -0
  266. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_apply_reap_grace_env.py +0 -0
  267. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_apply_resume_env.py +0 -0
  268. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_auto_stop_gating.py +0 -0
  269. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_catalogs.py +0 -0
  270. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_claude_error_detector.py +0 -0
  271. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_claude_foreign_schema.py +0 -0
  272. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli.py +0 -0
  273. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli_common.py +0 -0
  274. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli_init_install.py +0 -0
  275. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli_main_boundary.py +0 -0
  276. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli_monitor_http.py +0 -0
  277. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  278. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli_upgrade.py +0 -0
  279. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_codewhale_code_precedence.py +0 -0
  280. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_codewhale_plugin.py +0 -0
  281. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_coerce_int_poison.py +0 -0
  282. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config.py +0 -0
  283. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_fresh_eyes.py +0 -0
  284. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_max_rounds.py +0 -0
  285. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_nested_strictness.py +0 -0
  286. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_prompt_delivery.py +0 -0
  287. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_resume_guard.py +0 -0
  288. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_schedule.py +0 -0
  289. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_stop_file.py +0 -0
  290. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_strictness.py +0 -0
  291. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  292. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_transient_error_action.py +0 -0
  293. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_context_store.py +0 -0
  294. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_control_char_config.py +0 -0
  295. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cooperative_signal_env.py +0 -0
  296. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_defenses.py +0 -0
  297. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_detect_hung_latch.py +0 -0
  298. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_detector_isolation.py +0 -0
  299. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_docgen.py +0 -0
  300. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_doctor_cmd.py +0 -0
  301. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_effective_throttle_view.py +0 -0
  302. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_emit_config_migrated.py +0 -0
  303. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_emit_schedule.py +0 -0
  304. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
  305. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_event_log.py +0 -0
  306. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_events.py +0 -0
  307. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_events_cmd.py +0 -0
  308. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_events_cmd_rollover.py +0 -0
  309. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_events_cmd_tail.py +0 -0
  310. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_events_jsonl_decode.py +0 -0
  311. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_example_plugin.py +0 -0
  312. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_exec_prefix.py +0 -0
  313. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_exit_cause.py +0 -0
  314. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_exit_classifier.py +0 -0
  315. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  316. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_gemini_plugin.py +0 -0
  317. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_git_timeout.py +0 -0
  318. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_goal_assessor.py +0 -0
  319. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_goal_executor.py +0 -0
  320. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_goal_firewall_behavioral.py +0 -0
  321. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_goal_ledger.py +0 -0
  322. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_hook_redact_order.py +0 -0
  323. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_hooks.py +0 -0
  324. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_http_progress.py +0 -0
  325. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_http_progress_epoch.py +0 -0
  326. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_init_entry_points.py +0 -0
  327. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
  328. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_kill_pgroup_shielding.py +0 -0
  329. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_kimi_plugin.py +0 -0
  330. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_lifecycle.py +0 -0
  331. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_loader_split.py +0 -0
  332. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_metrics_cgroup_usage.py +0 -0
  333. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_metrics_swap.py +0 -0
  334. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_migrate_cmd.py +0 -0
  335. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_migrations.py +0 -0
  336. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_assembly.py +0 -0
  337. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_cmd_severity.py +0 -0
  338. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_dedup_identity.py +0 -0
  339. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_dedup_rearm.py +0 -0
  340. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  341. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  342. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  343. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_detectors.py +0 -0
  344. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_event_tail.py +0 -0
  345. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_loop.py +0 -0
  346. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_on_alert.py +0 -0
  347. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_remote.py +0 -0
  348. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
  349. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_schedule_pause.py +0 -0
  350. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_network_blip_signal_guard.py +0 -0
  351. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_notify.py +0 -0
  352. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_open_round_log.py +0 -0
  353. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_orphan_chain_single_pass.py +0 -0
  354. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_outer_round_ceiling.py +0 -0
  355. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_peek_argparse.py +0 -0
  356. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_peek_project_isolation.py +0 -0
  357. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_peek_schedule.py +0 -0
  358. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_peek_select.py +0 -0
  359. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_phase_agent_launch.py +0 -0
  360. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_phase_profiles.py +0 -0
  361. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_phase_select.py +0 -0
  362. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_pi_plugin.py +0 -0
  363. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_plugin_constants.py +0 -0
  364. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_plugin_manifest.py +0 -0
  365. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_plugin_scan_parity.py +0 -0
  366. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_plugins_config_fields.py +0 -0
  367. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_presets.py +0 -0
  368. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_procwait.py +0 -0
  369. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_prompt_loader.py +0 -0
  370. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_read_json_hardening.py +0 -0
  371. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_redact.py +0 -0
  372. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_resolve.py +0 -0
  373. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_resolve_dirty_tree.py +0 -0
  374. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_round_cmd.py +0 -0
  375. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_round_cmd_sigterm.py +0 -0
  376. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_round_log_helpers.py +0 -0
  377. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_round_outcome.py +0 -0
  378. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_round_outcome_agent_axis.py +0 -0
  379. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_round_view.py +0 -0
  380. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_run_bounded.py +0 -0
  381. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_runner.py +0 -0
  382. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_runner_sidecar_ordering.py +0 -0
  383. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_runner_throttle.py +0 -0
  384. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_scaffold.py +0 -0
  385. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_schedule.py +0 -0
  386. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_cmd_bounded.py +0 -0
  387. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_config_broken.py +0 -0
  388. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_crash_loop.py +0 -0
  389. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_mem_gate.py +0 -0
  390. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_phase_schedule.py +0 -0
  391. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_policy.py +0 -0
  392. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_round.py +0 -0
  393. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_round_log.py +0 -0
  394. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_round_oom_killed.py +0 -0
  395. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_round_terminate.py +0 -0
  396. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_round_wait_exit.py +0 -0
  397. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_schedule_gate.py +0 -0
  398. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_sentinel.py +0 -0
  399. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_wakeup.py +0 -0
  400. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_service_mode.py +0 -0
  401. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_service_unit.py +0 -0
  402. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_startup_battery_exit.py +0 -0
  403. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_startup_check.py +0 -0
  404. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_substrate.py +0 -0
  405. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
  406. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_terminate_round_wait_exit.py +0 -0
  407. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_throttle_back_off_ladder.py +0 -0
  408. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_throttle_epoch_bound.py +0 -0
  409. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_throttle_plugin_classification.py +0 -0
  410. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_upgrade_migrate.py +0 -0
  411. {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_vcs_state.py +0 -0
@@ -0,0 +1,86 @@
1
+ {
2
+ "version": 1,
3
+ "runtime_contract": {
4
+ "source_docs": [],
5
+ "verification_gates": [
6
+ {
7
+ "name": "ruff",
8
+ "command": ".venv/bin/ruff check . && .venv/bin/ruff format --check ."
9
+ }
10
+ ],
11
+ "require_progress_append": true,
12
+ "require_one_item_per_iteration": true,
13
+ "require_commit": true
14
+ },
15
+ "items": [
16
+ {
17
+ "category": "functional",
18
+ "description": "metrics.sample() always includes psi_full_total, io_psi_some_avg10, and io_psi_full_avg10 (None when unread). _read_psi returns (some_avg10, full_avg10, full_total). host_health.memory_pressure verdict and context stay identical when those extra keys are injected.",
19
+ "steps": [
20
+ "Add or update tests in tests/unit/test_metrics.py for the triple return and the three sample keys.",
21
+ "Add tests/unit/test_host_health.py coverage that a PSI-healthy sample plus huge io_psi_* / psi_full_total yields the same Pressure as the sample without those keys.",
22
+ "Implement parsing of full-line total= and a second _read_psi call on /proc/pressure/io inside sample(); do not edit host_health.py.",
23
+ "Run .venv/bin/python -m pytest tests/unit/test_metrics.py tests/unit/test_host_health.py tests/unit/test_monitor_detectors.py -q and the ruff gate."
24
+ ],
25
+ "passes": true,
26
+ "regression_notes": "76 passed (test_metrics + test_host_health + test_monitor_detectors); ruff check+format clean. host_health.py untouched. Darwin sample keys present as None."
27
+ },
28
+ {
29
+ "category": "functional",
30
+ "description": "host_cgroup_memory_limit events carry defer as a bool equal to _probe_and_emit_cgroup_defer's return value (True when both caps are finite and plausible, including MemorySwapMax=0).",
31
+ "steps": [
32
+ "Add tests/unit/test_cgroup_probe.py cases: memory_swap_max=0 with finite memory_max under host RAM returns True and emits defer true; memory_swap_max missing returns False and emits defer false.",
33
+ "Add defer: bool to emit_host_cgroup_memory_limit and pass the already-computed defer flag from _probe_and_emit_cgroup_defer.",
34
+ "Point the existing tests/e2e/test_e2e_pre_oom.py diagnostic at host_cgroup_memory_limit.defer.",
35
+ "Run .venv/bin/python -m pytest tests/unit/test_cgroup_probe.py -q and the ruff gate."
36
+ ],
37
+ "passes": true,
38
+ "regression_notes": "55 passed (test_cgroup_probe); ruff check+format clean. MemorySwapMax=0 emits defer true; missing swap.max emits defer false. D-2 diagnostic names host_cgroup_memory_limit.defer."
39
+ },
40
+ {
41
+ "category": "functional",
42
+ "description": "round_cgroup_memory may carry optional flat io_psi_some_avg10, io_psi_full_avg10, and psi_full_total from metrics.sample() at emit time. They are omitted when unread. They are never copied into Pressure.context.",
43
+ "steps": [
44
+ "Extend tests/unit/test_serve_round_cgroup_spine.py to monkeypatch metrics.sample and assert the three fields on the emitted round_cgroup_memory event.",
45
+ "Thread optional kwargs through emit_round_cgroup_memory and _emit_round_cgroup_memory; call sample() once at emit.",
46
+ "Run .venv/bin/python -m pytest tests/unit/test_serve_round_cgroup_spine.py tests/unit/test_serve_round_oom_killed.py -q and the ruff gate."
47
+ ],
48
+ "passes": true,
49
+ "regression_notes": "19 passed (test_serve_round_cgroup_spine + test_serve_round_oom_killed); ruff check+format clean. Optional PSI fields omitted when unread/None; present when sample() read them. Not copied into Pressure.context."
50
+ },
51
+ {
52
+ "category": "functional",
53
+ "description": "The paced pre-OOM child lives in tests/e2e/growth_child.py (8 MiB every 2.5s, page-touch). pi_growth_script installs that file instead of an inlined string. Importing the module does not start the infinite loop.",
54
+ "steps": [
55
+ "Create tests/e2e/growth_child.py with the existing constants and main() behind if __name__.",
56
+ "Add a constants test that imports the module and asserts _CHUNK_BYTES and _PACE_S.",
57
+ "Change tests/e2e/conftest.py pi_growth_script to ship that file.",
58
+ "Run the constants test plus the ruff gate."
59
+ ],
60
+ "passes": true,
61
+ "regression_notes": "1 passed (test_growth_child_constants); ruff check+format clean. pi_growth_script ships tests/e2e/growth_child.py via base64; import does not start the loop."
62
+ },
63
+ {
64
+ "category": "functional",
65
+ "description": "A skip-closed control-arm e2e exists: same unit as treatment except MemorySwapMax=0, MemoryHigh=120M kept, no oom.group and no OOMPolicy=kill. Unset AGENT_RUNNER_E2E_CGROUP and AGENT_RUNNER_E2E_PI, the new test skips. Assertions in source require defer true, no round_mem_terminated, no memory_high_engaged, events_oom_kill_delta>=1 or INVALID, DEFER-FIRST or KERNEL-FIRST named, growth child gone, host still answers.",
66
+ "steps": [
67
+ "Factor the systemd unit body so swap max is a parameter. Control uses 0. Do not add OOMPolicy or memory.oom.group.",
68
+ "Add the control-arm test and shared assertions. Gate with existing PI flag and/or AGENT_RUNNER_E2E_CGROUP. Control wait ceiling 180s.",
69
+ "Without those env flags, run .venv/bin/python -m pytest tests/e2e/test_e2e_pre_oom.py -q and confirm skip (not fail, not pass). Do not export the e2e flags. Run the ruff gate."
70
+ ],
71
+ "passes": true,
72
+ "regression_notes": "3 skipped (test_e2e_pre_oom.py without AGENT_RUNNER_E2E_CGROUP/PI); ruff check+format clean. Control unit MemorySwapMax=0, MemoryHigh=120M kept, no oom.group/OOMPolicy. Assertions in source: defer true, no round_mem_terminated, no memory_high_engaged, events_oom_kill_delta>=1 or INVALID, DEFER-FIRST or KERNEL-FIRST named, growth child gone, host still answers."
73
+ },
74
+ {
75
+ "category": "functional",
76
+ "description": "Root SECURITY.md gains one paragraph: isolation is the operator's job (exec_prefix, dedicated user, no passwordless sudo, egress); agent-runner stays lifecycle-safety. CHANGELOG adds ## [0.3.13] - UNRELEASED. No docs/SECURITY.md. Optional results template under gitignored docs/internal/notes is allowed and must not be staged.",
77
+ "steps": [
78
+ "Edit SECURITY.md Containment or Threat model with the reframe paragraph. Keep existing recipe links.",
79
+ "Insert CHANGELOG ## [0.3.13] - UNRELEASED above 0.3.12. Do not claim uncapped-before-coma.",
80
+ "Confirm docs/SECURITY.md does not exist. Run tests/invariants/test_docs_index_complete.py tests/invariants/test_doc_claims_match_ssot.py and the ruff gate."
81
+ ],
82
+ "passes": true,
83
+ "regression_notes": "4 passed (test_docs_index_complete + test_doc_claims_match_ssot); ruff check+format clean. docs/SECURITY.md absent. CHANGELOG [0.3.13] UNRELEASED; no uncapped-before-coma claim. Results template gitignored, not staged."
84
+ }
85
+ ]
86
+ }
@@ -0,0 +1,47 @@
1
+ ## 2026-09-18 sample() IO-PSI + psi_full_total
2
+
3
+ - item: metrics.sample() always includes psi_full_total, io_psi_some_avg10, and io_psi_full_avg10 (None when unread). _read_psi returns (some_avg10, full_avg10, full_total). host_health.memory_pressure verdict and context stay identical when those extra keys are injected.
4
+ - rationale: plan prefers ladder-blindness before defer-field / e2e fixture work. First unfinished item.
5
+ - files: agent_runner/metrics.py, tests/unit/test_metrics.py, tests/unit/test_host_health.py
6
+ - verification: `.venv/bin/python -m pytest tests/unit/test_metrics.py tests/unit/test_host_health.py tests/unit/test_monitor_detectors.py -q` → 76 passed; `.venv/bin/ruff check . && .venv/bin/ruff format --check .` clean
7
+ - notes: did not edit host_health.py or _NEW_MEM_SAMPLE_KEYS. Pre-existing pi-lens findings in cgroup_delegated / _brake_high_value left untouched (outside this item).
8
+
9
+ ## 2026-09-18 host_cgroup_memory_limit.defer bool
10
+
11
+ - item: host_cgroup_memory_limit events carry defer as a bool equal to _probe_and_emit_cgroup_defer's return value (True when both caps are finite and plausible, including MemorySwapMax=0).
12
+ - rationale: plan prefers locking the defer-field contract before e2e fixture work. First remaining unfinished item after sample() IO-PSI.
13
+ - files: agent_runner/_emit/memory.py, agent_runner/cli/_serve_cgroup.py, tests/unit/test_cgroup_probe.py, tests/e2e/test_e2e_pre_oom.py
14
+ - verification: `.venv/bin/python -m pytest tests/unit/test_cgroup_probe.py -q` → 55 passed; `.venv/bin/ruff check . && .venv/bin/ruff format --check .` clean
15
+ - notes: defer is a required emit kwarg (every probe is yes/no). Did not touch runbook.md (docs item). Pre-existing pi-lens typing findings in _serve_cgroup left untouched.
16
+
17
+ ## 2026-09-18 round_cgroup_memory corroborating PSI fields
18
+
19
+ - item: round_cgroup_memory may carry optional flat io_psi_some_avg10, io_psi_full_avg10, and psi_full_total from metrics.sample() at emit time. They are omitted when unread. They are never copied into Pressure.context.
20
+ - rationale: plan prefers locking ladder-blindness and defer-field contracts before e2e fixture work. First remaining unfinished item after host_cgroup_memory_limit.defer.
21
+ - files: agent_runner/_emit/memory.py, agent_runner/cli/_serve_cgroup.py, tests/unit/test_serve_round_cgroup_spine.py
22
+ - verification: `.venv/bin/python -m pytest tests/unit/test_serve_round_cgroup_spine.py tests/unit/test_serve_round_oom_killed.py -q` → 19 passed; `.venv/bin/ruff check . && .venv/bin/ruff format --check .` clean
23
+ - notes: assumed sample() runs only on the successful emit path (skip-no-op paths do not sample); each None key is omitted independently. Did not edit host_health.py, events.md, or runbook.md. Pre-existing pi-lens typing findings in _serve_cgroup left untouched.
24
+
25
+ ## 2026-09-18 extract growth_child.py
26
+
27
+ - item: The paced pre-OOM child lives in tests/e2e/growth_child.py (8 MiB every 2.5s, page-touch). pi_growth_script installs that file instead of an inlined string. Importing the module does not start the infinite loop.
28
+ - rationale: plan prefers extracting growth_child.py after ladder-blindness/defer-field contracts, before skip-closed control tests. First remaining unfinished item.
29
+ - files: tests/e2e/growth_child.py, tests/e2e/conftest.py, tests/unit/test_growth_child_constants.py
30
+ - verification: `.venv/bin/python -m pytest tests/unit/test_growth_child_constants.py -q` → 1 passed; `.venv/bin/ruff check . && .venv/bin/ruff format --check .` clean
31
+ - notes: assumed the constants test lives under tests/unit because CI and build.sh --ignore=tests/e2e. pi_growth_script still base64-over-ssh (same install path), reading tests/e2e/growth_child.py bytes instead of an inlined string. Importing the module does not enter main().
32
+
33
+ ## 2026-09-18 skip-closed control-arm e2e
34
+
35
+ - item: A skip-closed control-arm e2e exists: same unit as treatment except MemorySwapMax=0, MemoryHigh=120M kept, no oom.group and no OOMPolicy=kill. Unset AGENT_RUNNER_E2E_CGROUP and AGENT_RUNNER_E2E_PI, the new test skips. Assertions in source require defer true, no round_mem_terminated, no memory_high_engaged, events_oom_kill_delta>=1 or INVALID, DEFER-FIRST or KERNEL-FIRST named, growth child gone, host still answers.
36
+ - rationale: plan prefers skip-closed control tests after extracting growth_child.py. First remaining unfinished item.
37
+ - files: tests/e2e/conftest.py, tests/e2e/test_e2e_pre_oom.py
38
+ - verification: `.venv/bin/python -m pytest tests/e2e/test_e2e_pre_oom.py -q` → 3 skipped; `.venv/bin/ruff check . && .venv/bin/ruff format --check .` clean
39
+ - notes: assumed PI control pytest timeout includes +600 install budget matching treatment; local uses +120 per plan. Two thin wrappers (PI + local CGROUP) share `_assert_control`. Skip-closed (unset env => skip) is the honest local proof; did not export e2e flags or run sudo/systemctl. Did not add OOMPolicy or memory.oom.group.
40
+
41
+ ## 2026-09-18 SECURITY reframe + CHANGELOG stub
42
+
43
+ - item: Root SECURITY.md gains one paragraph: isolation is the operator's job (exec_prefix, dedicated user, no passwordless sudo, egress); agent-runner stays lifecycle-safety. CHANGELOG adds ## [0.3.13] - UNRELEASED. No docs/SECURITY.md. Optional results template under gitignored docs/internal/notes is allowed and must not be staged.
44
+ - rationale: plan says docs last. Last remaining unfinished item.
45
+ - files: SECURITY.md, CHANGELOG.md
46
+ - verification: `.venv/bin/python -m pytest tests/invariants/test_docs_index_complete.py tests/invariants/test_doc_claims_match_ssot.py -q` → 4 passed; `.venv/bin/ruff check . && .venv/bin/ruff format --check .` clean; `docs/SECURITY.md` absent
47
+ - notes: assumed not naming a competitor in SECURITY.md (Ralph item asks for the operator-vs-lifecycle split). Did not edit runbook.md (not in the Ralph item). Results template written under gitignored docs/internal/notes and not staged. Did not claim uncapped-before-coma or a live two-arm run.
@@ -5,6 +5,19 @@ 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.13] - 2026-09-18
9
+
10
+ ### Added
11
+ - Two production cgroup modes are now checkable on the same paced growth child: treatment (`MemorySwapMax` infinity) still cooperatively terminates and reaps; control (`MemorySwapMax=0`) defers to kernel cgroup-OOM of the capped child. Both leave the host up. The uncapped-before-coma case stays inferred.
12
+ - `metrics.sample()` always includes corroborating `psi_full_total`, `io_psi_some_avg10`, and `io_psi_full_avg10` (`None` when unread). They are not kill rungs; `host_health.memory_pressure` stays blind to them.
13
+ - `host_cgroup_memory_limit` carries `defer` as a bool (`True` when both caps are finite and plausible, including `MemorySwapMax=0`).
14
+ - `round_cgroup_memory` may carry those PSI fields as optional flat keys at emit time; omitted when unread.
15
+
16
+ ### Docs
17
+ - SECURITY.md: isolation is the operator's job (`exec_prefix`, dedicated user, no passwordless sudo, egress); agent-runner stays lifecycle-safety.
18
+
19
+ No config migration required. `peek --json` schema stays `2.6`.
20
+
8
21
  ## [0.3.12] - 2026-09-18
9
22
 
10
23
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.3.12
3
+ Version: 0.3.13
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
@@ -37,6 +37,12 @@ unprivileged user, inside a container or VM, without passwordless sudo, and
37
37
  with egress limits. The supervisor's defenses bound the blast radius of a
38
38
  *misbehaving* agent, not a *hostile* one.
39
39
 
40
+ Isolation is the operator's job. Isolation-enforcing supervisors exist;
41
+ agent-runner deliberately is not one of them. Lifecycle-safety stays ours
42
+ (round timeouts, process-group reaping, orphan stashing, auto-stop on
43
+ critical alerts). Containment stays yours: `[agent] exec_prefix`, a dedicated
44
+ unprivileged user, no passwordless sudo, and egress limits.
45
+
40
46
  ## Containment
41
47
 
42
48
  If you need to bound what the agent can reach, run it inside a container (or
@@ -112,6 +112,7 @@ def emit_host_cgroup_memory_limit(
112
112
  memory_max: int | None,
113
113
  memory_swap_max: int | None,
114
114
  cgroup_path: str | None,
115
+ defer: bool,
115
116
  swap_total_bytes: int | None = None,
116
117
  swap_cap_pct: float | None = None,
117
118
  memory_high: int | None = None,
@@ -120,8 +121,11 @@ def emit_host_cgroup_memory_limit(
120
121
  ) -> None:
121
122
  """Emit once at serve startup: this process's cgroup v2 memory budget
122
123
  (``metrics.cgroup_memory_limits``). ``None`` fields mean unlimited (or
123
- cgroup v2 unavailable). Serve uses this once to decide whether the
124
- mid-round hard floor can defer to kernel cgroup-OOM -- see
124
+ cgroup v2 unavailable). ``defer`` is the already-computed mid-round-floor
125
+ decision (True when both caps are finite and plausible, including
126
+ ``MemorySwapMax=0``) -- the event carries that yes/no, not just the
127
+ inputs. Serve uses this once to decide whether the mid-round hard floor
128
+ can defer to kernel cgroup-OOM -- see
125
129
  ``emit_mem_pressure_deferred_to_cgroup`` below.
126
130
 
127
131
  This also carries an optional startup advisory
@@ -152,6 +156,7 @@ def emit_host_cgroup_memory_limit(
152
156
  memory_max=memory_max,
153
157
  memory_swap_max=memory_swap_max,
154
158
  cgroup_path=cgroup_path,
159
+ defer=defer,
155
160
  swap_total_bytes=swap_total_bytes,
156
161
  swap_cap_pct=swap_cap_pct,
157
162
  memory_high=memory_high,
@@ -171,6 +176,9 @@ def emit_round_cgroup_memory(
171
176
  events_oom_delta: int,
172
177
  events_oom_kill_delta: int,
173
178
  bounding_cgroup_path: str | None,
179
+ io_psi_some_avg10: float | None = None,
180
+ io_psi_full_avg10: float | None = None,
181
+ psi_full_total: float | int | None = None,
174
182
  ) -> None:
175
183
  """Emit once per round: the round's peak cgroup ``memory.current`` (+swap) and
176
184
  the ``memory.events`` DELTAS (high/max/oom/oom_kill) over the round, read at the
@@ -185,9 +193,24 @@ def emit_round_cgroup_memory(
185
193
  process's own) cgroup; here it names the BOUNDING ANCESTOR (the one whose
186
194
  ``memory.max`` actually binds). Same key, two meanings across the two
187
195
  events an operator correlates would be a trap -- events.md pins field
188
- semantics permanently once shipped, so the distinct name is chosen now."""
196
+ semantics permanently once shipped, so the distinct name is chosen now.
197
+
198
+ Optional corroborating PSI fields (``io_psi_some_avg10``,
199
+ ``io_psi_full_avg10``, ``psi_full_total``) come from ``metrics.sample()``
200
+ at emit time. Each is omitted when unread (``None``). They ride FLAT on
201
+ this event -- never copied into ``Pressure.context``, so the kill ladder
202
+ stays blind to them."""
189
203
  from agent_runner.events import ROUND_CGROUP_MEMORY, emit
190
204
 
205
+ extra = {
206
+ k: v
207
+ for k, v in (
208
+ ("io_psi_some_avg10", io_psi_some_avg10),
209
+ ("io_psi_full_avg10", io_psi_full_avg10),
210
+ ("psi_full_total", psi_full_total),
211
+ )
212
+ if v is not None
213
+ }
191
214
  emit(
192
215
  log_dir,
193
216
  ROUND_CGROUP_MEMORY,
@@ -199,6 +222,7 @@ def emit_round_cgroup_memory(
199
222
  events_oom_delta=events_oom_delta,
200
223
  events_oom_kill_delta=events_oom_kill_delta,
201
224
  bounding_cgroup_path=bounding_cgroup_path,
225
+ **extra,
202
226
  )
203
227
 
204
228
 
@@ -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.12'
22
- __version_tuple__ = version_tuple = (0, 3, 12)
21
+ __version__ = version = '0.3.13'
22
+ __version_tuple__ = version_tuple = (0, 3, 13)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -128,6 +128,8 @@ def _emit_round_cgroup_memory(
128
128
  def _delta(key: str) -> int:
129
129
  return max(0, cur_ev.get(key, 0) - base_ev.get(key, 0))
130
130
 
131
+ # One sample() at emit -- corroborating IO/mem-PSI, omitted when unread.
132
+ s = metrics.sample()
131
133
  emit_round_cgroup_memory(
132
134
  log_dir,
133
135
  round_num=round_num,
@@ -138,6 +140,9 @@ def _emit_round_cgroup_memory(
138
140
  events_oom_delta=_delta("oom"),
139
141
  events_oom_kill_delta=_delta("oom_kill"),
140
142
  bounding_cgroup_path=state["bounding_cgroup_path"],
143
+ io_psi_some_avg10=s.get("io_psi_some_avg10"),
144
+ io_psi_full_avg10=s.get("io_psi_full_avg10"),
145
+ psi_full_total=s.get("psi_full_total"),
141
146
  )
142
147
  return cur, oom_kill_baseline
143
148
 
@@ -277,8 +282,9 @@ def _release_brake(log_dir: Path, round_num: int, previous: str, *, reason: str)
277
282
 
278
283
  def _probe_and_emit_cgroup_defer(log_dir: Path, *, brake_memory_high: bool = False) -> bool:
279
284
  """Probe this process's cgroup v2 memory budget once at serve startup,
280
- emit host_cgroup_memory_limit for observability, and return whether the
281
- mid-round hard floor should defer to kernel cgroup-OOM: True only when
285
+ emit host_cgroup_memory_limit (including the ``defer`` decision, not just
286
+ the inputs) for observability, and return whether the mid-round hard
287
+ floor should defer to kernel cgroup-OOM: True only when
282
288
  BOTH memory.max and memory.swap.max are finite (the field host's
283
289
  MemoryMax=320M + MemorySwapMax=160M shape) -- that budget is bounded end
284
290
  to end, so cgroup-OOM WILL fire and contain the agent while the host
@@ -401,6 +407,7 @@ def _probe_and_emit_cgroup_defer(log_dir: Path, *, brake_memory_high: bool = Fal
401
407
  memory_max=limits["memory_max"],
402
408
  memory_swap_max=swap_max,
403
409
  cgroup_path=limits["cgroup_path"],
410
+ defer=defer,
404
411
  swap_total_bytes=swap_total,
405
412
  swap_cap_pct=swap_cap_pct,
406
413
  memory_high=memory_high,
@@ -17,14 +17,18 @@ from agent_runner.clock import SYSTEM_CLOCK
17
17
  from agent_runner.events import now_iso_ms
18
18
 
19
19
  _PSI_MEMORY_PATH = Path("/proc/pressure/memory")
20
+ _PSI_IO_PATH = Path("/proc/pressure/io")
20
21
 
21
22
 
22
- def _read_psi(path: Path = _PSI_MEMORY_PATH) -> tuple[float, float] | None:
23
- """Parse ``/proc/pressure/memory``'s ``some``/``full`` ``avg10`` fields.
23
+ def _read_psi(path: Path = _PSI_MEMORY_PATH) -> tuple[float, float, int | None] | None:
24
+ """Parse a PSI file's ``some``/``full`` ``avg10`` and full-line ``total``.
24
25
 
25
- Returns ``None`` when the file is absent (non-Linux, or a kernel built
26
- without ``CONFIG_PSI``) or unreadable (``psi=0`` boot param) — the
27
- caller (``host_health``) degrades gracefully down the signal ladder.
26
+ Returns ``(some_avg10, full_avg10, full_total)``. ``full_avg10`` is ``0.0``
27
+ when the full line is missing; ``full_total`` is ``int(total=)`` from the
28
+ full line, else ``None``. Returns ``None`` when the file is absent
29
+ (non-Linux, or a kernel built without ``CONFIG_PSI``) or unreadable
30
+ (``psi=0`` boot param) — the caller (``host_health``) degrades
31
+ gracefully down the signal ladder.
28
32
  """
29
33
  try:
30
34
  text = path.read_text(encoding="utf-8")
@@ -32,11 +36,17 @@ def _read_psi(path: Path = _PSI_MEMORY_PATH) -> tuple[float, float] | None:
32
36
  return None
33
37
  some_avg10: float | None = None
34
38
  full_avg10: float | None = None
39
+ full_total: int | None = None
35
40
  for line in text.splitlines():
36
41
  parts = line.split()
37
42
  if not parts:
38
43
  continue
39
44
  fields = dict(p.split("=", 1) for p in parts[1:] if "=" in p)
45
+ if parts[0] == "full":
46
+ try:
47
+ full_total = int(fields["total"])
48
+ except (KeyError, ValueError):
49
+ pass
40
50
  raw = fields.get("avg10")
41
51
  if raw is None:
42
52
  continue
@@ -50,7 +60,7 @@ def _read_psi(path: Path = _PSI_MEMORY_PATH) -> tuple[float, float] | None:
50
60
  full_avg10 = value
51
61
  if some_avg10 is None:
52
62
  return None
53
- return some_avg10, full_avg10 if full_avg10 is not None else 0.0
63
+ return some_avg10, full_avg10 if full_avg10 is not None else 0.0, full_total
54
64
 
55
65
 
56
66
  def sample() -> dict[str, Any]:
@@ -58,22 +68,28 @@ def sample() -> dict[str, Any]:
58
68
 
59
69
  Deliberately NOT ``collect()`` (which shells out to ``pgrep`` for
60
70
  ``agent_process_count``, see ``_count_agent_processes`` below) — this is
61
- psutil counters + one optional ``/proc`` file read, safe to call every
62
- ~10s in a hot loop (e.g. a serve-loop mid-round check). ``host_health``
63
- is the pure interpreter of what this returns; this function only samples.
71
+ psutil counters + optional ``/proc/pressure/{memory,io}`` reads, safe to
72
+ call every ~10s in a hot loop (e.g. a serve-loop mid-round check).
73
+ ``host_health`` is the pure interpreter of what this returns; this
74
+ function only samples. IO-PSI and mem-PSI ``total`` are corroborating
75
+ fields the ladder ignores.
64
76
 
65
77
  ``swap_sout`` is cumulative (bytes swapped out since boot) — callers
66
78
  wanting a rate/delta diff two samples themselves.
67
79
  """
68
80
  vm = psutil.virtual_memory()
69
81
  swap = psutil.swap_memory()
70
- psi = _read_psi()
82
+ mem = _read_psi() or (None, None, None)
83
+ io_some, io_full, _ = _read_psi(_PSI_IO_PATH) or (None, None, None)
71
84
  return {
72
85
  "mem_available_mb": vm.available // (1024 * 1024),
73
86
  "mem_free_mb": vm.free // (1024 * 1024),
74
87
  "swap_sout": swap.sout,
75
- "psi_some_avg10": psi[0] if psi is not None else None,
76
- "psi_full_avg10": psi[1] if psi is not None else None,
88
+ "psi_some_avg10": mem[0],
89
+ "psi_full_avg10": mem[1],
90
+ "psi_full_total": mem[2],
91
+ "io_psi_some_avg10": io_some,
92
+ "io_psi_full_avg10": io_full,
77
93
  }
78
94
 
79
95
 
@@ -0,0 +1,95 @@
1
+ """Test clocks — FakeClock and TickingClock, both the full Clock surface.
2
+
3
+ Lives in tests/ (not agent_runner/) so it has no production consumer to keep
4
+ vulture happy.
5
+
6
+ * ``FakeClock`` pins epoch, monotonic, UTC-now and tz-now together; ``sleep``
7
+ advances virtual time, so a chunked pause loop terminates deterministically
8
+ instead of needing a monkeypatched ``stop`` flag.
9
+ * ``TickingClock`` is for real-subprocess poll loops: ``monotonic()`` jumps
10
+ ``step`` per call so a 10s sample interval elapses without waiting 10s;
11
+ ``sleep()`` is a real short block so the child can actually exit. Do not
12
+ substitute FakeClock there — FakeClock.sleep never yields to a live child.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import time
18
+ from datetime import UTC, datetime
19
+ from zoneinfo import ZoneInfo
20
+
21
+
22
+ class FakeClock:
23
+ def __init__(self, epoch: float = 1_700_000_000.0, *, start: str | None = None):
24
+ """``start``, when given, is an ISO-8601 UTC timestamp (e.g.
25
+ ``"2026-09-16T00:00:00Z"``) converted to ``epoch`` -- a more readable
26
+ alternative to a raw epoch float for tests that pin a calendar date."""
27
+ if start is not None:
28
+ epoch = datetime.fromisoformat(start.replace("Z", "+00:00")).timestamp()
29
+ self._epoch = epoch
30
+ self._mono = 0.0
31
+ self.slept: list[float] = []
32
+
33
+ # --- Clock surface -------------------------------------------------------
34
+ def epoch(self) -> float:
35
+ return self._epoch
36
+
37
+ def monotonic(self) -> float:
38
+ return self._mono
39
+
40
+ def sleep(self, seconds: float) -> None:
41
+ self.slept.append(seconds)
42
+ self.advance(seconds) # virtual time moves so pause loops make progress
43
+
44
+ def now_utc(self) -> datetime:
45
+ return datetime.fromtimestamp(self._epoch, UTC)
46
+
47
+ def now_in_zone(self, tz_name: str | None) -> datetime:
48
+ if tz_name is None:
49
+ return self.now_utc().astimezone()
50
+ return self.now_utc().astimezone(ZoneInfo(tz_name))
51
+
52
+ # --- test controls -------------------------------------------------------
53
+ def advance(self, seconds: float) -> None:
54
+ self._epoch += seconds
55
+ self._mono += seconds
56
+
57
+ def warp_epoch(self, delta: float) -> None:
58
+ """Shift the WALL clock only (simulates an NTP step); monotonic unmoved —
59
+ so a deadline measured on monotonic must be unaffected."""
60
+ self._epoch += delta
61
+
62
+
63
+ class TickingClock:
64
+ """Clock for real-subprocess poll loops.
65
+
66
+ ``monotonic()`` advances by ``step`` on every call so a ~10s sample interval
67
+ elapses without waiting ~10 real seconds. ``sleep()`` is a real short block
68
+ and does **not** advance monotonic — the poll fallback's per-tick pacing,
69
+ so a live child gets repeated chances to exit. Wall methods (``epoch`` /
70
+ ``now_utc`` / ``now_in_zone``) sit on a fixed epoch so this is a full
71
+ ``Clock``, not a two-method stub.
72
+ """
73
+
74
+ def __init__(self, step: float = 5.0, epoch: float = 1_700_000_000.0):
75
+ self._mono = 0.0
76
+ self._step = step
77
+ self._epoch = epoch
78
+
79
+ def epoch(self) -> float:
80
+ return self._epoch
81
+
82
+ def monotonic(self) -> float:
83
+ self._mono += self._step
84
+ return self._mono
85
+
86
+ def sleep(self, seconds: float) -> None:
87
+ time.sleep(seconds)
88
+
89
+ def now_utc(self) -> datetime:
90
+ return datetime.fromtimestamp(self._epoch, UTC)
91
+
92
+ def now_in_zone(self, tz_name: str | None) -> datetime:
93
+ if tz_name is None:
94
+ return self.now_utc().astimezone()
95
+ return self.now_utc().astimezone(ZoneInfo(tz_name))