cli-agent-runner 0.2.7__tar.gz → 0.2.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/.vulture-whitelist.py +10 -0
  2. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/CHANGELOG.md +11 -0
  3. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/PKG-INFO +9 -4
  4. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/README.md +8 -3
  5. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/_docgen.py +66 -6
  6. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/_emit.py +10 -0
  7. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/_version.py +2 -2
  8. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/api.py +1 -0
  9. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/cli/__init__.py +2 -0
  10. cli_agent_runner-0.2.8/agent_runner/cli/migrate_cmd.py +51 -0
  11. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/cli/upgrade_cmd.py +56 -3
  12. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/config.py +3 -4
  13. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/events.py +1 -0
  14. cli_agent_runner-0.2.8/agent_runner/migrations.py +89 -0
  15. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/presets/codewhale.toml +1 -0
  16. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/architecture.md +1 -0
  17. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/commands.md +55 -25
  18. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/configuration.md +1 -1
  19. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/long-running-agents.md +1 -7
  20. cli_agent_runner-0.2.8/docs/migrations/0.2.8.md +79 -0
  21. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/recipes/aider.md +10 -0
  22. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/recipes/codewhale.md +12 -1
  23. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/runbook.md +9 -4
  24. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_architecture.py +3 -1
  25. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_doc_claims_match_ssot.py +14 -0
  26. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_doc_cli_claims.py +6 -2
  27. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_doc_operator_surface.py +14 -2
  28. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_entry_points_resolve.py +1 -3
  29. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_config.py +23 -1
  30. cli_agent_runner-0.2.8/tests/unit/test_emit_config_migrated.py +24 -0
  31. cli_agent_runner-0.2.8/tests/unit/test_migrate_cmd.py +85 -0
  32. cli_agent_runner-0.2.8/tests/unit/test_migrations.py +59 -0
  33. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_presets.py +24 -2
  34. cli_agent_runner-0.2.8/tests/unit/test_upgrade_migrate.py +91 -0
  35. cli_agent_runner-0.2.7/docs/migrations/0.1.16.md +0 -97
  36. cli_agent_runner-0.2.7/docs/migrations/0.1.17.md +0 -80
  37. cli_agent_runner-0.2.7/docs/migrations/0.1.19.md +0 -61
  38. cli_agent_runner-0.2.7/docs/migrations/0.1.20.md +0 -80
  39. cli_agent_runner-0.2.7/docs/migrations/0.1.21.md +0 -126
  40. cli_agent_runner-0.2.7/docs/migrations/0.1.22.md +0 -93
  41. cli_agent_runner-0.2.7/docs/migrations/0.1.23.md +0 -132
  42. cli_agent_runner-0.2.7/docs/migrations/0.1.24.md +0 -124
  43. cli_agent_runner-0.2.7/docs/migrations/0.1.25.md +0 -96
  44. cli_agent_runner-0.2.7/docs/migrations/0.1.26.md +0 -135
  45. cli_agent_runner-0.2.7/docs/migrations/0.1.27.md +0 -176
  46. cli_agent_runner-0.2.7/docs/migrations/0.1.29.md +0 -90
  47. cli_agent_runner-0.2.7/docs/migrations/0.1.30.md +0 -73
  48. cli_agent_runner-0.2.7/docs/migrations/0.1.31.md +0 -89
  49. cli_agent_runner-0.2.7/docs/migrations/0.1.32.md +0 -113
  50. cli_agent_runner-0.2.7/docs/migrations/0.1.33.md +0 -88
  51. cli_agent_runner-0.2.7/docs/migrations/0.1.34.md +0 -110
  52. cli_agent_runner-0.2.7/docs/migrations/0.1.35.md +0 -98
  53. cli_agent_runner-0.2.7/docs/migrations/0.1.36.md +0 -73
  54. cli_agent_runner-0.2.7/docs/migrations/0.1.37.md +0 -65
  55. cli_agent_runner-0.2.7/docs/migrations/0.1.38.md +0 -53
  56. cli_agent_runner-0.2.7/docs/migrations/0.1.39.md +0 -74
  57. cli_agent_runner-0.2.7/docs/migrations/0.1.40.md +0 -42
  58. cli_agent_runner-0.2.7/docs/migrations/0.1.42.md +0 -58
  59. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/.codecov.yml +0 -0
  60. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  61. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  62. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  63. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  64. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/.github/workflows/ci.yml +0 -0
  65. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/.github/workflows/release.yml +0 -0
  66. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/.gitignore +0 -0
  67. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/BACKLOG.md +0 -0
  68. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/CODE_OF_CONDUCT.md +0 -0
  69. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/CONTRIBUTING.md +0 -0
  70. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/LICENSE +0 -0
  71. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/README.zh.md +0 -0
  72. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/SECURITY.md +0 -0
  73. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/__init__.py +0 -0
  74. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/_redact.py +0 -0
  75. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/_registry.py +0 -0
  76. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/_substrate.py +0 -0
  77. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/_throttle.py +0 -0
  78. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/agent_runtime.py +0 -0
  79. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/api_types.py +0 -0
  80. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/builtin_plugins/__init__.py +0 -0
  81. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/builtin_plugins/_constants.py +0 -0
  82. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
  83. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/builtin_plugins/codewhale.py +0 -0
  84. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
  85. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/builtin_plugins/gemini.py +0 -0
  86. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/builtin_plugins/kimi.py +0 -0
  87. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/builtin_plugins/pi.py +0 -0
  88. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/cli/__main__.py +0 -0
  89. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/cli/common.py +0 -0
  90. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/cli/events_cmd.py +0 -0
  91. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/cli/init_cmd.py +0 -0
  92. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/cli/install_cmd.py +0 -0
  93. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/cli/monitor_cmd.py +0 -0
  94. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/cli/peek_cmd.py +0 -0
  95. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/cli/round_cmd.py +0 -0
  96. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/cli/serve_cmd.py +0 -0
  97. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/cli/service_cmd.py +0 -0
  98. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/context_store.py +0 -0
  99. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/defenses.py +0 -0
  100. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/hooks.py +0 -0
  101. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/http_progress.py +0 -0
  102. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/lifecycle.py +0 -0
  103. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/metrics.py +0 -0
  104. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/monitor.py +0 -0
  105. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/presets/__init__.py +0 -0
  106. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/presets/aider.toml +0 -0
  107. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/presets/claude.toml +0 -0
  108. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/presets/gemini.toml +0 -0
  109. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/presets/kimi.toml +0 -0
  110. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/presets/pi.toml +0 -0
  111. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/prompt_loader.py +0 -0
  112. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/remote_relay.py +0 -0
  113. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/round_log.py +0 -0
  114. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/round_view.py +0 -0
  115. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/runner.py +0 -0
  116. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/scaffold.py +0 -0
  117. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/schedule.py +0 -0
  118. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/service_unit.py +0 -0
  119. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/startup_check.py +0 -0
  120. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/agent_runner/vcs_state.py +0 -0
  121. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/build.sh +0 -0
  122. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/README.md +0 -0
  123. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/events.md +0 -0
  124. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/migrations/0.1.28.md +0 -0
  125. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/migrations/0.2.0.md +0 -0
  126. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/migrations/0.2.1.md +0 -0
  127. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/migrations/0.2.2.md +0 -0
  128. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/migrations/0.2.3.md +0 -0
  129. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/migrations/0.2.4.md +0 -0
  130. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/migrations/0.2.5.md +0 -0
  131. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/migrations/0.2.6.md +0 -0
  132. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/migrations/0.2.7.md +0 -0
  133. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/plugins.md +0 -0
  134. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/quickstart.md +0 -0
  135. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/recipes/kimi.md +0 -0
  136. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/recipes/pi.md +0 -0
  137. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/docs/thesis.md +0 -0
  138. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/pyproject.toml +0 -0
  139. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/__init__.py +0 -0
  140. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/_test_helpers.py +0 -0
  141. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/conftest.py +0 -0
  142. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/contract/__init__.py +0 -0
  143. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/contract/test_public_api_surface.py +0 -0
  144. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/e2e/__init__.py +0 -0
  145. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/e2e/conftest.py +0 -0
  146. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/e2e/test_e2e_graceful_stop.py +0 -0
  147. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/e2e/test_e2e_install_systemd.py +0 -0
  148. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/e2e/test_e2e_monitor_remote.py +0 -0
  149. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
  150. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
  151. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
  152. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
  153. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/generate_vulture_whitelist.py +0 -0
  154. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/__init__.py +0 -0
  155. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_bounded_run.py +0 -0
  156. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_context_enricher_namespacing.py +0 -0
  157. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_dirty_handler_seam.py +0 -0
  158. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_fresh_eyes_signal.py +0 -0
  159. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_grace_kill_emission.py +0 -0
  160. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_install_dry_run.py +0 -0
  161. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_monitor_seeded.py +0 -0
  162. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_plugin_detector_loaded.py +0 -0
  163. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_plugin_owned_paths.py +0 -0
  164. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_plugin_real_flow.py +0 -0
  165. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_prompt_delivery_stdin.py +0 -0
  166. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
  167. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_scaffold_presets.py +0 -0
  168. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_serve_loop.py +0 -0
  169. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_substrate_fingerprint.py +0 -0
  170. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/integration/test_transient_error_backoff.py +0 -0
  171. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/__init__.py +0 -0
  172. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/_event_scan.py +0 -0
  173. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_atomic_write_enforced.py +0 -0
  174. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
  175. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_catalogs.py +0 -0
  176. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_classification_ssot.py +0 -0
  177. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_config_error_consistency.py +0 -0
  178. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_config_value_set_ssot.py +0 -0
  179. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_doc_builtin_plugins.py +0 -0
  180. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_doc_transient_error_claims.py +0 -0
  181. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_docs_generated.py +0 -0
  182. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_docs_index_complete.py +0 -0
  183. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_event_kind_registry.py +0 -0
  184. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_event_kinds_ssot.py +0 -0
  185. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_events_doc_contract.py +0 -0
  186. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_hook_contract_docs.py +0 -0
  187. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_layer_2_loop_size.py +0 -0
  188. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_module_boundaries.py +0 -0
  189. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_module_sizes.py +0 -0
  190. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_no_ai_signatures.py +0 -0
  191. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
  192. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_peek_schema_version.py +0 -0
  193. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_plugins_config_stable.py +0 -0
  194. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
  195. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
  196. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_round_result_stable.py +0 -0
  197. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
  198. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
  199. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_upstream_schema_canary.py +0 -0
  200. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
  201. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/literate/__init__.py +0 -0
  202. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/literate/parser.py +0 -0
  203. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/literate/test_parser.py +0 -0
  204. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/literate/test_quickstart.py +0 -0
  205. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/__init__.py +0 -0
  206. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_agent_runtime.py +0 -0
  207. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_agent_runtime_grace.py +0 -0
  208. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_agent_runtime_progress.py +0 -0
  209. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_agent_runtime_signal_name.py +0 -0
  210. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_api_assemble_prompt.py +0 -0
  211. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_api_events_stream.py +0 -0
  212. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_api_install.py +0 -0
  213. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_api_observation.py +0 -0
  214. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_api_read_round_num.py +0 -0
  215. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_api_resolve_phase.py +0 -0
  216. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_api_service.py +0 -0
  217. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_api_types.py +0 -0
  218. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_auto_stop_gating.py +0 -0
  219. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_catalogs.py +0 -0
  220. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_claude_error_detector.py +0 -0
  221. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_cli.py +0 -0
  222. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_cli_common.py +0 -0
  223. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_cli_init_install.py +0 -0
  224. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_cli_monitor_http.py +0 -0
  225. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_cli_service_peek_monitor.py +0 -0
  226. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_cli_upgrade.py +0 -0
  227. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_codewhale_plugin.py +0 -0
  228. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_config_fresh_eyes.py +0 -0
  229. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_config_max_rounds.py +0 -0
  230. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_config_prompt_delivery.py +0 -0
  231. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_config_schedule.py +0 -0
  232. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_config_stop_file.py +0 -0
  233. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
  234. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_config_transient_error_action.py +0 -0
  235. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_context_store.py +0 -0
  236. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_default_dirty_handler.py +0 -0
  237. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_defenses.py +0 -0
  238. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_detector_protocol.py +0 -0
  239. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_dirty_handlers.py +0 -0
  240. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_docgen.py +0 -0
  241. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_emit_schedule.py +0 -0
  242. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_events.py +0 -0
  243. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_events_cmd.py +0 -0
  244. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_events_cmd_tail.py +0 -0
  245. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_exit_cause.py +0 -0
  246. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_fresh_eyes_trigger.py +0 -0
  247. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_gemini_plugin.py +0 -0
  248. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_hook_failure_isolation.py +0 -0
  249. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_hooks.py +0 -0
  250. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_http_progress.py +0 -0
  251. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_init_entry_points.py +0 -0
  252. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_kimi_plugin.py +0 -0
  253. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_lifecycle.py +0 -0
  254. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_metrics.py +0 -0
  255. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_monitor_assembly.py +0 -0
  256. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
  257. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
  258. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
  259. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_monitor_detectors.py +0 -0
  260. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_monitor_remote.py +0 -0
  261. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_monitor_schedule_pause.py +0 -0
  262. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_network_blip_signal_guard.py +0 -0
  263. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_peek_argparse.py +0 -0
  264. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_peek_schedule.py +0 -0
  265. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_peek_select.py +0 -0
  266. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_pi_plugin.py +0 -0
  267. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_plugin_constants.py +0 -0
  268. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_prompt_loader.py +0 -0
  269. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_redact.py +0 -0
  270. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_round_log_helpers.py +0 -0
  271. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_round_view.py +0 -0
  272. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_runner.py +0 -0
  273. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_runner_throttle.py +0 -0
  274. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_scaffold.py +0 -0
  275. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_schedule.py +0 -0
  276. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_serve_cmd_bounded.py +0 -0
  277. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_serve_config_broken.py +0 -0
  278. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_serve_crash_loop.py +0 -0
  279. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_serve_round_log.py +0 -0
  280. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_serve_schedule_gate.py +0 -0
  281. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_serve_sentinel.py +0 -0
  282. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_serve_startup_hooks.py +0 -0
  283. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_service_unit.py +0 -0
  284. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_startup_check.py +0 -0
  285. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_substrate.py +0 -0
  286. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_throttle_plugin_classification.py +0 -0
  287. {cli_agent_runner-0.2.7 → cli_agent_runner-0.2.8}/tests/unit/test_vcs_state.py +0 -0
@@ -232,6 +232,16 @@ stash_idempotency_s
232
232
  # agent_runner.lifecycle.PIDFile
233
233
  path
234
234
 
235
+ # agent_runner.migrations.Migration
236
+ detect
237
+ apply
238
+ describe
239
+
240
+ # agent_runner.migrations.MigrationResult
241
+ new_text
242
+ applied
243
+ manual
244
+
235
245
  # agent_runner.monitor.LocalSource
236
246
  log_dir
237
247
 
@@ -5,6 +5,17 @@ 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.8] - 2026-08-25
9
+
10
+ ### Added
11
+ - `agent-runner migrate [--dry-run]` rewrites removed/renamed fields in `agent-runner.toml` to their current form, keeping a `.bak`; reports fields needing a manual change.
12
+ - `upgrade` auto-migrates the config before the package/service step and emits a `config_migrated` event; a config needing a manual change aborts the run before it stops the service. `--no-migrate` skips it.
13
+
14
+ ### Changed
15
+ - Removed-field `ConfigError` messages now point to `agent-runner migrate`.
16
+
17
+ See `docs/migrations/0.2.8.md`.
18
+
8
19
  ## [0.2.7] - 2026-08-25
9
20
 
10
21
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cli-agent-runner
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Restart-on-exit supervisor for autonomous CLI agents
5
5
  Project-URL: Homepage, https://github.com/wan9yu/cli-agent-runner
6
6
  Project-URL: Documentation, https://github.com/wan9yu/cli-agent-runner#readme
@@ -51,7 +51,7 @@ full disks, runaway memory.
51
51
  ┌──────────────────────────────────────────┐
52
52
  │ Layer 3: The Witness (monitor) │ 11 detectors + auto-stop
53
53
  ├──────────────────────────────────────────┤
54
- │ Layer 2: The Loop (serve, ~120 LOC) │ signal-trapping restart loop
54
+ │ Layer 2: The Loop (serve) │ signal-trapping restart loop
55
55
  ├──────────────────────────────────────────┤
56
56
  │ Layer 1: The Round (round) │ one agent invocation
57
57
  └──────────────────────────────────────────┘
@@ -80,14 +80,15 @@ agent-runner monitor # live anomaly detection
80
80
 
81
81
  Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
82
82
 
83
- ## 15 verbs
83
+ ## 16 verbs
84
84
 
85
85
  | Lifecycle | Observation |
86
86
  |---|---|
87
87
  | `init` / `install` / `uninstall` | `peek` — state snapshot |
88
88
  | `start` / `stop` / `kill` | `watch` — peek in a refresh loop |
89
89
  | `restart` / `status` | `monitor` — 11 detectors, alerts, auto-stop |
90
- | `round` / `serve` / `upgrade` | `events` — query / stream events.jsonl |
90
+ | `round` / `serve` | `events` — query / stream events.jsonl |
91
+ | `upgrade` / `migrate` | |
91
92
 
92
93
  Verb reference: [`docs/commands.md`](docs/commands.md).
93
94
 
@@ -106,6 +107,10 @@ guards it. Highlights:
106
107
 
107
108
  Full list and rationale: [`docs/architecture.md`](docs/architecture.md).
108
109
 
110
+ Optionally pause the loop during off-hours: `[schedule]` run/pause windows gate
111
+ `serve` (override with `serve --ignore-schedule`) — see the `[schedule]` section
112
+ of [`docs/configuration.md`](docs/configuration.md).
113
+
109
114
  ## Monitor: 11 detectors
110
115
 
111
116
  Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
@@ -15,7 +15,7 @@ full disks, runaway memory.
15
15
  ┌──────────────────────────────────────────┐
16
16
  │ Layer 3: The Witness (monitor) │ 11 detectors + auto-stop
17
17
  ├──────────────────────────────────────────┤
18
- │ Layer 2: The Loop (serve, ~120 LOC) │ signal-trapping restart loop
18
+ │ Layer 2: The Loop (serve) │ signal-trapping restart loop
19
19
  ├──────────────────────────────────────────┤
20
20
  │ Layer 1: The Round (round) │ one agent invocation
21
21
  └──────────────────────────────────────────┘
@@ -44,14 +44,15 @@ agent-runner monitor # live anomaly detection
44
44
 
45
45
  Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
46
46
 
47
- ## 15 verbs
47
+ ## 16 verbs
48
48
 
49
49
  | Lifecycle | Observation |
50
50
  |---|---|
51
51
  | `init` / `install` / `uninstall` | `peek` — state snapshot |
52
52
  | `start` / `stop` / `kill` | `watch` — peek in a refresh loop |
53
53
  | `restart` / `status` | `monitor` — 11 detectors, alerts, auto-stop |
54
- | `round` / `serve` / `upgrade` | `events` — query / stream events.jsonl |
54
+ | `round` / `serve` | `events` — query / stream events.jsonl |
55
+ | `upgrade` / `migrate` | |
55
56
 
56
57
  Verb reference: [`docs/commands.md`](docs/commands.md).
57
58
 
@@ -70,6 +71,10 @@ guards it. Highlights:
70
71
 
71
72
  Full list and rationale: [`docs/architecture.md`](docs/architecture.md).
72
73
 
74
+ Optionally pause the loop during off-hours: `[schedule]` run/pause windows gate
75
+ `serve` (override with `serve --ignore-schedule`) — see the `[schedule]` section
76
+ of [`docs/configuration.md`](docs/configuration.md).
77
+
73
78
  ## Monitor: 11 detectors
74
79
 
75
80
  Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
@@ -7,12 +7,16 @@ renderer registry so the substitution rule is testable in isolation.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import argparse
10
11
  import dataclasses
12
+ import functools
13
+ import importlib.resources
11
14
  import re
12
15
  import typing
13
16
  from collections.abc import Callable
14
17
  from pathlib import Path
15
18
 
19
+ from agent_runner.cli.init_cmd import _preset_names
16
20
  from agent_runner.config import (
17
21
  AgentConfig,
18
22
  Config,
@@ -152,18 +156,53 @@ def render_event_kinds_list() -> str:
152
156
  return "\n".join(f"- `{k}`" for k in sorted(KNOWN_EVENT_KINDS))
153
157
 
154
158
 
155
- def render_verb_table() -> str:
156
- """Walk the argparse subparsers and render a verb table."""
159
+ def _subparser_action() -> argparse.Action:
160
+ """The single ``_SubParsersAction`` on the top-level parser."""
157
161
  from agent_runner.cli import _build_parser
158
162
 
159
163
  parser = _build_parser()
160
- # Find the sub-parsers action there's exactly one.
161
- sub_action = next(a for a in parser._actions if a.__class__.__name__ == "_SubParsersAction")
164
+ return next(a for a in parser._actions if a.__class__.__name__ == "_SubParsersAction")
165
+
166
+
167
+ def _subparser_choices() -> dict[str, argparse.ArgumentParser]:
168
+ """Map ``verb -> subparser`` for the CLI, the SSOT both verb renderers use."""
169
+ return _subparser_action().choices
170
+
171
+
172
+ def _render_verb_flags(verb: str) -> str:
173
+ """Bullet list of a verb's own option flags, from its argparse subparser.
174
+
175
+ Parent-inherited ``--config`` / ``--json`` carry ``help=SUPPRESS`` on the
176
+ shared parent and are filtered here, so only verb-specific flags render.
177
+ """
178
+ choices = _subparser_choices()
179
+ lines: list[str] = []
180
+ for act in choices[verb]._actions:
181
+ if not act.option_strings: # positionals
182
+ continue
183
+ if isinstance(act, argparse._HelpAction): # -h/--help
184
+ continue
185
+ if act.help is argparse.SUPPRESS: # parent --config/--json
186
+ continue
187
+ name = max(act.option_strings, key=len)
188
+ if act.choices:
189
+ val = " {" + ",".join(map(str, act.choices)) + "}"
190
+ elif act.metavar:
191
+ val = f" {act.metavar}"
192
+ else:
193
+ val = ""
194
+ lines.append(f"- `{name}{val}` — {act.help}")
195
+ return "\n".join(lines)
196
+
197
+
198
+ def render_verb_table() -> str:
199
+ """Walk the argparse subparsers and render a verb table."""
200
+ sub_action = _subparser_action()
162
201
  rows = [
163
202
  "| Verb | Description |",
164
203
  "|---|---|",
165
204
  ]
166
- for verb, _sp in sub_action.choices.items():
205
+ for verb in sub_action.choices:
167
206
  # Argparse stores help text via `sub_action._choices_actions` indexed by add order.
168
207
  help_text = (
169
208
  next(
@@ -176,6 +215,18 @@ def render_verb_table() -> str:
176
215
  return "\n".join(rows)
177
216
 
178
217
 
218
+ def _render_preset(name: str) -> str:
219
+ """Fenced TOML block quoting a shipped ``agent_runner/presets/<name>.toml``.
220
+
221
+ The preset file is the SSOT; the recipe doc block is a verbatim mirror so a
222
+ flag or auth-hint edit in the preset can never drift from its recipe.
223
+ """
224
+ body = (importlib.resources.files("agent_runner.presets") / f"{name}.toml").read_text(
225
+ encoding="utf-8"
226
+ )
227
+ return "```toml\n" + body.rstrip() + "\n```"
228
+
229
+
179
230
  RENDERERS: dict[str, Callable[[], str]] = {
180
231
  "defenses-table": render_defenses_table,
181
232
  "detector-list": render_detector_list,
@@ -183,6 +234,13 @@ RENDERERS: dict[str, Callable[[], str]] = {
183
234
  "config-schema": render_config_schema_table,
184
235
  "verb-table": render_verb_table,
185
236
  }
237
+ RENDERERS.update(
238
+ {
239
+ f"flags-{v}": functools.partial(_render_verb_flags, v)
240
+ for v in ("init", "monitor", "serve", "migrate")
241
+ }
242
+ )
243
+ RENDERERS.update({f"preset-{n}": functools.partial(_render_preset, n) for n in _preset_names()})
186
244
 
187
245
  _GEN_OPEN = re.compile(r"<!-- gen:([a-z0-9-]+) -->")
188
246
 
@@ -196,7 +254,9 @@ def render(docs_dir: Path, *, write: bool = True) -> dict[Path, str]:
196
254
  Raises ``ValueError`` when a marker references an unknown renderer name.
197
255
  """
198
256
  out: dict[Path, str] = {}
199
- for md in sorted(docs_dir.glob("*.md")):
257
+ # Top-level docs plus recipes/. NOT rglob: migrations/ is frozen history and
258
+ # internal/ is gitignored — neither should be regenerated.
259
+ for md in sorted(docs_dir.glob("*.md")) + sorted((docs_dir / "recipes").glob("*.md")):
200
260
  text = md.read_text(encoding="utf-8")
201
261
  for match in _GEN_OPEN.finditer(text):
202
262
  name = match.group(1)
@@ -17,6 +17,7 @@ __all__ = [
17
17
  "emit_agent_auth_error_detected",
18
18
  "emit_agent_usage_recorded",
19
19
  "emit_anomaly_repetitive_tool",
20
+ "emit_config_migrated",
20
21
  "emit_fresh_eyes_round_triggered",
21
22
  "emit_max_rounds_reached",
22
23
  "emit_rate_limit_stop",
@@ -72,6 +73,15 @@ def emit_crash_loop(log_dir: Path, *, consecutive: int, exit_code: int, log_path
72
73
  emit(log_dir, CRASH_LOOP, consecutive=consecutive, exit_code=exit_code, reason=reason)
73
74
 
74
75
 
76
+ def emit_config_migrated(
77
+ log_dir: Path, *, applied: list[str], manual: list[str], path: str
78
+ ) -> None:
79
+ """Emit config_migrated when `migrate`/`upgrade` rewrites the config."""
80
+ from agent_runner.events import CONFIG_MIGRATED, emit
81
+
82
+ emit(log_dir, CONFIG_MIGRATED, applied=applied, manual=manual, path=path)
83
+
84
+
75
85
  def emit_stop_file_detected(
76
86
  log_dir: Path, *, stop_file: Path, content: str, rounds_completed: int
77
87
  ) -> None:
@@ -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.7'
22
- __version_tuple__ = version_tuple = (0, 2, 7)
21
+ __version__ = version = '0.2.8'
22
+ __version_tuple__ = version_tuple = (0, 2, 8)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -737,6 +737,7 @@ from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-ex
737
737
  emit_agent_usage_recorded,
738
738
  emit_anomaly_repetitive_tool,
739
739
  emit_config_broken,
740
+ emit_config_migrated,
740
741
  emit_crash_loop,
741
742
  emit_fresh_eyes_round_triggered,
742
743
  emit_max_rounds_reached,
@@ -20,6 +20,7 @@ from agent_runner.cli import (
20
20
  events_cmd,
21
21
  init_cmd,
22
22
  install_cmd,
23
+ migrate_cmd,
23
24
  monitor_cmd,
24
25
  peek_cmd,
25
26
  round_cmd,
@@ -69,6 +70,7 @@ def _build_parser() -> argparse.ArgumentParser:
69
70
  serve_cmd.add_parser(sub, parent)
70
71
  round_cmd.add_parser(sub, parent)
71
72
  upgrade_cmd.add_parser(sub, parent)
73
+ migrate_cmd.add_parser(sub, parent)
72
74
 
73
75
  return parser
74
76
 
@@ -0,0 +1,51 @@
1
+ """agent-runner migrate — rewrite removed/renamed config fields to current form."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import tomllib
6
+ from pathlib import Path
7
+
8
+ from agent_runner import migrations
9
+ from agent_runner.cli.common import fail # `fail(msg, *, code=1)` prints to stderr, returns code
10
+
11
+
12
+ def add_parser(sub, parent) -> None:
13
+ p = sub.add_parser(
14
+ "migrate",
15
+ parents=[parent],
16
+ help="Rewrite removed/renamed fields in agent-runner.toml to current form",
17
+ )
18
+ p.add_argument("--dry-run", action="store_true", help="Show changes without writing the file")
19
+ p.set_defaults(func=cmd)
20
+
21
+
22
+ def cmd(args) -> int:
23
+ cfg_path = Path(getattr(args, "config", None) or "agent-runner.toml")
24
+ try:
25
+ text = cfg_path.read_text(encoding="utf-8")
26
+ except OSError as e:
27
+ return fail(f"cannot read {cfg_path}: {e}", code=2)
28
+ try:
29
+ parsed = tomllib.loads(text)
30
+ except tomllib.TOMLDecodeError as e:
31
+ return fail(f"{cfg_path} is not valid TOML: {e}", code=2)
32
+
33
+ result = migrations.run_migrations(text, parsed)
34
+ if not result.applied and not result.manual:
35
+ print(f"{cfg_path}: nothing to migrate")
36
+ return 0
37
+
38
+ for line in result.applied:
39
+ print(f" rewrite: {line}")
40
+ for line in result.manual:
41
+ print(f" MANUAL: {line}")
42
+
43
+ if args.dry_run:
44
+ print("(dry-run — no changes written)")
45
+ return 1 if result.manual else 0
46
+
47
+ if result.applied:
48
+ cfg_path.with_suffix(cfg_path.suffix + ".bak").write_text(text, encoding="utf-8")
49
+ cfg_path.write_text(result.new_text, encoding="utf-8")
50
+ print(f"wrote {cfg_path} (backup: {cfg_path.name}.bak)")
51
+ return 1 if result.manual else 0
@@ -16,13 +16,14 @@ import re
16
16
  import subprocess
17
17
  import sys
18
18
  import time
19
+ import tomllib
19
20
  from pathlib import Path
20
21
 
21
22
  import agent_runner
22
- from agent_runner import __version__, api, events
23
+ from agent_runner import __version__, api, events, migrations
23
24
  from agent_runner.api_types import ServiceMode
24
25
  from agent_runner.cli.common import cfg_from_args, fail, info
25
- from agent_runner.config import Config
26
+ from agent_runner.config import Config, ConfigError
26
27
 
27
28
 
28
29
  def add_parser(sub, parent) -> None:
@@ -48,11 +49,28 @@ def add_parser(sub, parent) -> None:
48
49
  help="Upgrade the package + smoke only; do not stop/start the service "
49
50
  "(you restart it yourself).",
50
51
  )
52
+ p.add_argument(
53
+ "--no-migrate",
54
+ action="store_true",
55
+ help="Skip automatic config migration during upgrade",
56
+ )
51
57
  p.set_defaults(func=cmd)
52
58
 
53
59
 
54
60
  def cmd(args) -> int:
61
+ cfg_path = Path(args.config)
62
+ applied, manual = _migrate_config_file(cfg_path, no_migrate=getattr(args, "no_migrate", False))
63
+ if manual:
64
+ return fail(
65
+ "config needs manual migration before upgrade:\n "
66
+ + "\n ".join(manual)
67
+ + "\nfix it (or run `agent-runner migrate`) then retry"
68
+ ) # aborts BEFORE any stop/pip — fail fast
55
69
  cfg = _try_load_cfg(args)
70
+ if applied and cfg is not None:
71
+ api.emit_config_migrated(
72
+ cfg.runtime.log_dir, applied=applied, manual=[], path=str(cfg_path)
73
+ )
56
74
  return _run_upgrade(
57
75
  cfg,
58
76
  target=args.target,
@@ -61,12 +79,47 @@ def cmd(args) -> int:
61
79
  )
62
80
 
63
81
 
82
+ def _migrate_config_file(cfg_path: Path, *, no_migrate: bool) -> tuple[list[str], list[str]]:
83
+ """Auto-migrate the raw config BEFORE load_config (which rejects old keys).
84
+
85
+ Only mutates when the migration is COMPLETE. If any transform needs a manual
86
+ fix, the caller aborts the upgrade — so we write NOTHING (no `.bak`, no
87
+ rewrite) and return ``([], manual)`` report-only, leaving the file untouched
88
+ for the user to run `agent-runner migrate` and re-upgrade. Only when there is
89
+ no manual remainder do we write the `.bak` + rewrite and return the applied
90
+ changes."""
91
+ if no_migrate or not cfg_path.exists():
92
+ return [], []
93
+ text = cfg_path.read_text(encoding="utf-8")
94
+ try:
95
+ parsed = tomllib.loads(text)
96
+ except tomllib.TOMLDecodeError:
97
+ return [], [] # broken TOML — let the normal load path report it
98
+ result = migrations.run_migrations(text, parsed)
99
+ if result.manual:
100
+ return [], result.manual # doomed upgrade → don't mutate the config
101
+ if result.applied:
102
+ cfg_path.with_suffix(cfg_path.suffix + ".bak").write_text(text, encoding="utf-8")
103
+ cfg_path.write_text(result.new_text, encoding="utf-8")
104
+ return result.applied, []
105
+ return [], []
106
+
107
+
64
108
  def _try_load_cfg(args) -> Config | None:
65
- """Load the project config if present; None when absent (package-only)."""
109
+ """Load the project config if present; None when absent (package-only).
110
+
111
+ Also degrades to None on a ConfigError (a still-un-migratable legacy config)
112
+ or a broken-TOML file: the package-only path is a safe fallback, and crashing
113
+ `upgrade` with a traceback over a removed key or a syntax error would be worse
114
+ than upgrading without a restart.
115
+ """
66
116
  try:
67
117
  return cfg_from_args(args)
68
118
  except FileNotFoundError:
69
119
  return None
120
+ except (ConfigError, tomllib.TOMLDecodeError):
121
+ info("config did not load; proceeding with a package-only upgrade (no restart)")
122
+ return None
70
123
 
71
124
 
72
125
  def _pip_env_flags() -> list[str]:
@@ -444,14 +444,14 @@ def load_config(toml_path: Path) -> Config:
444
444
  if "round_timeout_per_phase" in runtime_d:
445
445
  raise ConfigError(
446
446
  "runtime.round_timeout_per_phase removed in 0.1.16; "
447
- "use [phases.<name>] round_timeout_s = X see docs/migrations/0.1.16.md"
447
+ "use [phases.<name>] round_timeout_s = X. Run `agent-runner migrate`."
448
448
  )
449
449
 
450
450
  if runtime_d.get("rate_limit_action") is not None:
451
451
  raise ConfigError(
452
452
  "runtime.rate_limit_action was removed in 0.1.29; use "
453
453
  "runtime.transient_error_action (same allowed values: "
454
- "back_off / skip / stop). See docs/migrations/0.1.29.md."
454
+ "back_off / skip / stop). Run `agent-runner migrate`."
455
455
  )
456
456
 
457
457
  transient_error_action_raw = runtime_d.get("transient_error_action")
@@ -544,8 +544,7 @@ def load_config(toml_path: Path) -> Config:
544
544
  vcs_d = raw.get("vcs", {})
545
545
  if "orphan_action" in vcs_d:
546
546
  raise ConfigError(
547
- "vcs.orphan_action removed in 0.1.18; use vcs.dirty_action "
548
- "see docs/migrations/0.1.17.md"
547
+ "vcs.orphan_action removed in 0.1.18; use vcs.dirty_action. Run `agent-runner migrate`."
549
548
  )
550
549
  dirty_action = str(vcs_d.get("dirty_action", "stash"))
551
550
  if dirty_action not in _VALID_DIRTY_ACTIONS:
@@ -34,6 +34,7 @@ AGENT_NETWORK_BLIP = "agent_network_blip"
34
34
  AGENT_SPAWN = "agent_spawn"
35
35
  AGENT_USAGE_RECORDED = "agent_usage_recorded"
36
36
  CONFIG_BROKEN = "config_broken"
37
+ CONFIG_MIGRATED = "config_migrated"
37
38
  CRASH_LOOP = "crash_loop"
38
39
  DIRTY_AUTO_COMMITTED = "dirty_auto_committed"
39
40
  DIRTY_COMMIT_FAILED = "dirty_commit_failed"
@@ -0,0 +1,89 @@
1
+ """Executable config migrations. A registry of transforms; each detects a
2
+ removed/renamed key in the parsed TOML and either renames it on the raw file
3
+ text (auto) or reports a manual instruction. Rewriting is targeted regex on the
4
+ raw text so comments and formatting are preserved — no TOML writer dependency."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import re
9
+ import tomllib
10
+ from collections.abc import Callable
11
+ from dataclasses import dataclass
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class Migration:
16
+ detect: Callable[[dict], bool] # old key present in the parsed TOML?
17
+ apply: Callable[[str], str] | None # raw-text rewrite; None = manual-only
18
+ describe: str # report line (auto) or instruction (manual)
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class MigrationResult:
23
+ new_text: str
24
+ applied: list[str]
25
+ manual: list[str]
26
+
27
+
28
+ def _rename_key(old: str, new: str) -> Callable[[str], str]:
29
+ """Rename a bare TOML assignment `old = ...` to `new = ...`, preserving indent,
30
+ spacing, value, and inline comment. Anchored to line-start assignments, so a
31
+ commented-out `# old = ...` or `old` inside a value/string is never matched."""
32
+ pat = re.compile(rf"(?m)^(?P<indent>[ \t]*){re.escape(old)}(?P<sp>[ \t]*=)")
33
+ return lambda text: pat.sub(rf"\g<indent>{new}\g<sp>", text)
34
+
35
+
36
+ MIGRATIONS: list[Migration] = [
37
+ Migration(
38
+ detect=lambda p: "rate_limit_action" in p.get("runtime", {}),
39
+ apply=_rename_key("rate_limit_action", "transient_error_action"),
40
+ describe="runtime.rate_limit_action → runtime.transient_error_action",
41
+ ),
42
+ Migration(
43
+ detect=lambda p: "orphan_action" in p.get("vcs", {}),
44
+ apply=_rename_key("orphan_action", "dirty_action"),
45
+ describe="vcs.orphan_action → vcs.dirty_action",
46
+ ),
47
+ Migration(
48
+ detect=lambda p: "round_timeout_per_phase" in p.get("runtime", {}),
49
+ apply=None,
50
+ describe=(
51
+ "runtime.round_timeout_per_phase (removed 0.1.16) must be moved "
52
+ "manually to [phases.<name>] round_timeout_s"
53
+ ),
54
+ ),
55
+ ]
56
+
57
+
58
+ def run_migrations(text: str, parsed: dict) -> MigrationResult:
59
+ new_text = text
60
+ applied: list[str] = []
61
+ manual: list[str] = []
62
+ for m in MIGRATIONS:
63
+ if not m.detect(parsed):
64
+ continue
65
+ if m.apply is None:
66
+ manual.append(m.describe)
67
+ continue
68
+ rewritten = m.apply(new_text)
69
+ if rewritten == new_text:
70
+ # Detected but the line-anchored rename matched nothing (e.g. a
71
+ # dotted-key `runtime.rate_limit_action = ...` at top level) —
72
+ # route to manual so we never report "applied" on an unchanged file.
73
+ manual.append(m.describe)
74
+ continue
75
+ try:
76
+ tomllib.loads(rewritten)
77
+ except tomllib.TOMLDecodeError:
78
+ # The rename would collide with a target key that is already present
79
+ # (e.g. both `orphan_action` and `dirty_action` set), producing a
80
+ # duplicate assignment — invalid TOML. Never adopt it; hand the user
81
+ # a manual instruction instead so `migrate`/`upgrade` don't corrupt
82
+ # the file and then traceback on the next load.
83
+ manual.append(
84
+ m.describe + " (target key already present — remove the deprecated key manually)"
85
+ )
86
+ continue
87
+ new_text = rewritten
88
+ applied.append(m.describe)
89
+ return MigrationResult(new_text=new_text, applied=applied, manual=manual)
@@ -11,6 +11,7 @@
11
11
  command = ["codewhale", "exec", "--auto", "--output-format", "stream-json"]
12
12
  prompt_arg_template = ["{prompt}"]
13
13
  name = "codewhale"
14
+ # [agent.env] omitted — DeepSeek key is ambient (env or codewhale keyring).
14
15
 
15
16
  [runtime]
16
17
  work_dir = "."
@@ -199,6 +199,7 @@ See `docs/plugins.md` for the `DirtyHandler` protocol and override recipe.
199
199
  - `agent_usage_recorded`
200
200
  - `anomaly_repetitive_tool`
201
201
  - `config_broken`
202
+ - `config_migrated`
202
203
  - `crash_loop`
203
204
  - `dirty_auto_committed`
204
205
  - `dirty_commit_failed`