cli-agent-runner 0.2.2__tar.gz → 0.2.4__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.
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/CHANGELOG.md +48 -15
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/PKG-INFO +11 -11
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/README.md +10 -10
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/_emit.py +37 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/agent_runtime.py +39 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/api.py +21 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/api_types.py +9 -0
- cli_agent_runner-0.2.4/agent_runner/builtin_plugins/_constants.py +125 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/builtin_plugins/claude_rate_limit.py +11 -27
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/builtin_plugins/codewhale.py +16 -36
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/builtin_plugins/gemini.py +6 -28
- cli_agent_runner-0.2.4/agent_runner/builtin_plugins/kimi.py +93 -0
- cli_agent_runner-0.2.4/agent_runner/builtin_plugins/pi.py +279 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/cli/monitor_cmd.py +5 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/config.py +7 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/events.py +1 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/hooks.py +10 -6
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/monitor.py +82 -11
- cli_agent_runner-0.2.4/agent_runner/presets/kimi.toml +38 -0
- cli_agent_runner-0.2.4/agent_runner/presets/pi.toml +41 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/round_log.py +30 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/runner.py +7 -2
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/service_unit.py +7 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/startup_check.py +19 -4
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/README.md +4 -2
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/architecture.md +10 -6
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/commands.md +8 -7
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/configuration.md +8 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/long-running-agents.md +6 -4
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.20.md +10 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.23.md +1 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.25.md +1 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.29.md +1 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.30.md +1 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.35.md +1 -1
- cli_agent_runner-0.2.4/docs/migrations/0.2.3.md +44 -0
- cli_agent_runner-0.2.4/docs/migrations/0.2.4.md +66 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/plugins.md +81 -8
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/quickstart.md +1 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/recipes/aider.md +5 -3
- cli_agent_runner-0.2.4/docs/recipes/kimi.md +119 -0
- cli_agent_runner-0.2.4/docs/recipes/pi.md +113 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/runbook.md +63 -22
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/thesis.md +3 -2
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/pyproject.toml +2 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/_test_helpers.py +33 -5
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_prompt_delivery_stdin.py +4 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_run_one_round_with_fake_agent.py +2 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_scaffold_presets.py +3 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_doc_claims_match_ssot.py +6 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_agent_runtime.py +47 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_agent_runtime_grace.py +6 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_agent_runtime_progress.py +3 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_api_observation.py +47 -6
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_claude_error_detector.py +52 -21
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_cli_service_peek_monitor.py +27 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_codewhale_plugin.py +14 -12
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_config.py +59 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_events.py +39 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_gemini_plugin.py +12 -12
- cli_agent_runner-0.2.4/tests/unit/test_kimi_plugin.py +147 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_monitor_detectors.py +57 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_monitor_remote.py +9 -0
- cli_agent_runner-0.2.4/tests/unit/test_pi_plugin.py +449 -0
- cli_agent_runner-0.2.4/tests/unit/test_plugin_constants.py +61 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_presets.py +12 -1
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_round_log_helpers.py +60 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_service_unit.py +9 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_startup_check.py +29 -0
- cli_agent_runner-0.2.2/agent_runner/builtin_plugins/_constants.py +0 -56
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/.codecov.yml +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/.gitignore +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/.vulture-whitelist.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/BACKLOG.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/LICENSE +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/README.zh.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/SECURITY.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/cli/serve_cmd.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/build.sh +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/events.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.16.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.17.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.19.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.21.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.22.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.24.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.26.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.27.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.28.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.31.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.32.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.33.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.34.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.36.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.37.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.38.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.39.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.40.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.1.42.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.2.0.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.2.1.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/migrations/0.2.2.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/__init__.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/conftest.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/contract/test_public_api_surface.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_architecture.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.2.2 → cli_agent_runner-0.2.4}/tests/unit/test_vcs_state.py +0 -0
|
@@ -5,6 +5,35 @@ 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.4] - 2026-07-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `kimi_error_detector` plugin — classifies transient errors from Kimi Code CLI rounds (`turn.step.retrying` records) so a rate-limited round backs off instead of hot-restarting. No usage events: the CLI's stream-json output carries no token counters.
|
|
12
|
+
- `agent_auth_error_detected` event — a plugin reports an auth failure named by the agent CLI's own structured output, and the `oauth_fail` detector counts those rounds directly. Makes pi's 401 loop visible (pi exits 0); the text-heuristic path keeps its nonzero-exit gate unchanged.
|
|
13
|
+
- `pi_error_detector` plugin — emits per-round token usage (summed across the round's assistant messages, since pi reports usage per message) and classifies transient errors from Pi Coding Agent rounds. pi exits 0 on provider failure, so the classifier reads the final message's `stopReason`/`errorMessage` rather than the exit code.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Round-log tail scanning hardened: plugin JSONL parsers filter non-JSON chatter *before* windowing (a stderr burst of any size can no longer evict the terminal event), monitor detectors share the same 200-line window (was 50 — the oauth/network text scans had the identical eviction risk), and each monitor poll reads only the newest 20 round logs instead of every log ever written.
|
|
17
|
+
- A relative `runtime.work_dir` (and every path derived from it) now resolves against the config file's directory instead of the caller's cwd, so `--config /abs/proj/agent-runner.toml` drives `/abs/proj` no matter where the supervisor was launched from.
|
|
18
|
+
- Remote monitor (`monitor --host <alias>`) was silently observing an empty world and reporting healthy — it listed remote filenames over ssh but read every path locally. It now fails loudly at startup (exit 1, with guidance to run the monitor on the host) until remote reads are implemented.
|
|
19
|
+
- Agent round logs (`{log_dir}/rounds/R*-*.log`) are now pruned under `runtime.round_log_retention` at the start of every round — previously that family grew unboundedly, since only the serve-level `round-<N>.log` family was pruned, and only at serve startup.
|
|
20
|
+
- Startup validation resolves the agent command exactly as the spawn does (child's PATH — `[agent.env]` may override it — and `work_dir` base); `PWD` now stays pinned to `work_dir` even if `[agent.env]` sets it. The stdout+stderr merge is now pinned by a behavioral test, not just prose.
|
|
21
|
+
- Generated serve systemd units now set `KillMode=mixed`: with systemd's default `control-group`, `systemctl stop` SIGTERMed the whole cgroup — agent child included — making the graceful round drain structurally ineffective. Existing installs: re-run `agent-runner install` (or add a drop-in) to pick this up.
|
|
22
|
+
|
|
23
|
+
See `docs/migrations/0.2.4.md`.
|
|
24
|
+
|
|
25
|
+
## [0.2.3] - 2026-07-26
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- `--preset kimi` — a preset for [Kimi Code CLI](https://github.com/MoonshotAI/kimi-code) (`kimi -p --output-format stream-json`). `docs/recipes/kimi.md` also documents running Kimi K3 through the existing `claude` preset via Moonshot's Anthropic-compatible endpoint.
|
|
29
|
+
- `--preset pi` — a preset for [Pi Coding Agent](https://github.com/earendil-works/pi) (`pi -p -na --mode json --model …`; `-na` pins project trust off for unattended runs). `docs/recipes/pi.md` documents driving Kimi K3 through pi via a Moonshot OpenAI-compatible provider.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- The agent subprocess now runs in `runtime.work_dir` (`cwd=` on spawn). Previously it inherited the supervisor's cwd and only launch conventions (systemd `WorkingDirectory=`, relative `--config`) kept the two aligned — fatal for agent CLIs with no working-directory flag of their own (e.g. `pi`). The startup check now also validates a relative `agent.command[0]` against `work_dir`, matching where it executes.
|
|
33
|
+
- Plugin round-log tail window widened 50 → 200 lines: a stderr burst after the agent's terminal JSONL event could evict it from the scan window, silently dropping usage/transient classification. The round log's merged stdout+stderr contract is now documented (`HookContext.agent_log_path`, `docs/long-running-agents.md`) — the merge is deliberate; auth/network detection reads stderr text from it.
|
|
34
|
+
|
|
35
|
+
See `docs/migrations/0.2.3.md`.
|
|
36
|
+
|
|
8
37
|
## [0.2.2] - 2026-07-18
|
|
9
38
|
|
|
10
39
|
### Removed
|
|
@@ -386,7 +415,7 @@ See `docs/migrations/0.1.17.md`.
|
|
|
386
415
|
|
|
387
416
|
### Acknowledgements
|
|
388
417
|
|
|
389
|
-
|
|
418
|
+
A downstream integrator's post-Q1-audit feedback (2026-05-14) surfaced the per-phase sub-table need (S3) and the multi-file prompt pattern (S1). Their R721 frontmatter-strip lesson informed making the (already-shipping) strip behavior an explicit opt-out config flag rather than hardcoded.
|
|
390
419
|
|
|
391
420
|
## [0.1.15] - 2026-05-14
|
|
392
421
|
|
|
@@ -418,10 +447,10 @@ independent components bundled into one release because both serve the same
|
|
|
418
447
|
|
|
419
448
|
### Acknowledgements
|
|
420
449
|
|
|
421
|
-
Two of six nice-to-have items surfaced in
|
|
450
|
+
Two of six nice-to-have items surfaced in a downstream integrator's v0.1.12
|
|
422
451
|
production-evaluation report (2026-05-14). An earlier scope also included
|
|
423
452
|
per-phase runtime override; spec review caught that `runtime.round_timeout_per_phase`
|
|
424
|
-
already covers
|
|
453
|
+
already covers the integrator's stated need, so that component was deferred to a future
|
|
425
454
|
release where a second per-phase field surfaces. Other items in the report
|
|
426
455
|
(detector helper, hot-reload, replay) remain intentionally out of scope.
|
|
427
456
|
|
|
@@ -440,7 +469,7 @@ release where a second per-phase field surfaces. Other items in the report
|
|
|
440
469
|
|
|
441
470
|
### Acknowledgements
|
|
442
471
|
|
|
443
|
-
Thanks to
|
|
472
|
+
Thanks to our downstream integrators — this release answers their imminent
|
|
444
473
|
production-deployment requirement for upgrade-without-disruption. After
|
|
445
474
|
confirming round duration (10-40 min) fits within graceful-stop tolerance,
|
|
446
475
|
this release scopes to round-boundary upgrade UX (Level 1). Mid-round
|
|
@@ -465,7 +494,7 @@ real production need.
|
|
|
465
494
|
|
|
466
495
|
### Acknowledgements
|
|
467
496
|
|
|
468
|
-
Thanks to
|
|
497
|
+
Thanks to our downstream integrators for the deep v0.1.10 audit-session feedback
|
|
469
498
|
(6 items, 3-round real-run testing on ARMv8 Pi). This release reframes those
|
|
470
499
|
items into a coherent "Plugin & Operator Transparency" theme across three
|
|
471
500
|
layers: transparency (see what plugins do), operator override (escape hatches
|
|
@@ -487,16 +516,16 @@ for audit/debug), and diagnostic quality (errors point at the next debug step).
|
|
|
487
516
|
|
|
488
517
|
### Migration notes
|
|
489
518
|
|
|
490
|
-
- `Config.plugins` type change is breaking for any caller reading the field as a dict (`cfg.plugins.get("foo")`). Plugin authors using `[plugins.
|
|
519
|
+
- `Config.plugins` type change is breaking for any caller reading the field as a dict (`cfg.plugins.get("foo")`). Plugin authors using `[plugins.acme_*]`-style keys: read them from `cfg.plugins.raw.get("acme_*")` instead.
|
|
491
520
|
- `LockHeldError` message format changed (now includes holder info: `"another agent-runner is holding /path (held by PID N, age Ns, cmd: ...)"` or stale/missing variants). Operators grepping the exact format string need to update.
|
|
492
|
-
- For
|
|
521
|
+
- For the integrator's P5 confusion: see new `docs/architecture.md` section "Plugin injection: two paths" — `inject_context` and `disable_pre_round_hooks` are INDEPENDENT flags. Setting one does not affect the other.
|
|
493
522
|
- **Known limitation**: `[plugins] disable` removes named plugins from the hook / context-enricher / detector / event-kind registries, but does NOT remove a disabled plugin's owned VCS paths (the `register_plugin_owned_paths` registry has no name attribution today). Mostly inert. If this becomes a real issue, file a GitHub issue.
|
|
494
523
|
|
|
495
524
|
## [0.1.11] - 2026-05-13
|
|
496
525
|
|
|
497
526
|
### Acknowledgements
|
|
498
527
|
|
|
499
|
-
Thanks again to
|
|
528
|
+
Thanks again to our downstream integrators — this release closes the
|
|
500
529
|
network-resilience gap that 0.1.10's `MonitorRemoteError` propagation
|
|
501
530
|
exposed, plus adds per-occurrence agent network blip observability requested
|
|
502
531
|
during 0.1.10 handover review.
|
|
@@ -525,7 +554,7 @@ For network-related failures, the events index points at the diagnostic body:
|
|
|
525
554
|
|
|
526
555
|
### Acknowledgements
|
|
527
556
|
|
|
528
|
-
Thanks to
|
|
557
|
+
Thanks to our downstream integrators for the Phase 4 second-pass production feedback that drove every change in this release. Six audit memos across 50 minutes of validated runtime surfaced four specific gaps; this release closes them.
|
|
529
558
|
|
|
530
559
|
### Added
|
|
531
560
|
|
|
@@ -553,7 +582,7 @@ Thanks to the Argus Gateway team for the Phase 4 second-pass production feedback
|
|
|
553
582
|
|
|
554
583
|
### Acknowledgements
|
|
555
584
|
|
|
556
|
-
Thanks to
|
|
585
|
+
Thanks to our downstream integrators for the dev/qa/product wall-time data
|
|
557
586
|
(Phase 4 feedback §3.1) that drove this API shape. Their three-role
|
|
558
587
|
distribution made the case for per-phase overrides concrete.
|
|
559
588
|
|
|
@@ -579,7 +608,7 @@ documented plugin-author public surface.
|
|
|
579
608
|
|
|
580
609
|
### Acknowledgements
|
|
581
610
|
|
|
582
|
-
Thanks to
|
|
611
|
+
Thanks to our downstream integrators for Phase 4 dogfooding feedback that drove
|
|
583
612
|
every item in this release. 3 audit memos (~90KB) silently swept into an
|
|
584
613
|
orphan stash is a real-world failure mode; this release closes that loop.
|
|
585
614
|
|
|
@@ -608,7 +637,7 @@ orphan stash is a real-world failure mode; this release closes that loop.
|
|
|
608
637
|
|
|
609
638
|
- Plugin outputs in plugin-declared paths (e.g. `proposals/`,
|
|
610
639
|
`logs/plugins/my_plugin/`) no longer silently swept into orphan stashes
|
|
611
|
-
by `process_orphan_wip`. Previously: 90KB
|
|
640
|
+
by `process_orphan_wip`. Previously: 90KB audit memos invisible
|
|
612
641
|
after Phase 4 round; required stash archaeology to recover.
|
|
613
642
|
|
|
614
643
|
### Migration
|
|
@@ -651,7 +680,7 @@ Or regenerate cleanly:
|
|
|
651
680
|
agent-runner init --preset claude --force
|
|
652
681
|
```
|
|
653
682
|
|
|
654
|
-
Plugin authors (
|
|
683
|
+
Plugin authors (downstream integrators, etc.): no public API was renamed or removed
|
|
655
684
|
from your import surface. The deleted symbols (`agent_runner.agent_runtime.CRITICAL_ENV_DEFAULTS`,
|
|
656
685
|
`agent_runner.agent_runtime.merge_critical_envs`) were internal — not part of
|
|
657
686
|
the documented plugin API. A new public-API contract test
|
|
@@ -882,7 +911,7 @@ Initial public release on PyPI as `cli-agent-runner`.
|
|
|
882
911
|
|
|
883
912
|
### Added
|
|
884
913
|
- Three-layer model: Round / Loop / Witness.
|
|
885
|
-
-
|
|
914
|
+
- 14 CLI verbs: `init`, `install`, `uninstall`, `start`, `stop`, `kill`,
|
|
886
915
|
`cancel`, `restart`, `status`, `round`, `serve`, `peek`, `watch`, `monitor`.
|
|
887
916
|
- 11 named defenses (round timeout, process group isolation, orphan stash
|
|
888
917
|
with SHA lock, set-diff classification, smoke check, flock concurrency,
|
|
@@ -909,7 +938,11 @@ Initial public release on PyPI as `cli-agent-runner`.
|
|
|
909
938
|
- Tag-triggered release publishing to PyPI via Trusted Publishing OIDC,
|
|
910
939
|
gated by a manual approval on the `pypi` GitHub environment.
|
|
911
940
|
|
|
912
|
-
[Unreleased]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.
|
|
941
|
+
[Unreleased]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.4...HEAD
|
|
942
|
+
[0.2.4]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.3...v0.2.4
|
|
943
|
+
[0.2.3]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.2...v0.2.3
|
|
944
|
+
[0.2.2]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.1...v0.2.2
|
|
945
|
+
[0.2.1]: https://github.com/wan9yu/cli-agent-runner/compare/v0.2.0...v0.2.1
|
|
913
946
|
[0.2.0]: https://github.com/wan9yu/cli-agent-runner/compare/v0.1.42...v0.2.0
|
|
914
947
|
[0.1.26]: https://github.com/wan9yu/cli-agent-runner/compare/v0.1.25...v0.1.26
|
|
915
948
|
[0.1.25]: https://github.com/wan9yu/cli-agent-runner/compare/v0.1.24...v0.1.25
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
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
|
|
@@ -41,7 +41,7 @@ Description-Content-Type: text/markdown
|
|
|
41
41
|
# agent-runner
|
|
42
42
|
|
|
43
43
|
A restart-on-exit supervisor for autonomous coding CLIs. Ships presets for
|
|
44
|
-
Claude Code, aider, gemini and
|
|
44
|
+
Claude Code, aider, gemini, codewhale, kimi and pi; any prompt-arg CLI via custom
|
|
45
45
|
config. Spawn the agent round-after-round under defenses that prevent the
|
|
46
46
|
failure modes
|
|
47
47
|
that bite in production: stuck rounds, orphan commits, OAuth burn loops,
|
|
@@ -116,20 +116,20 @@ Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
|
|
|
116
116
|
- `oauth_fail` — burning API quota on auth-rejected rounds
|
|
117
117
|
- `disk_critical` — writing to a near-full disk risks corruption
|
|
118
118
|
|
|
119
|
-
Runs
|
|
119
|
+
Runs against the supervised project's local logs:
|
|
120
120
|
|
|
121
121
|
```bash
|
|
122
|
-
agent-runner monitor #
|
|
123
|
-
agent-runner monitor --host pi # remote, 60s poll
|
|
122
|
+
agent-runner monitor # 30s poll
|
|
124
123
|
agent-runner monitor --json | jq -c # pipe to downstream consumers
|
|
125
124
|
```
|
|
126
125
|
|
|
127
|
-
> **
|
|
128
|
-
>
|
|
129
|
-
>
|
|
130
|
-
>
|
|
131
|
-
> See [`docs/runbook.md`](docs/runbook.md) § "Remote monitor & SSH
|
|
132
|
-
>
|
|
126
|
+
> **Remote monitoring is unsupported in this version**: `monitor --host <alias>`
|
|
127
|
+
> cannot read a remote host's round logs or events, so it exits with an error at
|
|
128
|
+
> startup instead of watching an empty world and reporting healthy. Run the
|
|
129
|
+
> monitor on the supervised host itself (`ssh <alias>`, then `agent-runner
|
|
130
|
+
> monitor`). See [`docs/runbook.md`](docs/runbook.md) § "Remote monitor & SSH
|
|
131
|
+
> trust" — the SSH trust boundary applies to any agent-runner command you drive
|
|
132
|
+
> over ssh.
|
|
133
133
|
|
|
134
134
|
## Documentation
|
|
135
135
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# agent-runner
|
|
6
6
|
|
|
7
7
|
A restart-on-exit supervisor for autonomous coding CLIs. Ships presets for
|
|
8
|
-
Claude Code, aider, gemini and
|
|
8
|
+
Claude Code, aider, gemini, codewhale, kimi and pi; any prompt-arg CLI via custom
|
|
9
9
|
config. Spawn the agent round-after-round under defenses that prevent the
|
|
10
10
|
failure modes
|
|
11
11
|
that bite in production: stuck rounds, orphan commits, OAuth burn loops,
|
|
@@ -80,20 +80,20 @@ Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
|
|
|
80
80
|
- `oauth_fail` — burning API quota on auth-rejected rounds
|
|
81
81
|
- `disk_critical` — writing to a near-full disk risks corruption
|
|
82
82
|
|
|
83
|
-
Runs
|
|
83
|
+
Runs against the supervised project's local logs:
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
agent-runner monitor #
|
|
87
|
-
agent-runner monitor --host pi # remote, 60s poll
|
|
86
|
+
agent-runner monitor # 30s poll
|
|
88
87
|
agent-runner monitor --json | jq -c # pipe to downstream consumers
|
|
89
88
|
```
|
|
90
89
|
|
|
91
|
-
> **
|
|
92
|
-
>
|
|
93
|
-
>
|
|
94
|
-
>
|
|
95
|
-
> See [`docs/runbook.md`](docs/runbook.md) § "Remote monitor & SSH
|
|
96
|
-
>
|
|
90
|
+
> **Remote monitoring is unsupported in this version**: `monitor --host <alias>`
|
|
91
|
+
> cannot read a remote host's round logs or events, so it exits with an error at
|
|
92
|
+
> startup instead of watching an empty world and reporting healthy. Run the
|
|
93
|
+
> monitor on the supervised host itself (`ssh <alias>`, then `agent-runner
|
|
94
|
+
> monitor`). See [`docs/runbook.md`](docs/runbook.md) § "Remote monitor & SSH
|
|
95
|
+
> trust" — the SSH trust boundary applies to any agent-runner command you drive
|
|
96
|
+
> over ssh.
|
|
97
97
|
|
|
98
98
|
## Documentation
|
|
99
99
|
|
|
@@ -14,6 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
|
17
|
+
"emit_agent_auth_error_detected",
|
|
17
18
|
"emit_agent_usage_recorded",
|
|
18
19
|
"emit_anomaly_repetitive_tool",
|
|
19
20
|
"emit_fresh_eyes_round_triggered",
|
|
@@ -150,6 +151,38 @@ def emit_transient_error_detected(
|
|
|
150
151
|
)
|
|
151
152
|
|
|
152
153
|
|
|
154
|
+
def emit_agent_auth_error_detected(
|
|
155
|
+
log_dir: Path,
|
|
156
|
+
*,
|
|
157
|
+
round_num: int,
|
|
158
|
+
agent: str,
|
|
159
|
+
raw: str,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Emit an authentication/authorization failure the agent itself reported.
|
|
162
|
+
|
|
163
|
+
Contract: emitted by a per-CLI plugin only when the agent's OWN structured
|
|
164
|
+
output names the failure (e.g. an HTTP 401 in its JSON event stream). That
|
|
165
|
+
is certain evidence, unlike the monitor's ``oauth_fail`` text heuristic,
|
|
166
|
+
which scans free-text log tails and therefore needs a nonzero-exit shield
|
|
167
|
+
against prose that merely mentions "401". The monitor counts a round
|
|
168
|
+
carrying this event without that shield — which is what makes an auth loop
|
|
169
|
+
visible for a CLI that exits 0 on provider failure.
|
|
170
|
+
|
|
171
|
+
No back-off partner event: an auth failure is permanent until an operator
|
|
172
|
+
fixes the credential, so it is deliberately not a transient classification.
|
|
173
|
+
"""
|
|
174
|
+
from agent_runner._redact import redact_secrets
|
|
175
|
+
from agent_runner.events import AGENT_AUTH_ERROR_DETECTED, emit
|
|
176
|
+
|
|
177
|
+
emit(
|
|
178
|
+
log_dir,
|
|
179
|
+
AGENT_AUTH_ERROR_DETECTED,
|
|
180
|
+
round_num=round_num,
|
|
181
|
+
agent=agent,
|
|
182
|
+
raw=redact_secrets(raw),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
153
186
|
def emit_transient_error_recovered(
|
|
154
187
|
log_dir: Path,
|
|
155
188
|
*,
|
|
@@ -205,7 +238,10 @@ def emit_agent_usage_recorded(
|
|
|
205
238
|
Claude: count of ``tool_use`` content blocks across all assistant events.
|
|
206
239
|
Gemini: ``stats.tool_calls``.
|
|
207
240
|
- ``phase``: phase label from HookContext (e.g. "planning"); empty string when None.
|
|
208
|
-
- ``success``:
|
|
241
|
+
- ``success``: the supervisor's clean-exit predicate (``RoundResult.ok``).
|
|
242
|
+
A plugin MAY additionally fold in the agent's own terminal verdict when
|
|
243
|
+
its CLI's exit code is unreliable — pi does, because pi exits 0 on
|
|
244
|
+
provider failure.
|
|
209
245
|
"""
|
|
210
246
|
from agent_runner.events import AGENT_USAGE_RECORDED, emit
|
|
211
247
|
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 4)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -11,6 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
import os
|
|
13
13
|
import re
|
|
14
|
+
import shutil
|
|
14
15
|
import signal
|
|
15
16
|
import subprocess # noqa: TID251 — sanctioned subprocess caller
|
|
16
17
|
import threading
|
|
@@ -52,6 +53,15 @@ class RunResult:
|
|
|
52
53
|
killed_for_grace: bool = False
|
|
53
54
|
grace_kill_children: list[dict] = field(default_factory=list)
|
|
54
55
|
|
|
56
|
+
@property
|
|
57
|
+
def ok(self) -> bool:
|
|
58
|
+
"""The supervisor's round-success predicate: clean exit, no timeout.
|
|
59
|
+
|
|
60
|
+
Single definition of "did this round fail" at the supervisor level. A
|
|
61
|
+
plugin MAY narrow it further when its CLI's exit code is unreliable.
|
|
62
|
+
"""
|
|
63
|
+
return self.exit_code == 0 and not self.timed_out
|
|
64
|
+
|
|
55
65
|
|
|
56
66
|
def _build_argv(command: list[str], prompt_arg_template: list[str], prompt: str) -> list[str]:
|
|
57
67
|
"""Build full argv: command + prompt args (with {prompt} substituted)."""
|
|
@@ -130,6 +140,24 @@ def _live_children(
|
|
|
130
140
|
_RESULT_MARKER = b'"type":"result"'
|
|
131
141
|
|
|
132
142
|
|
|
143
|
+
def resolve_exec_target(command0: str, work_dir: Path, env_path: str | None = None) -> str | None:
|
|
144
|
+
"""Model of Popen's POSIX exec resolution for argv[0].
|
|
145
|
+
|
|
146
|
+
Kept beside the ``Popen`` in :func:`run` so validation and exec cannot
|
|
147
|
+
drift: a slash-containing argv[0] resolves against the child's cwd
|
|
148
|
+
(``work_dir``); a bare name is looked up on the CHILD's PATH
|
|
149
|
+
(``env_path`` — pass ``[agent.env]``'s PATH override if set; ``None``
|
|
150
|
+
falls back to the supervisor's, matching env inheritance). Returns the
|
|
151
|
+
resolved executable path, or ``None`` if it would not exec.
|
|
152
|
+
"""
|
|
153
|
+
if "/" in command0:
|
|
154
|
+
candidate = Path(command0)
|
|
155
|
+
if not candidate.is_absolute():
|
|
156
|
+
candidate = work_dir / candidate
|
|
157
|
+
return shutil.which(str(candidate))
|
|
158
|
+
return shutil.which(command0, path=env_path)
|
|
159
|
+
|
|
160
|
+
|
|
133
161
|
def run(
|
|
134
162
|
*,
|
|
135
163
|
command: list[str],
|
|
@@ -137,6 +165,7 @@ def run(
|
|
|
137
165
|
prompt: str,
|
|
138
166
|
prompt_delivery: str = "argv",
|
|
139
167
|
timeout_s: int,
|
|
168
|
+
work_dir: Path,
|
|
140
169
|
log_path: Path,
|
|
141
170
|
env_extra: dict[str, str],
|
|
142
171
|
max_grace_after_result_s: int = 0,
|
|
@@ -149,6 +178,10 @@ def run(
|
|
|
149
178
|
|
|
150
179
|
Wall-clock timeout (R1128). On timeout: SIGTERM pgroup → REAP_GRACE_S → SIGKILL.
|
|
151
180
|
|
|
181
|
+
work_dir: the agent child's working directory; callers pass the
|
|
182
|
+
already-absolute cfg.runtime.work_dir. CLIs with no --cwd flag of their
|
|
183
|
+
own (e.g. pi) depend on this.
|
|
184
|
+
|
|
152
185
|
max_grace_after_result_s: when > 0, start a countdown after the first
|
|
153
186
|
type=result event is detected in the log. After it elapses, reap the
|
|
154
187
|
process group only if the agent has no live worker processes left (a
|
|
@@ -175,16 +208,21 @@ def run(
|
|
|
175
208
|
if stdin_mode
|
|
176
209
|
else _build_argv(command, prompt_arg_template, prompt)
|
|
177
210
|
)
|
|
178
|
-
|
|
211
|
+
# PWD pinned last — it mirrors cwd= (a correctness pin, not a knob), so
|
|
212
|
+
# an [agent.env] PWD cannot silently diverge from where the child runs.
|
|
213
|
+
env = {**os.environ, **env_extra, "PWD": str(work_dir)}
|
|
179
214
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
180
215
|
log_file = log_path.open("w", encoding="utf-8")
|
|
181
216
|
start = time.time()
|
|
182
217
|
last_progress_at = start
|
|
183
218
|
proc = subprocess.Popen(
|
|
184
219
|
argv,
|
|
220
|
+
cwd=work_dir,
|
|
185
221
|
env=env,
|
|
186
222
|
stdin=subprocess.PIPE if stdin_mode else subprocess.DEVNULL,
|
|
187
223
|
stdout=log_file,
|
|
224
|
+
# Merged on purpose: oauth_fail / network_fail / network-blip detection
|
|
225
|
+
# regex-scan stderr text out of this log (see hooks.agent_log_path).
|
|
188
226
|
stderr=subprocess.STDOUT,
|
|
189
227
|
start_new_session=True,
|
|
190
228
|
)
|
|
@@ -486,6 +486,8 @@ def _poll_once(project: str | Path, *, host: str | None) -> list[monitor.Alert]:
|
|
|
486
486
|
if host is None:
|
|
487
487
|
src = monitor.LocalSource(log_dir=cfg.runtime.log_dir)
|
|
488
488
|
else:
|
|
489
|
+
# Dormant: monitor_loop rejects --host before any poll, so this branch
|
|
490
|
+
# is unreachable today. See monitor.MonitorRemoteUnsupportedError.
|
|
489
491
|
src = monitor.RemoteSource(host=host, project=_project_name(work_dir))
|
|
490
492
|
events = monitor.parse_events_from_jsonl_files(src.events_files())
|
|
491
493
|
metrics = monitor.parse_events_from_jsonl_files(src.metrics_files())
|
|
@@ -525,12 +527,29 @@ def monitor_loop(
|
|
|
525
527
|
to that kind as the canonical "supervision is up" signal (monitor is otherwise
|
|
526
528
|
silent during healthy operation by design).
|
|
527
529
|
|
|
530
|
+
``host`` (remote mode) raises ``MonitorRemoteUnsupportedError`` immediately:
|
|
531
|
+
remote reads are unimplemented, so a remote loop would poll an empty world
|
|
532
|
+
and report healthy forever. The check is eager — this wrapper validates
|
|
533
|
+
before handing back the generator, so the failure lands at startup rather
|
|
534
|
+
than at the first ``next()``.
|
|
535
|
+
"""
|
|
536
|
+
if host is not None:
|
|
537
|
+
raise monitor.MonitorRemoteUnsupportedError(host)
|
|
538
|
+
return _monitor_loop_iter(project, host=host, interval_s=interval_s)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _monitor_loop_iter(
|
|
542
|
+
project: str | Path | None = None, *, host: str | None = None, interval_s: int = 30
|
|
543
|
+
) -> Iterator[monitor.Alert]:
|
|
544
|
+
"""Polling generator behind ``monitor_loop``.
|
|
545
|
+
|
|
528
546
|
Tolerates transient ``MonitorRemoteError`` failures (from ``--host`` ssh)
|
|
529
547
|
for up to ``cfg.monitor.remote_failure_tolerance_s`` seconds with exponential
|
|
530
548
|
backoff (1s → 2s → 4s → ... → 30s cap). Each retry emits ``monitor_remote_blip``;
|
|
531
549
|
crossing the cap emits one ``monitor_remote_giveup`` and propagates the error
|
|
532
550
|
(CLI exits 1; systemd restarts the process). Setting tolerance to 0 preserves
|
|
533
|
-
the 0.1.10 immediate-propagate behavior with no blip events emitted.
|
|
551
|
+
the 0.1.10 immediate-propagate behavior with no blip events emitted. That
|
|
552
|
+
path is dormant while remote mode is rejected at startup.
|
|
534
553
|
"""
|
|
535
554
|
import json as _json
|
|
536
555
|
|
|
@@ -771,6 +790,7 @@ def check_self_terminated_sentinel(log_dir: Path) -> bool:
|
|
|
771
790
|
# Re-export emit_* wrappers from _emit module (extracted for size hygiene).
|
|
772
791
|
# Preserves the public import surface: `from agent_runner.api import emit_*` continues to work.
|
|
773
792
|
from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-export
|
|
793
|
+
emit_agent_auth_error_detected,
|
|
774
794
|
emit_agent_usage_recorded,
|
|
775
795
|
emit_anomaly_repetitive_tool,
|
|
776
796
|
emit_config_broken,
|
|
@@ -173,6 +173,15 @@ class RoundResult:
|
|
|
173
173
|
dirty_outcome: DirtyOutcome | None = None
|
|
174
174
|
killed_for_grace: bool = False
|
|
175
175
|
|
|
176
|
+
@property
|
|
177
|
+
def ok(self) -> bool:
|
|
178
|
+
"""The supervisor's round-success predicate: clean exit, no timeout.
|
|
179
|
+
|
|
180
|
+
Single definition of "did this round fail" at the supervisor level. A
|
|
181
|
+
plugin MAY narrow it further when its CLI's exit code is unreliable.
|
|
182
|
+
"""
|
|
183
|
+
return self.exit_code == 0 and not self.timed_out
|
|
184
|
+
|
|
176
185
|
|
|
177
186
|
@dataclass(frozen=True)
|
|
178
187
|
class InitResult:
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Shared constants and tail-scan helpers for built-in CLI plugins.
|
|
2
|
+
|
|
3
|
+
Extracted to single source of truth so every round-log tail-scanner
|
|
4
|
+
(plugin JSONL parsers and monitor text detectors) uses the same window
|
|
5
|
+
size, raw-text caps, transient-error back-off defaults, and the same
|
|
6
|
+
HTTP-status → classification ladder.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from collections import deque
|
|
13
|
+
from collections.abc import Iterator
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, TextIO
|
|
16
|
+
|
|
17
|
+
_TAIL_LINES: int = 200
|
|
18
|
+
"""Number of log lines to scan from the end of a round log.
|
|
19
|
+
|
|
20
|
+
The round log is merged stdout+stderr. JSONL consumers window via
|
|
21
|
+
``json_tail`` (chatter filtered before windowing); plain-text consumers
|
|
22
|
+
(monitor oauth/network detectors) window the raw tail — keep this generous
|
|
23
|
+
so a stderr burst cannot evict what they scan for."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def json_tail(f: TextIO, maxlen: int = _TAIL_LINES) -> deque[str]:
|
|
27
|
+
"""Last ``maxlen`` JSON-looking lines of a merged round log.
|
|
28
|
+
|
|
29
|
+
Non-JSON chatter (stderr text) is filtered BEFORE windowing, so a burst
|
|
30
|
+
of any size cannot evict the terminal JSONL event from the window.
|
|
31
|
+
"""
|
|
32
|
+
return deque((ln for ln in f if ln.lstrip()[:1] in "{["), maxlen=maxlen)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def json_events(log_path: Path) -> Iterator[dict]:
|
|
36
|
+
"""JSON objects in a round log's tail window, in file order.
|
|
37
|
+
|
|
38
|
+
One reader for every plugin's parse loop: window via ``json_tail``, then
|
|
39
|
+
per line strip / skip blank / ``json.loads`` / drop anything that is not an
|
|
40
|
+
object. Non-JSON lines are expected — the round log merges stdout+stderr,
|
|
41
|
+
and every CLI writes some plain text there.
|
|
42
|
+
"""
|
|
43
|
+
with log_path.open("r", encoding="utf-8", errors="replace") as f:
|
|
44
|
+
tail = json_tail(f)
|
|
45
|
+
for line in tail:
|
|
46
|
+
line = line.strip()
|
|
47
|
+
if not line:
|
|
48
|
+
continue
|
|
49
|
+
try:
|
|
50
|
+
event = json.loads(line)
|
|
51
|
+
except json.JSONDecodeError:
|
|
52
|
+
continue
|
|
53
|
+
if isinstance(event, dict):
|
|
54
|
+
yield event
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_RAW_CAP: int = 200
|
|
58
|
+
"""Maximum length for ``raw`` field in transient_error_detected payload."""
|
|
59
|
+
|
|
60
|
+
# Default back-off durations (seconds) for non-precise transient classifications.
|
|
61
|
+
# rate_limit_account uses exact resetsAt epoch from claude; not in this table.
|
|
62
|
+
_BACK_OFF_DEFAULTS: dict[str, int] = {
|
|
63
|
+
"rate_limit_model": 60,
|
|
64
|
+
"api_transient_5xx": 60,
|
|
65
|
+
"api_timeout": 30,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# 5xx codes treated as transient (retry-worthy server errors per RFC 9110):
|
|
69
|
+
# 500=unexpected, 502=bad gateway, 503=unavailable, 504=gateway timeout,
|
|
70
|
+
# 529=overloaded (Anthropic's non-RFC code emitted during sustained capacity
|
|
71
|
+
# issues; treated as transient per Anthropic SDK behavior).
|
|
72
|
+
# Excluded: 501 (not implemented = permanent), 505 (HTTP version mismatch).
|
|
73
|
+
_5XX_STATUSES: frozenset[int] = frozenset({500, 502, 503, 504, 529})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def classify_transient_status(status: Any) -> str | None:
|
|
77
|
+
"""Map an HTTP status from a CLI's error record to a transient bucket.
|
|
78
|
+
|
|
79
|
+
The one ladder every plugin classifier delegates to, so a status is
|
|
80
|
+
bucketed identically no matter which CLI reported it.
|
|
81
|
+
|
|
82
|
+
``None`` means 'not transient — do not back off'. Notably 401 (auth) and
|
|
83
|
+
404 (unknown model) land here: both are permanent until an operator fixes
|
|
84
|
+
configuration, which is oauth/config territory rather than something a
|
|
85
|
+
back-off would clear. Non-integer or missing statuses are ``None`` too;
|
|
86
|
+
a CLI that reports no status gets no classification.
|
|
87
|
+
"""
|
|
88
|
+
if not isinstance(status, int) or isinstance(status, bool):
|
|
89
|
+
return None
|
|
90
|
+
if status == 429:
|
|
91
|
+
return "rate_limit_model"
|
|
92
|
+
if status in _5XX_STATUSES:
|
|
93
|
+
return "api_transient_5xx"
|
|
94
|
+
if status == 408:
|
|
95
|
+
return "api_timeout"
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
_CLASSIFICATIONS: frozenset[str] = frozenset(
|
|
100
|
+
{
|
|
101
|
+
"rate_limit_account",
|
|
102
|
+
"rate_limit_model",
|
|
103
|
+
"api_transient_5xx",
|
|
104
|
+
"api_timeout",
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
"""Canonical set of transient-error classifications.
|
|
108
|
+
|
|
109
|
+
rate_limit_account uses server-provided resetsAt (excluded from
|
|
110
|
+
_BACK_OFF_DEFAULTS table); others use defaults from that table.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
_EXP_CAP: int = 5
|
|
114
|
+
"""Maximum exponent for transient-error consecutive backoff: 2^5 = 32×.
|
|
115
|
+
|
|
116
|
+
Beyond this, the multiplier plateaus. Combined with _ABSOLUTE_CAP_S, this
|
|
117
|
+
prevents runaway wait times during sustained outages (max wait = 30min).
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
_ABSOLUTE_CAP_S: int = 1800
|
|
121
|
+
"""Absolute upper bound on supervisor-applied transient back-off (30 min).
|
|
122
|
+
|
|
123
|
+
Applies after exp multiplier — even if base × 2^5 exceeds this, the wait
|
|
124
|
+
is clipped here. Defends against an indefinitely-stuck supervisor.
|
|
125
|
+
"""
|