cli-agent-runner 0.2.20__tar.gz → 0.2.21__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 (370) hide show
  1. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.vulture-whitelist.py +1 -0
  2. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/CHANGELOG.md +13 -0
  3. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/PKG-INFO +1 -1
  4. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/SECURITY.md +20 -0
  5. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_version.py +2 -2
  6. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/agent_runtime.py +33 -8
  7. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/serve_cmd.py +13 -1
  8. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/config/models.py +11 -0
  9. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/config/parsers.py +9 -0
  10. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/runner.py +1 -1
  11. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/startup_check.py +41 -2
  12. cli_agent_runner-0.2.21/deploy/Dockerfile +24 -0
  13. cli_agent_runner-0.2.21/deploy/docker-compose.yml +18 -0
  14. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/README.md +2 -0
  15. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/commands.md +1 -1
  16. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/configuration.md +5 -1
  17. cli_agent_runner-0.2.21/docs/recipes/container-pi.md +179 -0
  18. cli_agent_runner-0.2.21/docs/recipes/deploy-all-in-one.md +49 -0
  19. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_architecture.py +1 -0
  20. cli_agent_runner-0.2.21/tests/invariants/test_container_stop_bound.py +18 -0
  21. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime_container.py +46 -3
  22. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config.py +41 -0
  23. cli_agent_runner-0.2.21/tests/unit/test_exec_prefix.py +150 -0
  24. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_startup_check.py +51 -0
  25. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.codecov.yml +0 -0
  26. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  27. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  28. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  29. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  30. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.github/workflows/ci.yml +0 -0
  31. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.github/workflows/release.yml +0 -0
  32. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.gitignore +0 -0
  33. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/BACKLOG.md +0 -0
  34. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/CODE_OF_CONDUCT.md +0 -0
  35. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/CONTRIBUTING.md +0 -0
  36. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/LICENSE +0 -0
  37. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/README.md +0 -0
  38. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/README.zh.md +0 -0
  39. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/__init__.py +0 -0
  40. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_docgen.py +0 -0
  41. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_emit/__init__.py +0 -0
  42. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_emit/agent.py +0 -0
  43. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_emit/memory.py +0 -0
  44. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_emit/rounds.py +0 -0
  45. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_emit/serve.py +0 -0
  46. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_monitor_detectors.py +0 -0
  47. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_monitor_registry.py +0 -0
  48. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_monitor_state.py +0 -0
  49. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_plugin_scan.py +0 -0
  50. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_redact.py +0 -0
  51. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_registry.py +0 -0
  52. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_resolve.py +0 -0
  53. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_round_outcome.py +0 -0
  54. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_round_support.py +0 -0
  55. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_serve_policy.py +0 -0
  56. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_substrate.py +0 -0
  57. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_throttle.py +0 -0
  58. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/api.py +0 -0
  59. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/api_types.py +0 -0
  60. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/__init__.py +0 -0
  61. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/_constants.py +0 -0
  62. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  63. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/codewhale.py +0 -0
  64. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
  65. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/gemini.py +0 -0
  66. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/kimi.py +0 -0
  67. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/pi.py +0 -0
  68. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/__init__.py +0 -0
  69. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/__main__.py +0 -0
  70. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/_serve_cgroup.py +0 -0
  71. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/_serve_round.py +0 -0
  72. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/common.py +0 -0
  73. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/events_cmd.py +0 -0
  74. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/init_cmd.py +0 -0
  75. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/install_cmd.py +0 -0
  76. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/migrate_cmd.py +0 -0
  77. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/monitor_cmd.py +0 -0
  78. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/peek_cmd.py +0 -0
  79. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/round_cmd.py +0 -0
  80. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/service_cmd.py +0 -0
  81. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/upgrade_cmd.py +0 -0
  82. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/clock.py +0 -0
  83. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/config/__init__.py +0 -0
  84. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/config/errors.py +0 -0
  85. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/config/loader.py +0 -0
  86. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/config/validators.py +0 -0
  87. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/context_store.py +0 -0
  88. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/defenses.py +0 -0
  89. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/events.py +0 -0
  90. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/hooks.py +0 -0
  91. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/host_health.py +0 -0
  92. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/http_progress.py +0 -0
  93. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/lifecycle.py +0 -0
  94. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/metrics.py +0 -0
  95. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/migrations.py +0 -0
  96. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/monitor.py +0 -0
  97. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/phase_select.py +0 -0
  98. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/__init__.py +0 -0
  99. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/aider.toml +0 -0
  100. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/claude.toml +0 -0
  101. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/codewhale.toml +0 -0
  102. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/gemini.toml +0 -0
  103. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/kimi.toml +0 -0
  104. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/pi.toml +0 -0
  105. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/prompt_loader.py +0 -0
  106. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/remote_relay.py +0 -0
  107. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/round_log.py +0 -0
  108. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/round_view.py +0 -0
  109. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/scaffold.py +0 -0
  110. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/schedule.py +0 -0
  111. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/service_unit.py +0 -0
  112. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/vcs_state.py +0 -0
  113. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/build.sh +0 -0
  114. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/architecture.md +0 -0
  115. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/events.md +0 -0
  116. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/long-running-agents.md +0 -0
  117. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/memory-efficiency.md +0 -0
  118. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/migrations/0.2.md +0 -0
  119. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/plugins.md +0 -0
  120. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/quickstart.md +0 -0
  121. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/recipes/aider.md +0 -0
  122. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/recipes/codewhale.md +0 -0
  123. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/recipes/kimi.md +0 -0
  124. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/recipes/pi.md +0 -0
  125. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/runbook.md +0 -0
  126. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/thesis.md +0 -0
  127. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/pyproject.toml +0 -0
  128. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/__init__.py +0 -0
  129. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/_clock.py +0 -0
  130. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/_test_helpers.py +0 -0
  131. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/conftest.py +0 -0
  132. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/contract/__init__.py +0 -0
  133. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/contract/test_public_api_surface.py +0 -0
  134. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/e2e/__init__.py +0 -0
  135. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/e2e/conftest.py +0 -0
  136. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  137. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/e2e/test_e2e_install_systemd.py +0 -0
  138. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  139. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  140. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  141. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  142. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  143. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/generate_vulture_whitelist.py +0 -0
  144. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/__init__.py +0 -0
  145. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_auto_stop_e2e.py +0 -0
  146. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_bounded_run.py +0 -0
  147. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_context_enricher_namespacing.py +0 -0
  148. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_dirty_handler_seam.py +0 -0
  149. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_fresh_eyes_signal.py +0 -0
  150. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_grace_kill_emission.py +0 -0
  151. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_install_dry_run.py +0 -0
  152. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_monitor_seeded.py +0 -0
  153. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_plugin_detector_loaded.py +0 -0
  154. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_plugin_owned_paths.py +0 -0
  155. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_plugin_real_flow.py +0 -0
  156. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  157. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  158. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_scaffold_presets.py +0 -0
  159. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_serve_loop.py +0 -0
  160. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_spawn_round_mem_floor.py +0 -0
  161. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_spawn_round_wedged.py +0 -0
  162. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_substrate_fingerprint.py +0 -0
  163. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_transient_error_backoff.py +0 -0
  164. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/__init__.py +0 -0
  165. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/_docs.py +0 -0
  166. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/_event_scan.py +0 -0
  167. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_atomic_write_enforced.py +0 -0
  168. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_authored_facts_declared.py +0 -0
  169. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  170. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_bdd_test_conventions.py +0 -0
  171. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_catalogs.py +0 -0
  172. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_classification_ssot.py +0 -0
  173. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_config_error_consistency.py +0 -0
  174. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_config_value_set_ssot.py +0 -0
  175. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
  176. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  177. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
  178. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_cli_claims.py +0 -0
  179. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_operator_surface.py +0 -0
  180. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
  181. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  182. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_usage_event_fields.py +0 -0
  183. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_docs_generated.py +0 -0
  184. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_docs_index_complete.py +0 -0
  185. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_entry_points_resolve.py +0 -0
  186. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_event_int_coercion.py +0 -0
  187. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_event_kind_registry.py +0 -0
  188. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_event_kinds_ssot.py +0 -0
  189. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_events_doc_contract.py +0 -0
  190. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
  191. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_hook_contract_docs.py +0 -0
  192. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_import_footprint.py +0 -0
  193. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_layer_2_loop_size.py +0 -0
  194. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_migrations_registry_ssot.py +0 -0
  195. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_module_boundaries.py +0 -0
  196. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_module_sizes.py +0 -0
  197. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_monitor_self_exclusion.py +0 -0
  198. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_ai_signatures.py +0 -0
  199. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_internal_refs.py +0 -0
  200. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_price_logic.py +0 -0
  201. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  202. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_raw_time.py +0 -0
  203. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_vacuous_invariants.py +0 -0
  204. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_version_refs.py +0 -0
  205. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_peek_schema_version.py +0 -0
  206. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_phase_select_stateless.py +0 -0
  207. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_plugins_config_stable.py +0 -0
  208. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  209. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  210. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_round_alloc_growth.py +0 -0
  211. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_round_log_decode_boundary.py +0 -0
  212. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_round_num_single_source.py +0 -0
  213. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_round_result_stable.py +0 -0
  214. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  215. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  216. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_timeout_budget_invariant.py +0 -0
  217. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_upstream_schema_canary.py +0 -0
  218. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  219. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/literate/__init__.py +0 -0
  220. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/literate/parser.py +0 -0
  221. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/literate/test_parser.py +0 -0
  222. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/literate/test_quickstart.py +0 -0
  223. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/__init__.py +0 -0
  224. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime.py +0 -0
  225. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime_grace.py +0 -0
  226. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
  227. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime_progress.py +0 -0
  228. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime_reap.py +0 -0
  229. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  230. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_assemble_prompt.py +0 -0
  231. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_events_stream.py +0 -0
  232. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_install.py +0 -0
  233. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_observation.py +0 -0
  234. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_read_round_num.py +0 -0
  235. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_resolve_phase.py +0 -0
  236. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_service.py +0 -0
  237. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_types.py +0 -0
  238. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_auto_stop_gating.py +0 -0
  239. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_catalogs.py +0 -0
  240. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cgroup_probe.py +0 -0
  241. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_claude_error_detector.py +0 -0
  242. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_claude_foreign_schema.py +0 -0
  243. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli.py +0 -0
  244. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli_common.py +0 -0
  245. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli_init_install.py +0 -0
  246. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli_main_boundary.py +0 -0
  247. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli_monitor_http.py +0 -0
  248. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  249. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli_upgrade.py +0 -0
  250. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_codewhale_code_precedence.py +0 -0
  251. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_codewhale_plugin.py +0 -0
  252. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_coerce_int_poison.py +0 -0
  253. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_fresh_eyes.py +0 -0
  254. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_max_rounds.py +0 -0
  255. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_nested_strictness.py +0 -0
  256. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_prompt_delivery.py +0 -0
  257. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_schedule.py +0 -0
  258. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_stop_file.py +0 -0
  259. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_strictness.py +0 -0
  260. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  261. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_transient_error_action.py +0 -0
  262. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_context_store.py +0 -0
  263. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_control_char_config.py +0 -0
  264. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_default_dirty_handler.py +0 -0
  265. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_defenses.py +0 -0
  266. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_detect_hung_latch.py +0 -0
  267. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_detector_isolation.py +0 -0
  268. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_detector_protocol.py +0 -0
  269. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_dirty_handlers.py +0 -0
  270. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_docgen.py +0 -0
  271. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_effective_throttle_view.py +0 -0
  272. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_emit_config_migrated.py +0 -0
  273. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_emit_schedule.py +0 -0
  274. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
  275. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_events.py +0 -0
  276. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_events_cmd.py +0 -0
  277. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_events_cmd_rollover.py +0 -0
  278. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_events_cmd_tail.py +0 -0
  279. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_events_jsonl_decode.py +0 -0
  280. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_example_dirty_handler.py +0 -0
  281. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_example_plugin.py +0 -0
  282. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_exit_cause.py +0 -0
  283. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_exit_classifier.py +0 -0
  284. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  285. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_gemini_plugin.py +0 -0
  286. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_git_timeout.py +0 -0
  287. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_hook_failure_isolation.py +0 -0
  288. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_hook_redact_order.py +0 -0
  289. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_hooks.py +0 -0
  290. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_host_health.py +0 -0
  291. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_http_progress.py +0 -0
  292. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_http_progress_epoch.py +0 -0
  293. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_init_entry_points.py +0 -0
  294. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
  295. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_kimi_plugin.py +0 -0
  296. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_lifecycle.py +0 -0
  297. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_metrics.py +0 -0
  298. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_metrics_cgroup_usage.py +0 -0
  299. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_metrics_swap.py +0 -0
  300. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_migrate_cmd.py +0 -0
  301. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_migrations.py +0 -0
  302. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_assembly.py +0 -0
  303. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_cmd_severity.py +0 -0
  304. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_dedup_identity.py +0 -0
  305. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_dedup_rearm.py +0 -0
  306. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  307. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  308. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  309. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_detectors.py +0 -0
  310. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_event_tail.py +0 -0
  311. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_loop.py +0 -0
  312. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_on_alert.py +0 -0
  313. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_remote.py +0 -0
  314. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
  315. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_schedule_pause.py +0 -0
  316. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_network_blip_signal_guard.py +0 -0
  317. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_open_round_log.py +0 -0
  318. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_orphan_chain_single_pass.py +0 -0
  319. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_outer_round_ceiling.py +0 -0
  320. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_peek_argparse.py +0 -0
  321. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_peek_project_isolation.py +0 -0
  322. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_peek_schedule.py +0 -0
  323. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_peek_select.py +0 -0
  324. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_phase_agent_launch.py +0 -0
  325. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_phase_profiles.py +0 -0
  326. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_phase_select.py +0 -0
  327. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_pi_plugin.py +0 -0
  328. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_plugin_constants.py +0 -0
  329. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_plugin_scan_parity.py +0 -0
  330. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_presets.py +0 -0
  331. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_primary_prompt_file_home.py +0 -0
  332. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_prompt_loader.py +0 -0
  333. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_read_json_hardening.py +0 -0
  334. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_redact.py +0 -0
  335. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_resolve.py +0 -0
  336. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_round_cmd.py +0 -0
  337. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_round_cmd_sigterm.py +0 -0
  338. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_round_log_helpers.py +0 -0
  339. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_round_outcome.py +0 -0
  340. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_round_outcome_agent_axis.py +0 -0
  341. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_round_view.py +0 -0
  342. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_runner.py +0 -0
  343. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_runner_sidecar_ordering.py +0 -0
  344. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_runner_throttle.py +0 -0
  345. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_scaffold.py +0 -0
  346. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_schedule.py +0 -0
  347. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_cmd_bounded.py +0 -0
  348. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_config_broken.py +0 -0
  349. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_crash_loop.py +0 -0
  350. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_mem_gate.py +0 -0
  351. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_phase_schedule.py +0 -0
  352. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_policy.py +0 -0
  353. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_round.py +0 -0
  354. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
  355. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_round_log.py +0 -0
  356. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_round_oom_killed.py +0 -0
  357. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_round_terminate.py +0 -0
  358. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_schedule_gate.py +0 -0
  359. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_sentinel.py +0 -0
  360. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_startup_hooks.py +0 -0
  361. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_service_mode.py +0 -0
  362. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_service_unit.py +0 -0
  363. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_startup_battery_exit.py +0 -0
  364. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_substrate.py +0 -0
  365. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
  366. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_throttle_back_off_ladder.py +0 -0
  367. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_throttle_epoch_bound.py +0 -0
  368. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_throttle_plugin_classification.py +0 -0
  369. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_upgrade_migrate.py +0 -0
  370. {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_vcs_state.py +0 -0
@@ -133,6 +133,7 @@ prompt_arg_template
133
133
  name
134
134
  env
135
135
  prompt_delivery
136
+ exec_prefix
136
137
 
137
138
  # agent_runner.config.models.Config
138
139
  agent
@@ -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.2.21] - 2026-09-11
9
+
10
+ ### Added
11
+ - `[agent] exec_prefix`: an optional token list prepended to the agent command at spawn (with `{work_dir}` substituted), so the agent can run inside a container you supply (`docker`/`podman run …`) while agent-runner supervises the resulting process from outside. The agent's identity — throttle, crash-loop, usage plugins — still keys on `command`, unchanged.
12
+ - Startup check: a stdin-delivery agent whose container `exec_prefix` omits `-i`/`--interactive` now fails loudly at boot instead of silently hanging until the round-timeout kill.
13
+ - Docs: container recipes (`docs/recipes/container-pi.md`, with Kimi/Claude/rootless-podman deltas), an all-in-one deploy recipe (`deploy/` + `docs/recipes/deploy-all-in-one.md`), and a `SECURITY.md` containment section.
14
+
15
+ ### Fixed
16
+ - The best-effort container stop no longer reports a false "stop failed" when a `--rm` container has already exited on its own; an already-removed container now counts as a successful stop.
17
+
18
+ ### Notes
19
+ - Not breaking: no `agent-runner migrate` step, no systemd unit change.
20
+
8
21
  ## [0.2.20] - 2026-09-09
9
22
 
10
23
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.2.20
3
+ Version: 0.2.21
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,26 @@ 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
+ ## Containment
41
+
42
+ If you need to bound what the agent can reach, run it inside a container (or
43
+ VM) you supply, via `[agent] exec_prefix` — see
44
+ [docs/recipes/container-pi.md](docs/recipes/container-pi.md). The container
45
+ (or VM) enforces the isolation; agent-runner still just watches the
46
+ resulting process from outside, the same way it watches a bare agent
47
+ process.
48
+
49
+ One limit worth knowing: agent-runner's own process-tree defenses (reaping a
50
+ hung round's descendants) only see processes in its own subtree, and a
51
+ container-runtime-managed process usually isn't one of them. On a stuck
52
+ round, the one remaining lever is stopping the container by the id
53
+ agent-runner captured at spawn, which only works for a plain `docker
54
+ run …`/`podman run …` invocation — not one wrapped in `sudo` or other global
55
+ flags (see container-pi.md's footguns). Using a container this way gives you
56
+ a narrower safety net than the host-level reaping a bare agent process gets,
57
+ not a wider one — keep running it as a dedicated unprivileged user with no
58
+ passwordless sudo and tight egress limits regardless.
59
+
40
60
  ## Scope
41
61
 
42
62
  In scope: the `agent_runner` Python package, its CLI, its bundled systemd
@@ -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.2.20'
22
- __version_tuple__ = version_tuple = (0, 2, 20)
21
+ __version__ = version = '0.2.21'
22
+ __version_tuple__ = version_tuple = (0, 2, 21)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -26,6 +26,11 @@ from agent_runner.clock import SYSTEM_CLOCK, Clock
26
26
 
27
27
  REAP_GRACE_S = 5
28
28
 
29
+ # Wall-clock bound on the best-effort `<runtime> stop` subprocess. Pinned by an
30
+ # invariant test to stay <= _serve_policy._ROUND_TERM_GRACE_S so serve's
31
+ # round-terminate escalation never SIGKILLs round_cmd mid-stop.
32
+ _CONTAINER_STOP_TIMEOUT_S = REAP_GRACE_S + 5
33
+
29
34
  # Backstop cap on the stored child "name" (see _live_children). The primary
30
35
  # source (kernel comm) is already bounded (15 bytes on Linux); this only
31
36
  # guards the argv[0]-basename fallback used when comm is unavailable.
@@ -362,6 +367,20 @@ def _detect_container_run(command: list[str]) -> tuple[str, int] | None:
362
367
  return None
363
368
 
364
369
 
370
+ # Matches the docker/podman flags that attach the container's stdin: `-i`, any
371
+ # short cluster containing `i` (`-it`, `-ti`, `-di`), and `--interactive[=…]`;
372
+ # NOT `--init` (double-dash, so the single-dash alternative can't consume it).
373
+ _INTERACTIVE_FLAG = re.compile(r"^(-[a-z]*i[a-z]*|--interactive(=.*)?)$")
374
+
375
+
376
+ def _exec_prefix_is_interactive(exec_prefix: list[str]) -> bool:
377
+ """True when the operator's ``exec_prefix`` carries a stdin-attaching flag.
378
+ Only the prefix's own docker flags matter -- the wrapped ``command`` runs
379
+ INSIDE the container, so an ``-i`` there is irrelevant to whether the
380
+ runtime forwards stdin."""
381
+ return any(_INTERACTIVE_FLAG.match(tok) for tok in exec_prefix)
382
+
383
+
365
384
  # `docker run`/`podman run` OPTIONS known to take a separate value token --
366
385
  # used only to walk PAST them without mistaking their value for IMAGE (e.g.
367
386
  # `--name foo`: without this, a naive scan would stop at "foo" thinking it's
@@ -479,10 +498,12 @@ def _best_effort_container_stop(
479
498
  ``(container_id, stop_ok)``: `container_id` is None when no id could be
480
499
  recovered (the container never actually started, or the cidfile is still
481
500
  empty) -- `stop_ok` is then also None, meaning no stop was attempted.
482
- Otherwise `stop_ok` is whether the `stop` subprocess exited zero. Every
483
- failure here is swallowed -- this is advisory best-effort, never a
484
- guarantee; the caller's loud warning + event is the actual floor when
485
- this doesn't land."""
501
+ Otherwise `stop_ok` is True when the `stop` subprocess exited zero, or
502
+ when it failed because the container was already gone (a `--rm`
503
+ container can self-remove before our stop lands -- that's not a failure
504
+ to report). Every other failure here is swallowed -- this is advisory
505
+ best-effort, never a guarantee; the caller's loud warning + event is the
506
+ actual floor when this doesn't land."""
486
507
  if cidfile is None:
487
508
  return None, None
488
509
  try:
@@ -493,12 +514,16 @@ def _best_effort_container_stop(
493
514
  return None, None
494
515
  try:
495
516
  result = subprocess.run(
496
- [runtime, "stop", cid],
517
+ [runtime, "stop", "-t", str(REAP_GRACE_S), cid],
497
518
  stdout=subprocess.DEVNULL,
498
- stderr=subprocess.DEVNULL,
499
- timeout=10,
519
+ stderr=subprocess.PIPE,
520
+ timeout=_CONTAINER_STOP_TIMEOUT_S,
500
521
  )
501
- return cid, result.returncode == 0
522
+ if result.returncode == 0:
523
+ return cid, True
524
+ # A `--rm` container that already exited is GONE, not orphaned — not a failure.
525
+ stderr = (result.stderr or b"").decode("utf-8", "replace").lower()
526
+ return cid, "no such container" in stderr
502
527
  except (OSError, subprocess.TimeoutExpired):
503
528
  return cid, False
504
529
 
@@ -29,6 +29,7 @@ from agent_runner._throttle import (
29
29
  round_outcome,
30
30
  round_was_mem_terminated,
31
31
  )
32
+ from agent_runner.agent_runtime import _detect_container_run
32
33
  from agent_runner.api import (
33
34
  check_self_terminated_sentinel,
34
35
  emit_config_broken,
@@ -611,7 +612,9 @@ def cmd(args) -> int:
611
612
  return fail_code
612
613
  stop_file = cfg.runtime.stop_file # cache: same pattern as effective_max_rounds
613
614
  work_dir = cfg.runtime.work_dir
614
- defer_to_cgroup = _probe_and_emit_cgroup_defer(log_dir)
615
+ # Emitted exactly once per serve lifetime -- the per-round defer decision below
616
+ # reuses this bool rather than re-probing/re-emitting every round.
617
+ cgroup_probe_defer = _probe_and_emit_cgroup_defer(log_dir)
615
618
  rounds_completed = 0
616
619
  # Three independent consecutive-failure counters, one per breaker: b12
617
620
  # crash-loop (unknown short crashes), mem-loop (mem-terminated
@@ -685,6 +688,15 @@ def cmd(args) -> int:
685
688
  ]
686
689
  if phase_arg is not None:
687
690
  round_argv += ["--phase", phase_arg]
691
+ # Phase-aware, per-round: a phase can override [phases.<name>.agent]
692
+ # command to a bare container run even when the base agent isn't one
693
+ # (exec_prefix is base-only, but command isn't) -- computing this once
694
+ # from the base agent would wrongly keep deferring a containerized
695
+ # phase's mid-round kill to dockerd's own cgroup-OOM.
696
+ defer_to_cgroup = cgroup_probe_defer and (
697
+ _detect_container_run(cfg.profile_for(phase_arg).agent.spawn_command(work_dir))
698
+ is None
699
+ )
688
700
  r_returncode = _spawn_round(
689
701
  round_argv,
690
702
  round_log_path,
@@ -30,6 +30,7 @@ class AgentConfig:
30
30
  name: str | None = None
31
31
  env: dict[str, str] = field(default_factory=dict)
32
32
  prompt_delivery: Literal["argv", "stdin"] = "argv"
33
+ exec_prefix: list[str] = field(default_factory=list)
33
34
 
34
35
  @property
35
36
  def binary(self) -> str | None:
@@ -40,6 +41,16 @@ class AgentConfig:
40
41
  serve and runner don't each re-spell ``Path(command[0]).name``."""
41
42
  return Path(self.command[0]).name if self.command else None
42
43
 
44
+ def spawn_command(self, work_dir: Path) -> list[str]:
45
+ """The effective argv to spawn: an opaque operator-supplied ``exec_prefix``
46
+ (e.g. ``docker run … img``) spliced BEFORE ``command``, with the one
47
+ ``{work_dir}`` token substituted IN THE PREFIX ONLY. ``command`` (and thus
48
+ ``binary``) is appended verbatim and never rewritten, so the
49
+ throttle/crash-loop/hook identity join stays keyed on the real agent. Empty
50
+ ``exec_prefix`` ⇒ ``command`` unchanged. The SSOT both the spawn (runner) and
51
+ the startup battery resolve, so they can't drift."""
52
+ return [t.replace("{work_dir}", str(work_dir)) for t in self.exec_prefix] + self.command
53
+
43
54
 
44
55
  @dataclass(frozen=True)
45
56
  class RuntimeConfig:
@@ -106,12 +106,16 @@ def _parse_agent(
106
106
  f"{field_prefix} command: must be a non-empty list; run `agent-runner migrate`"
107
107
  )
108
108
  env_d = _require_table(agent_d, "env", label=f"{field_prefix.strip('[]')}.env")
109
+ exec_prefix = _require_str_list(
110
+ agent_d.get("exec_prefix", []), field=f"{field_prefix} exec_prefix"
111
+ )
109
112
  return AgentConfig(
110
113
  command=command,
111
114
  prompt_arg_template=prompt_arg_template,
112
115
  name=agent_d.get("name"),
113
116
  env={str(k): str(v) for k, v in env_d.items()},
114
117
  prompt_delivery=prompt_delivery, # type: ignore[arg-type] # narrowed above
118
+ exec_prefix=exec_prefix,
115
119
  )
116
120
 
117
121
 
@@ -214,6 +218,11 @@ def _parse_phase_overrides(
214
218
  agent_sub = value["agent"]
215
219
  if not isinstance(agent_sub, dict):
216
220
  raise ConfigError(f"[phases.{phase_name}.agent] must be a table")
221
+ if "exec_prefix" in agent_sub:
222
+ raise ConfigError(
223
+ f"[phases.{phase_name}.agent] exec_prefix: not allowed per-phase — set "
224
+ "exec_prefix once on the base [agent] table (it applies to every phase)"
225
+ )
217
226
  merged = {**agent_d, **agent_sub}
218
227
  phase_agent = _parse_agent(
219
228
  merged,
@@ -554,7 +554,7 @@ def _run_one_round_inner(cfg: Config, *, phase_override: str | None = None) -> R
554
554
  )
555
555
 
556
556
  result = agent_runtime.run(
557
- command=profile.agent.command,
557
+ command=profile.agent.spawn_command(cfg.runtime.work_dir),
558
558
  prompt_arg_template=profile.agent.prompt_arg_template,
559
559
  prompt=prompt,
560
560
  prompt_delivery=profile.agent.prompt_delivery,
@@ -50,13 +50,16 @@ def _check_log_dir(cfg: Config) -> CheckResult:
50
50
  def _check_agent_target(agent: AgentConfig, work_dir: Path, name: str) -> CheckResult:
51
51
  if not agent.command:
52
52
  return CheckResult(name, False, "agent.command is empty", permanent=True)
53
- cli = agent.command[0]
53
+ cli = agent.spawn_command(work_dir)[0]
54
54
  # Validate with the exact resolution the spawn uses (agent_runtime owns
55
55
  # the model): slash-containing commands resolve against work_dir (the
56
56
  # child's cwd); bare names use the CHILD's PATH ([agent.env] may set it).
57
57
  resolved = agent_runtime.resolve_exec_target(cli, work_dir, env_path=agent.env.get("PATH"))
58
58
  if resolved is None:
59
59
  relative = "/" in cli
60
+ # cli is exec_prefix[0] when a prefix is set (spawn_command splices the
61
+ # prefix first) -- pointing the fix at agent.command[0] would be wrong.
62
+ target = "[agent] exec_prefix[0]" if agent.exec_prefix else "agent.command[0]"
60
63
  return CheckResult(
61
64
  name,
62
65
  False,
@@ -68,13 +71,41 @@ def _check_agent_target(agent: AgentConfig, work_dir: Path, name: str) -> CheckR
68
71
  how_to_fix=(
69
72
  "fix the path relative to runtime.work_dir, or use an absolute path"
70
73
  if relative
71
- else f"install {cli} or set agent.command[0] to its absolute path"
74
+ else f"install {cli} or set {target} to its absolute path"
72
75
  ),
73
76
  permanent=True,
74
77
  )
75
78
  return CheckResult(name, True)
76
79
 
77
80
 
81
+ def _check_stdin_container_interactive(
82
+ agent: AgentConfig, work_dir: Path, name: str
83
+ ) -> CheckResult:
84
+ """A stdin-delivery agent whose container run drops stdin (no -i/--interactive)
85
+ silently hangs until the round-timeout kill -- fail loud at boot instead."""
86
+ if agent.prompt_delivery != "stdin":
87
+ return CheckResult(name, True)
88
+ argv = agent.spawn_command(work_dir)
89
+ if agent_runtime._detect_container_run(argv) is None:
90
+ return CheckResult(name, True)
91
+ # Whether docker forwards stdin depends ONLY on docker's own flags
92
+ # (exec_prefix) -- agent.command runs INSIDE the container, so an -i/
93
+ # --interactive there is irrelevant and must not satisfy this check.
94
+ if agent_runtime._exec_prefix_is_interactive(agent.exec_prefix):
95
+ return CheckResult(name, True)
96
+
97
+ return CheckResult(
98
+ name,
99
+ False,
100
+ reason="stdin prompt delivery into a container, but exec_prefix lacks -i/--interactive",
101
+ how_to_fix=(
102
+ 'add "-i" to [agent] exec_prefix (docker drops stdin without it '
103
+ "→ the agent hangs until the round-timeout kill)"
104
+ ),
105
+ permanent=True,
106
+ )
107
+
108
+
78
109
  def _check_work_dir_is_git(cfg: Config) -> CheckResult:
79
110
  from agent_runner.vcs_state import GitTimeout, is_git_repo
80
111
 
@@ -194,6 +225,14 @@ def _agent_cli_checks(cfg: Config) -> list[CheckResult]:
194
225
  profile = cfg.profile_for(phase)
195
226
  name = "agent_cli_in_path" if phase is None else f"agent_cli_in_path:{phase}"
196
227
  results.append(_check_agent_target(profile.agent, cfg.runtime.work_dir, name))
228
+ stdin_name = (
229
+ "stdin_container_interactive"
230
+ if phase is None
231
+ else f"stdin_container_interactive:{phase}"
232
+ )
233
+ results.append(
234
+ _check_stdin_container_interactive(profile.agent, cfg.runtime.work_dir, stdin_name)
235
+ )
197
236
  return results
198
237
 
199
238
 
@@ -0,0 +1,24 @@
1
+ # Template Dockerfile for running the whole unit (supervisor + agent CLI) in
2
+ # one container. This is a recipe to copy and adapt into your own project —
3
+ # see docs/recipes/deploy-all-in-one.md — not a published or maintained image.
4
+
5
+ FROM python:3.12-slim
6
+
7
+ # git is required: agent-runner's dirty-tree detection shells out to it.
8
+ RUN apt-get update && apt-get install -y --no-install-recommends git \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ RUN pip install --no-cache-dir cli-agent-runner
12
+ # Pin a specific release for reproducible builds if you need one, e.g.
13
+ # `cli-agent-runner==X.Y.Z`.
14
+
15
+ # --- Agent CLI slot -----------------------------------------------------
16
+ # agent-runner is CLI-agnostic and ships none. Install whichever agent you
17
+ # configured in agent-runner.toml here, e.g. (needs a Node base image, or an
18
+ # added Node install step first):
19
+ # RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent
20
+ # -------------------------------------------------------------------------
21
+
22
+ WORKDIR /work
23
+ ENTRYPOINT ["agent-runner"]
24
+ CMD ["serve"]
@@ -0,0 +1,18 @@
1
+ # Template compose file for the all-in-one deploy recipe — copy alongside
2
+ # Dockerfile into your own project and adapt. See
3
+ # docs/recipes/deploy-all-in-one.md.
4
+
5
+ services:
6
+ agent-runner:
7
+ build: .
8
+ # Mount your project (it must contain agent-runner.toml at its root,
9
+ # matching [runtime] work_dir = "." in that file) at /work.
10
+ volumes:
11
+ - .:/work
12
+ working_dir: /work
13
+ environment:
14
+ # Value-less: pass through from the host shell. Add whatever your
15
+ # agent CLI needs the same way.
16
+ - ANTHROPIC_API_KEY
17
+ command: ["serve"]
18
+ restart: unless-stopped
@@ -29,6 +29,8 @@ move it to the doc whose charter it belongs to (see the decision procedure in
29
29
  | [long-running-agents.md](long-running-agents.md) | Context-rot essay: fresh eyes, long-lineage runs |
30
30
  | [recipes/aider.md](recipes/aider.md) | Per-CLI walkthrough — aider |
31
31
  | [recipes/codewhale.md](recipes/codewhale.md) | Per-CLI walkthrough — codewhale |
32
+ | [recipes/container-pi.md](recipes/container-pi.md) | Per-CLI walkthrough — pi (+ kimi/claude/podman deltas) run in a container via `exec_prefix` |
33
+ | [recipes/deploy-all-in-one.md](recipes/deploy-all-in-one.md) | Recipe — supervisor + agent CLI in one container |
32
34
  | [recipes/kimi.md](recipes/kimi.md) | Per-CLI walkthrough — Kimi Code CLI + Kimi K3 via the claude preset |
33
35
  | [recipes/pi.md](recipes/pi.md) | Per-CLI walkthrough — Pi Coding Agent + Kimi K3 via a Moonshot provider |
34
36
 
@@ -275,7 +275,7 @@ The transforms `migrate` applies (generated from the registry):
275
275
  - unknown [schedule] key(s) rejected in 0.2.12; delete them (allowed: ['pause_windows', 'run_windows', 'timezone'])
276
276
  - monitor.anomaly_repetitive_threshold > anomaly_repetitive_window: lower the threshold or raise the window so the detector can fire
277
277
  - [<table>] given as a scalar, not a table; give it real [table] content (no auto-fix possible)
278
- - unknown [agent] key(s) rejected in 0.2.13; delete them (allowed: ['command', 'env', 'name', 'prompt_arg_template', 'prompt_delivery'])
278
+ - unknown [agent] key(s) rejected in 0.2.13; delete them (allowed: ['command', 'env', 'exec_prefix', 'name', 'prompt_arg_template', 'prompt_delivery'])
279
279
  - unknown [runtime] key(s) rejected in 0.2.13; delete them (allowed: ['disable_pre_round_hooks', 'dry_run', 'fresh_eyes_every_n', 'grace_kill_ignore_patterns', 'log_dir', 'max_grace_after_result_s', 'max_rounds', 'narrative_file', 'restart_delay_s', 'round_log_retention', 'round_timeout_s', 'stop_file', 'substrate_fingerprint_paths', 'transient_error_action', 'work_dir'])
280
280
  - unknown [vcs] key(s) rejected in 0.2.13; delete them (allowed: ['dirty_action', 'stash_idempotency_s'])
281
281
  - unknown [monitor] key(s) rejected in 0.2.13; delete them (allowed: ['anomaly_repetitive_threshold', 'anomaly_repetitive_window', 'auth_fail_hint', 'auth_fail_patterns', 'auto_stop_on', 'host_health', 'remote_failure_tolerance_s', 'round_progress_interval_s', 'supervisor_stale_threshold_s'])
@@ -30,6 +30,7 @@ running with newly-set `dirty_action = "auto_commit"` is undefined).
30
30
  | `name` | `str \| None` | None |
31
31
  | `env` | `dict[str, str]` | {} |
32
32
  | `prompt_delivery` | `Literal['argv', 'stdin']` | 'argv' |
33
+ | `exec_prefix` | `list[str]` | [] |
33
34
 
34
35
  ### `[runtime]`
35
36
 
@@ -311,7 +312,7 @@ sub-tables reject unknown keys: `agent` and `runtime` as noted below, and
311
312
 
312
313
  | Sub-table | Overrides | Fields accepted |
313
314
  |---|---|---|
314
- | `[phases.<name>.agent]` | `[agent]` | any `[agent]` field; field-merged onto the base `[agent]`, then validated (so the `stdin` + `{prompt}` cross-check runs on the merged result) |
315
+ | `[phases.<name>.agent]` | `[agent]` | any `[agent]` field except `exec_prefix` (base-only, see below); field-merged onto the base `[agent]`, then validated (so the `stdin` + `{prompt}` cross-check runs on the merged result) |
315
316
  | `[phases.<name>.runtime]` | `[runtime]` | `round_timeout_s`, `disable_pre_round_hooks` only |
316
317
  | `[phases.<name>.schedule]` | `[schedule]` | any `[schedule]` field; **replaces** the global windows wholesale |
317
318
  | `[phases.<name>.prompt]` | `[prompt]` | `files` only |
@@ -343,6 +344,9 @@ the base config regardless of phase:
343
344
  `[monitor]`, and `[plugins]` — global only.
344
345
  - `[prompt]` fields other than `files` (`context_injection_mode`,
345
346
  `inject_context`, `concat_separator`, …).
347
+ - **`agent.exec_prefix`** — set once on the base `[agent]` table; it applies to
348
+ every phase. A `[phases.<name>.agent]` sub-table that sets its own
349
+ `exec_prefix` is rejected at load.
346
350
 
347
351
  ### `phase_policy` — `wait` vs `skip`
348
352
 
@@ -0,0 +1,179 @@
1
+ # Running pi inside a container
2
+
3
+ This is [pi.md](pi.md)'s agent, run inside a container you supply, via
4
+ `[agent] exec_prefix` (schema: [configuration.md](../configuration.md)).
5
+ `exec_prefix` is an opaque list spliced onto the front of `command` at spawn —
6
+ so the effective process is `docker run … my-pi-image pi --mode json`, and
7
+ agent-runner watches that `docker run` client as an ordinary process, exactly
8
+ as it would watch a bare `pi` process. The container (and the OS underneath
9
+ it) enforces the isolation; agent-runner does not sandbox the agent itself —
10
+ it runs the agent in a container you supply and watches from outside.
11
+
12
+ ## Prerequisites
13
+
14
+ - Docker (or Podman — see the rootless delta below) installed on the
15
+ supervisor host.
16
+ - Your user in the `docker` group — **not** `sudo` (see Footguns).
17
+ - An image you build yourself, e.g.:
18
+ ```dockerfile
19
+ FROM node:20-slim
20
+ RUN npm i -g --ignore-scripts @earendil-works/pi-coding-agent
21
+ # the template runs as -u 1000:1000 with HOME=/home/agent, so the image
22
+ # must provide that dir owned by the UID (or $HOME-relative writes fail):
23
+ RUN mkdir -p /home/agent && chown 1000:1000 /home/agent
24
+ ```
25
+ - A provider/model configured for pi — same as [pi.md](pi.md)'s Provider auth
26
+ section.
27
+ - A git repo as `work_dir`, bind-mounted into the container at `/work`.
28
+
29
+ ## Config
30
+
31
+ ```toml
32
+ [agent]
33
+ command = ["pi", "--mode", "json"]
34
+ exec_prefix = ["docker", "run", "--rm", "-i", "--init", "--stop-timeout", "5",
35
+ "-u", "1000:1000", "-e", "HOME=/home/agent",
36
+ "-v", "{work_dir}:/work:Z", "-w", "/work",
37
+ "-e", "ANTHROPIC_API_KEY", "my-pi-image"]
38
+ ```
39
+
40
+ `{work_dir}` is the one templated token — agent-runner substitutes it with
41
+ the run's absolute `work_dir` at spawn; every other token passes through
42
+ untouched. `command` stays `["pi", "--mode", "json"]`, so pi's identity
43
+ (throttle / crash-loop / usage-plugin keying, all keyed on `command[0]`) is
44
+ unaffected by running inside docker.
45
+
46
+ ### Why each flag
47
+
48
+ - **`--rm`** — remove the container on exit. Without it, containers
49
+ accumulate one per round.
50
+ - **`-i`** — keep stdin open. pi's `prompt_delivery = "stdin"` needs it;
51
+ without `-i`, docker drops stdin and the agent hangs until
52
+ `round_timeout_s` kills it. agent-runner checks this at startup and
53
+ refuses to start a stdin-delivery config whose `exec_prefix` is missing
54
+ `-i`.
55
+ - **`--init`** — run an init process as PID 1 inside the container, so
56
+ SIGTERM reaches pi and in-container zombies get reaped. Without it, a
57
+ shell-form entrypoint as PID 1 can ignore SIGTERM outright.
58
+ - **`--stop-timeout 5`** — bound `docker stop`'s grace period so teardown
59
+ after a stop stays short and predictable.
60
+ - **`-u 1000:1000`** — run as your host UID, not root. A root-owned file
61
+ written into the bind-mounted `work_dir` poisons agent-runner's host-side
62
+ dirty-tree stash/commit, and trips git's "dubious ownership" check
63
+ in-container. Use `id -u`/`id -g` if your UID isn't 1000.
64
+ - **`-e HOME=/home/agent`** — an arbitrary UID has no `/etc/passwd` entry, so
65
+ `~` expansion breaks inside the container; set `HOME` explicitly.
66
+ - **`-v {work_dir}:/work:Z`** — bind-mount the run's `work_dir`. `:Z`
67
+ relabels the mount for SELinux hosts (Fedora/RHEL); drop it elsewhere.
68
+ - **`-w /work`** — pi has no `--cwd` flag (see [pi.md](pi.md)), so the
69
+ container's working directory has to already be the mount.
70
+ - **`-e ANTHROPIC_API_KEY`** (value-less) — forwards the key from
71
+ agent-runner's own environment into the container. **Never write
72
+ `-e ANTHROPIC_API_KEY=sk-…`** — a valued `-e` puts the secret straight into
73
+ the `docker run` argv, which lands in `ps` output and `/proc/<pid>/cmdline`
74
+ for any local user to read. Add every other provider key the same,
75
+ value-less way.
76
+ - **`my-pi-image`** — the image you built above.
77
+
78
+ ## Auth
79
+
80
+ Prefer env vars, as above — there's no state to lose when `--rm` removes the
81
+ container each round. If your provider needs a credential *file* (e.g.
82
+ `pi /login`'s `~/.pi/agent/auth.json`), mount it **outside** `work_dir` —
83
+ mounting it inside would show up as tracked-file churn to agent-runner's
84
+ dirty-tree detector every round:
85
+
86
+ ```
87
+ -v /home/you/.pi-container-auth:/home/agent/.pi/agent:ro
88
+ ```
89
+
90
+ ## Footguns
91
+
92
+ These aren't enforced by agent-runner (`exec_prefix` is opaque) — get them
93
+ right yourself:
94
+
95
+ - **`sudo docker run` loses the clean stop.** agent-runner's container-orphan
96
+ defense only injects a `--cidfile` (and so can attempt a clean `docker
97
+ stop` on a stuck round) for the literal `docker run …` shape. A
98
+ `sudo`-wrapped or globally-flagged form is still *detected* — you get the
99
+ warning and event — but not covered by the clean stop. Put your user in
100
+ the `docker` group instead of prefixing `sudo`.
101
+ - **Missing `--rm`** accumulates one container per round, forever.
102
+ - **`--network none`** breaks the agent outright — it can't reach the model
103
+ API.
104
+ - **Missing `-u`** runs the container as root — see the root-owned-files
105
+ footgun above.
106
+ - **`-d`/`--detach`** — never use it. Detached `docker run` exits 0
107
+ immediately while the container is still starting, so agent-runner sees an
108
+ instant "successful" round with no agent work actually done.
109
+ - **`-t` without a real TTY** fails fast — docker allocates a pseudo-TTY and
110
+ errors when the supervisor's own stdin/stdout aren't one, which under a
111
+ service manager they usually aren't.
112
+ - **`--sig-proxy=false`** disables the relay of agent-runner's SIGTERM into
113
+ the container for cooperative shutdown. Leave it at its default (`true`).
114
+
115
+ ## Monitoring
116
+
117
+ `agent_process_count` in `peek`/metrics reads `0` for a containerized round —
118
+ expected, not a failure. The host-side probe counts processes named after the
119
+ agent (`pi`), but the host process is `docker`/`podman`; the agent runs in the
120
+ container's own PID namespace where the probe can't see it. Round liveness is
121
+ tracked by the container process agent-runner supervises directly, not this
122
+ count.
123
+
124
+ ## Not for tiny edge hosts
125
+
126
+ This recipe assumes a host with room for a container runtime on top of the
127
+ agent's own (non-trivial) memory footprint. It isn't a fit for a small edge
128
+ board — run pi bare there instead (see [pi.md](pi.md)).
129
+
130
+ ## Deltas
131
+
132
+ ### kimi — ENV-only, no mount
133
+
134
+ [kimi.md](kimi.md)'s CLI is fully configured over four env vars and needs no
135
+ file at all — prefer this shape whenever your CLI supports it, since there's
136
+ nothing to bind-mount:
137
+
138
+ ```toml
139
+ [agent]
140
+ command = ["kimi", "--output-format", "stream-json"]
141
+ exec_prefix = ["docker", "run", "--rm", "-i", "--init", "--stop-timeout", "5",
142
+ "-u", "1000:1000", "-e", "HOME=/home/agent",
143
+ "-v", "{work_dir}:/work:Z", "-w", "/work",
144
+ "-e", "KIMI_MODEL_NAME", "-e", "KIMI_MODEL_API_KEY",
145
+ "-e", "KIMI_MODEL_BASE_URL", "-e", "KIMI_MODEL_PROVIDER_TYPE",
146
+ "my-kimi-image"]
147
+ ```
148
+
149
+ ### claude — root rejection + config mount
150
+
151
+ Claude Code refuses `--dangerously-skip-permissions` when run as root, so
152
+ `-u` is mandatory here, not just recommended. If you authenticate via
153
+ `~/.claude` instead of an API key, mount it outside `work_dir` and point
154
+ `CLAUDE_CONFIG_DIR` at the mount:
155
+
156
+ ```toml
157
+ exec_prefix = ["docker", "run", "--rm", "-i", "--init", "--stop-timeout", "5",
158
+ "-u", "1000:1000", "-e", "HOME=/home/agent",
159
+ "-v", "{work_dir}:/work:Z", "-w", "/work",
160
+ "-v", "/home/you/.claude-container:/home/agent/.claude:ro",
161
+ "-e", "CLAUDE_CONFIG_DIR=/home/agent/.claude",
162
+ "my-claude-image"]
163
+ ```
164
+
165
+ ### rootless podman — `--userns=keep-id` instead of `-u`
166
+
167
+ Podman rootless already runs as your user; swap `-u 1000:1000` for
168
+ `--userns=keep-id`, which maps your host UID inside the container without
169
+ needing root at all:
170
+
171
+ ```toml
172
+ exec_prefix = ["podman", "run", "--rm", "-i", "--init", "--stop-timeout", "5",
173
+ "--userns=keep-id", "-e", "HOME=/home/agent",
174
+ "-v", "{work_dir}:/work:Z", "-w", "/work",
175
+ "-e", "ANTHROPIC_API_KEY", "my-pi-image"]
176
+ ```
177
+
178
+ See also: [docs/configuration.md](../configuration.md),
179
+ [SECURITY.md](../../SECURITY.md#containment).
@@ -0,0 +1,49 @@
1
+ # Running the whole unit in one container
2
+
3
+ This is a **recipe** — `deploy/Dockerfile` and `deploy/docker-compose.yml`
4
+ are templates to copy and adapt into your own project, not a published or
5
+ maintained image.
6
+
7
+ Where [container-pi.md](container-pi.md) boxes the *agent* and keeps the
8
+ supervisor on the host, this recipe does the opposite: the supervisor and
9
+ the agent CLI both run inside one container, and that container is the unit
10
+ you bound against the host (filesystem, network, resources). agent-runner
11
+ still supervises the agent CLI as an ordinary in-container subprocess — this
12
+ container doesn't add any isolation between the supervisor and the agent, it
13
+ only bounds the whole unit against the host it runs on.
14
+
15
+ ## When to use this vs `exec_prefix`
16
+
17
+ - **`exec_prefix`** ([container-pi.md](container-pi.md)) — box the agent
18
+ only; the supervisor keeps its normal host-level view (events, `peek`,
19
+ host PSI/memory signals). Prefer this by default.
20
+ - **This recipe** — you want one image that *is* the deployable unit (e.g.
21
+ shipping the whole thing to a host you don't otherwise manage). In
22
+ exchange, the supervisor's view of memory/PSI pressure is now the
23
+ container's own, not the host's.
24
+
25
+ ## Files
26
+
27
+ - `deploy/Dockerfile` — `python:3.12-slim` + `pip install cli-agent-runner` +
28
+ a commented-out slot for your agent CLI's install step.
29
+ - `deploy/docker-compose.yml` — builds the image, mounts your project (which
30
+ should contain `agent-runner.toml`) at `/work`, runs `serve`.
31
+
32
+ ## Use it
33
+
34
+ ```bash
35
+ cp deploy/Dockerfile deploy/docker-compose.yml your-project/
36
+ cd your-project
37
+ # edit Dockerfile: uncomment/add your agent CLI's install step
38
+ # agent-runner.toml already exists at the project root (agent-runner init)
39
+ docker compose up -d
40
+ docker compose logs -f
41
+ ```
42
+
43
+ ## Not for tiny edge hosts
44
+
45
+ Same caveat as [container-pi.md](container-pi.md): don't reach for this on a
46
+ small edge board. It's a fat-host/homelab/server recipe.
47
+
48
+ See also: [docs/quickstart.md](../quickstart.md),
49
+ [SECURITY.md](../../SECURITY.md#containment).