cli-agent-runner 0.1.41__tar.gz → 0.1.42__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.1.41 → cli_agent_runner-0.1.42}/CHANGELOG.md +15 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/PKG-INFO +5 -5
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/README.md +4 -4
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/README.zh.md +5 -5
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/_emit.py +23 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/api.py +56 -1
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/cli/serve_cmd.py +26 -5
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/defenses.py +12 -2
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/events.py +2 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/monitor.py +0 -25
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/runner.py +5 -2
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/vcs_state.py +51 -3
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/architecture.md +7 -5
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/commands.md +1 -1
- cli_agent_runner-0.1.42/docs/migrations/0.1.42.md +58 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/thesis.md +12 -7
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/_test_helpers.py +8 -3
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_bounded_run.py +10 -2
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_fresh_eyes_signal.py +2 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_substrate_fingerprint.py +5 -1
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_architecture.py +4 -1
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_defenses.py +2 -2
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_monitor_detectors.py +1 -18
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_runner.py +2 -2
- cli_agent_runner-0.1.42/tests/unit/test_serve_config_broken.py +33 -0
- cli_agent_runner-0.1.42/tests/unit/test_serve_crash_loop.py +128 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_vcs_state.py +69 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/.codecov.yml +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/.gitignore +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/.vulture-whitelist.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/LICENSE +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/SECURITY.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/agent_runtime.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/config.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/detector_helpers.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/build.sh +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/deploy/example-agent-runner.toml +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/deploy/launchd.plist.tmpl +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/deploy/run-loop.sh +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/deploy/systemd.service.tmpl +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/README.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/configuration.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/events.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/marketing/README.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/marketing/promo-cn.html +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.16.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.17.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.19.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.20.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.21.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.22.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.23.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.24.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.25.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.26.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.27.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.28.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.29.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.30.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.31.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.32.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.33.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.34.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.35.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.36.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.37.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.38.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.39.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/migrations/0.1.40.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/plugins.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/docs/runbook.md +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/pyproject.toml +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/__init__.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/conftest.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/contract/test_public_api_surface.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_config.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_detector_helpers.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_events.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.1.41 → cli_agent_runner-0.1.42}/tests/unit/test_substrate.py +0 -0
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.42] - 2026-06-25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `crash_loop` defense — serve stops after 5 consecutive *unknown* short crashes (non-zero exit, <60s, no classified transient), escalating the restart delay and recording the failure reason. Ends the respawn-forever crash loop; recoverable-slow failures (rate-limit / quota / 5xx / timeout) still ride the transient-error backoff unchanged.
|
|
12
|
+
- `config_broken` defense — a permanent startup-battery failure now halts serve (distinct no-retry exit code `78`) instead of respawning a broken config every round.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- `vcs.dirty_action` no longer sweeps the runner's own `log_dir` bookkeeping when `log_dir` is inside `work_dir`: `auto_commit` excludes it from the commit (no more phantom `git_head` advance on a zero-work round) and `stash` excludes it from `git stash push -u` (logs no longer vanish). `.evolving/` and agent work are unaffected.
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
- The inert `smoke_fail_rate` monitor alert (could never fire — superseded by the always-on `config_broken` stop). Monitor now ships 11 detectors.
|
|
19
|
+
|
|
20
|
+
### Docs
|
|
21
|
+
- `thesis.md`: the stuck-loop defense is described honestly as a notify-level, opt-in-to-auto-stop monitor detector (`anomaly_repetitive_active`), not a default hard-stop; fixed the `stuck_loop_detected` naming drift.
|
|
22
|
+
|
|
8
23
|
## [0.1.41] - 2026-06-07
|
|
9
24
|
|
|
10
25
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.42
|
|
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
|
|
@@ -49,7 +49,7 @@ full disks, runaway memory.
|
|
|
49
49
|
|
|
50
50
|
```
|
|
51
51
|
┌──────────────────────────────────────────┐
|
|
52
|
-
│ Layer 3: The Witness (monitor) │
|
|
52
|
+
│ Layer 3: The Witness (monitor) │ 11 detectors + auto-stop
|
|
53
53
|
├──────────────────────────────────────────┤
|
|
54
54
|
│ Layer 2: The Loop (serve, ~120 LOC) │ signal-trapping restart loop
|
|
55
55
|
├──────────────────────────────────────────┤
|
|
@@ -86,14 +86,14 @@ Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
|
|
|
86
86
|
|---|---|
|
|
87
87
|
| `init` / `install` / `uninstall` | `peek` — state snapshot |
|
|
88
88
|
| `start` / `stop` / `kill` / `cancel` | `watch` — peek in a refresh loop |
|
|
89
|
-
| `restart` / `status` | `monitor` —
|
|
89
|
+
| `restart` / `status` | `monitor` — 11 detectors, alerts, auto-stop |
|
|
90
90
|
| `round` / `serve` / `upgrade` | `events` — query / stream events.jsonl |
|
|
91
91
|
|
|
92
92
|
Verb reference: [`docs/commands.md`](docs/commands.md).
|
|
93
93
|
|
|
94
94
|
## Defenses (built in)
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
12 named defenses, structured as data — see `agent-runner peek --select defenses`.
|
|
97
97
|
Each carries the historical incident it codifies and the invariant test that
|
|
98
98
|
guards it. Highlights:
|
|
99
99
|
|
|
@@ -106,7 +106,7 @@ guards it. Highlights:
|
|
|
106
106
|
|
|
107
107
|
Full list and rationale: [`docs/architecture.md`](docs/architecture.md).
|
|
108
108
|
|
|
109
|
-
## Monitor:
|
|
109
|
+
## Monitor: 11 detectors
|
|
110
110
|
|
|
111
111
|
Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
|
|
112
112
|
`mem_pressure`, `smoke_fail_rate`, `network_fail`, `rate_limit_active`,
|
|
@@ -12,7 +12,7 @@ full disks, runaway memory.
|
|
|
12
12
|
|
|
13
13
|
```
|
|
14
14
|
┌──────────────────────────────────────────┐
|
|
15
|
-
│ Layer 3: The Witness (monitor) │
|
|
15
|
+
│ Layer 3: The Witness (monitor) │ 11 detectors + auto-stop
|
|
16
16
|
├──────────────────────────────────────────┤
|
|
17
17
|
│ Layer 2: The Loop (serve, ~120 LOC) │ signal-trapping restart loop
|
|
18
18
|
├──────────────────────────────────────────┤
|
|
@@ -49,14 +49,14 @@ Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
|
|
|
49
49
|
|---|---|
|
|
50
50
|
| `init` / `install` / `uninstall` | `peek` — state snapshot |
|
|
51
51
|
| `start` / `stop` / `kill` / `cancel` | `watch` — peek in a refresh loop |
|
|
52
|
-
| `restart` / `status` | `monitor` —
|
|
52
|
+
| `restart` / `status` | `monitor` — 11 detectors, alerts, auto-stop |
|
|
53
53
|
| `round` / `serve` / `upgrade` | `events` — query / stream events.jsonl |
|
|
54
54
|
|
|
55
55
|
Verb reference: [`docs/commands.md`](docs/commands.md).
|
|
56
56
|
|
|
57
57
|
## Defenses (built in)
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
12 named defenses, structured as data — see `agent-runner peek --select defenses`.
|
|
60
60
|
Each carries the historical incident it codifies and the invariant test that
|
|
61
61
|
guards it. Highlights:
|
|
62
62
|
|
|
@@ -69,7 +69,7 @@ guards it. Highlights:
|
|
|
69
69
|
|
|
70
70
|
Full list and rationale: [`docs/architecture.md`](docs/architecture.md).
|
|
71
71
|
|
|
72
|
-
## Monitor:
|
|
72
|
+
## Monitor: 11 detectors
|
|
73
73
|
|
|
74
74
|
Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
|
|
75
75
|
`mem_pressure`, `smoke_fail_rate`, `network_fail`, `rate_limit_active`,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
把任意 CLI agent(Claude Code、自研 agent、任何长跑命令)包装成可被
|
|
8
8
|
systemd / launchd 拉起、能被远程观测的服务。**每轮跑完进程退出**,外层
|
|
9
|
-
supervisor 重启 —— 这是核心模式。中间穿插
|
|
9
|
+
supervisor 重启 —— 这是核心模式。中间穿插 12 条防御,避开 production 上
|
|
10
10
|
最容易翻车的几条路:
|
|
11
11
|
|
|
12
12
|
- 轮卡死、Tool 调用空转 → 硬墙 timeout
|
|
@@ -20,7 +20,7 @@ supervisor 重启 —— 这是核心模式。中间穿插 11 条防御,避开
|
|
|
20
20
|
|
|
21
21
|
```
|
|
22
22
|
┌──────────────────────────────────────────┐
|
|
23
|
-
│ Layer 3:Witness(monitor) │
|
|
23
|
+
│ Layer 3:Witness(monitor) │ 11 个检测器 + 自动停服
|
|
24
24
|
├──────────────────────────────────────────┤
|
|
25
25
|
│ Layer 2:Loop(serve,~120 LOC 薄壳) │ 捕获信号,循环拉起 round
|
|
26
26
|
├──────────────────────────────────────────┤
|
|
@@ -63,7 +63,7 @@ agent-runner monitor # 实时异常检测,OAuth/磁盘 critical
|
|
|
63
63
|
|---|---|
|
|
64
64
|
| `init` / `install` / `uninstall` | `peek` —— 项目状态快照 |
|
|
65
65
|
| `start` / `stop` / `kill` / `cancel` | `watch` —— peek 在刷新循环里 |
|
|
66
|
-
| `restart` / `status` | `monitor` ——
|
|
66
|
+
| `restart` / `status` | `monitor` —— 11 个检测器 + 告警 + 自动停服 |
|
|
67
67
|
| `round` / `serve` / `upgrade` | `events` —— 查询 / 流式订阅 events.jsonl |
|
|
68
68
|
|
|
69
69
|
**停服三动词**有清晰的语义分层:
|
|
@@ -73,7 +73,7 @@ agent-runner monitor # 实时异常检测,OAuth/磁盘 critical
|
|
|
73
73
|
|
|
74
74
|
动词参考:[`docs/commands.md`](docs/commands.md)。
|
|
75
75
|
|
|
76
|
-
## 内置防御(
|
|
76
|
+
## 内置防御(12 条)
|
|
77
77
|
|
|
78
78
|
防御以数据形式定义在 `agent_runner/defenses.py`,可通过
|
|
79
79
|
`agent-runner peek --select defenses` 直接拿到。每条防御自带:
|
|
@@ -95,7 +95,7 @@ agent-runner monitor # 实时异常检测,OAuth/磁盘 critical
|
|
|
95
95
|
|
|
96
96
|
完整列表 + 历史出处:[`docs/architecture.md`](docs/architecture.md)。
|
|
97
97
|
|
|
98
|
-
## Monitor:
|
|
98
|
+
## Monitor:11 个检测器
|
|
99
99
|
|
|
100
100
|
**只告警**(warning 级,服务继续跑):
|
|
101
101
|
`timeout_rate` / `hung` / `orphan_chain` / `disk_warning` /
|
|
@@ -45,6 +45,29 @@ def emit_max_rounds_reached(log_dir: Path, *, rounds_completed: int, max_rounds:
|
|
|
45
45
|
emit(log_dir, MAX_ROUNDS_REACHED, rounds_completed=rounds_completed, max_rounds=max_rounds)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
def emit_config_broken(log_dir: Path, *, reason: str) -> None:
|
|
49
|
+
"""Emit config_broken (serve stopped on a permanent startup-battery failure)."""
|
|
50
|
+
from agent_runner.events import CONFIG_BROKEN, emit
|
|
51
|
+
|
|
52
|
+
emit(log_dir, CONFIG_BROKEN, reason=reason)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def emit_crash_loop(log_dir: Path, *, consecutive: int, exit_code: int, log_path: Path) -> None:
|
|
56
|
+
"""Emit crash_loop (serve stopped after consecutive unknown short crashes).
|
|
57
|
+
|
|
58
|
+
Captures the failure reason — a redacted tail of the round log — so a
|
|
59
|
+
recurring unknown crash can later be classified into a transient bucket.
|
|
60
|
+
"""
|
|
61
|
+
from agent_runner._redact import redact_secrets
|
|
62
|
+
from agent_runner.events import CRASH_LOOP, emit
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
reason = redact_secrets(log_path.read_text(errors="replace")[-2000:])
|
|
66
|
+
except OSError:
|
|
67
|
+
reason = ""
|
|
68
|
+
emit(log_dir, CRASH_LOOP, consecutive=consecutive, exit_code=exit_code, reason=reason)
|
|
69
|
+
|
|
70
|
+
|
|
48
71
|
def emit_stop_file_detected(
|
|
49
72
|
log_dir: Path, *, stop_file: Path, content: str, rounds_completed: int
|
|
50
73
|
) -> 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.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
21
|
+
__version__ = version = '0.1.42'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 42)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -18,7 +18,7 @@ import sysconfig
|
|
|
18
18
|
import time
|
|
19
19
|
from collections.abc import Iterator
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Any
|
|
21
|
+
from typing import Any, Literal
|
|
22
22
|
|
|
23
23
|
from agent_runner import events, lifecycle
|
|
24
24
|
from agent_runner.api_types import (
|
|
@@ -45,6 +45,59 @@ from agent_runner.service_unit import (
|
|
|
45
45
|
serve_unit_filename,
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
+
# Exit code for a permanent (no-retry) startup-battery failure. A broken config
|
|
49
|
+
# does not self-heal between rounds, so serve STOPS rather than respawning it
|
|
50
|
+
# forever. 78 = EX_CONFIG (sysexits) — avoids argparse's 2 and the generic 1.
|
|
51
|
+
# Lives here (not runner.py) so serve_cmd can import it from the sanctioned api
|
|
52
|
+
# facade without coupling to runner (runner imports api, not the reverse).
|
|
53
|
+
PERMANENT_CONFIG_EXIT = 78
|
|
54
|
+
|
|
55
|
+
# Crash-loop circuit breaker (b12). The serve loop escalates the restart delay
|
|
56
|
+
# on consecutive UNKNOWN short crashes (non-zero exit, short duration, no
|
|
57
|
+
# classified transient) and STOPS after CRASH_LOOP_THRESHOLD of them — the Run 6
|
|
58
|
+
# ~100-empty-rounds scar. Recoverable-slow failures (rate limit / 5h quota / 5xx
|
|
59
|
+
# / timeout) are already handled by the transient-error throttle and never reach
|
|
60
|
+
# this path. A clean (exit 0), long, or classified-transient round resets the run.
|
|
61
|
+
CRASH_LOOP_THRESHOLD = 5
|
|
62
|
+
CRASH_LOOP_SHORT_EXIT_S = 60 # mirrors monitor.SHORT_EXIT_THRESHOLD_S
|
|
63
|
+
CRASH_LOOP_MAX_DELAY_S = 1800 # cap the escalating restart delay (30 min)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def post_round_decision(
|
|
67
|
+
*,
|
|
68
|
+
returncode: int,
|
|
69
|
+
duration_s: float,
|
|
70
|
+
throttle_active: bool,
|
|
71
|
+
consecutive: int,
|
|
72
|
+
restart_delay_s: int,
|
|
73
|
+
) -> tuple[Literal["config_broken", "crash_loop", "continue"], int, int]:
|
|
74
|
+
"""Restart policy after one round — keeps the serve loop a thin dispatcher.
|
|
75
|
+
|
|
76
|
+
Returns ``(action, delay_s, consecutive)`` where action is:
|
|
77
|
+
- ``"config_broken"`` — permanent startup failure (b18): stop.
|
|
78
|
+
- ``"crash_loop"`` — CRASH_LOOP_THRESHOLD consecutive unknown short crashes
|
|
79
|
+
(b12): stop. An unknown short crash is a non-zero, fast exit with no
|
|
80
|
+
classified transient (rate-limit/5xx/timeout are handled by the throttle).
|
|
81
|
+
- ``"continue"`` — sleep ``delay_s`` then run the next round.
|
|
82
|
+
|
|
83
|
+
A clean (exit 0), long, or transient round resets ``consecutive`` to 0; an
|
|
84
|
+
unknown short crash escalates the delay (restart × 2ⁿ, capped) until the stop.
|
|
85
|
+
"""
|
|
86
|
+
if returncode == PERMANENT_CONFIG_EXIT:
|
|
87
|
+
return ("config_broken", 0, consecutive)
|
|
88
|
+
unknown_short_crash = (
|
|
89
|
+
returncode != 0 and duration_s < CRASH_LOOP_SHORT_EXIT_S and not throttle_active
|
|
90
|
+
)
|
|
91
|
+
if unknown_short_crash:
|
|
92
|
+
consecutive += 1
|
|
93
|
+
if consecutive >= CRASH_LOOP_THRESHOLD:
|
|
94
|
+
return ("crash_loop", 0, consecutive)
|
|
95
|
+
delay = min(restart_delay_s * 2**consecutive, CRASH_LOOP_MAX_DELAY_S)
|
|
96
|
+
return ("continue", delay, consecutive)
|
|
97
|
+
delay = restart_delay_s if returncode == 0 else restart_delay_s * 2
|
|
98
|
+
return ("continue", delay, 0)
|
|
99
|
+
|
|
100
|
+
|
|
48
101
|
_PROJECT_NAME_RE = re.compile(r"^[A-Za-z0-9._-]+$")
|
|
49
102
|
|
|
50
103
|
_LINGER_HINT = (
|
|
@@ -730,6 +783,8 @@ def check_self_terminated_sentinel(log_dir: Path) -> bool:
|
|
|
730
783
|
from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-export
|
|
731
784
|
emit_agent_usage_recorded,
|
|
732
785
|
emit_anomaly_repetitive_tool,
|
|
786
|
+
emit_config_broken,
|
|
787
|
+
emit_crash_loop,
|
|
733
788
|
emit_fresh_eyes_round_triggered,
|
|
734
789
|
emit_max_rounds_reached,
|
|
735
790
|
emit_rate_limit_stop,
|
|
@@ -23,12 +23,15 @@ from agent_runner._throttle import _check_throttle_state
|
|
|
23
23
|
from agent_runner._throttle import reset_counters as _reset_counters
|
|
24
24
|
from agent_runner.api import (
|
|
25
25
|
check_self_terminated_sentinel,
|
|
26
|
+
emit_config_broken,
|
|
27
|
+
emit_crash_loop,
|
|
26
28
|
emit_fresh_eyes_round_triggered,
|
|
27
29
|
emit_max_rounds_reached,
|
|
28
30
|
emit_rate_limit_stop,
|
|
29
31
|
emit_round_substrate_after,
|
|
30
32
|
emit_round_substrate_before,
|
|
31
33
|
emit_stop_file_detected,
|
|
34
|
+
post_round_decision,
|
|
32
35
|
)
|
|
33
36
|
from agent_runner.cli.common import cfg_from_args
|
|
34
37
|
from agent_runner.hooks import run_serve_startup_hooks
|
|
@@ -135,6 +138,7 @@ def cmd(args) -> int:
|
|
|
135
138
|
stop_file = cfg.runtime.stop_file # cache: same pattern as effective_max_rounds
|
|
136
139
|
work_dir = cfg.runtime.work_dir
|
|
137
140
|
rounds_completed = 0
|
|
141
|
+
consecutive_crashes = 0 # b12: consecutive UNKNOWN short crashes (crash-loop breaker)
|
|
138
142
|
|
|
139
143
|
try:
|
|
140
144
|
pid_file.write(os.getpid())
|
|
@@ -197,6 +201,7 @@ def cmd(args) -> int:
|
|
|
197
201
|
every_n=cfg.runtime.fresh_eyes_every_n,
|
|
198
202
|
)
|
|
199
203
|
round_log_path = log_dir / f"round-{round_num}.log"
|
|
204
|
+
round_started = time.monotonic()
|
|
200
205
|
with round_log_path.open("w") as f:
|
|
201
206
|
r = subprocess.run(
|
|
202
207
|
[
|
|
@@ -211,6 +216,7 @@ def cmd(args) -> int:
|
|
|
211
216
|
stdout=f,
|
|
212
217
|
stderr=subprocess.STDOUT,
|
|
213
218
|
)
|
|
219
|
+
round_duration_s = time.monotonic() - round_started
|
|
214
220
|
atomic_relink(log_dir / ROUND_CURRENT_LINK, round_log_path)
|
|
215
221
|
git_head_after = compute_git_head(work_dir)
|
|
216
222
|
paths_hash_after = compute_paths_hash(work_dir, cfg.runtime.substrate_fingerprint_paths)
|
|
@@ -221,13 +227,28 @@ def cmd(args) -> int:
|
|
|
221
227
|
paths_hash=paths_hash_after,
|
|
222
228
|
)
|
|
223
229
|
rounds_completed += 1
|
|
230
|
+
# Restart policy (config_broken / crash_loop / continue) lives in the
|
|
231
|
+
# tested api.post_round_decision helper so this loop stays thin.
|
|
232
|
+
action, delay, consecutive_crashes = post_round_decision(
|
|
233
|
+
returncode=r.returncode,
|
|
234
|
+
duration_s=round_duration_s,
|
|
235
|
+
throttle_active=_check_throttle_state(log_dir) is not None,
|
|
236
|
+
consecutive=consecutive_crashes,
|
|
237
|
+
restart_delay_s=cfg.runtime.restart_delay_s,
|
|
238
|
+
)
|
|
239
|
+
if action == "config_broken":
|
|
240
|
+
emit_config_broken(log_dir, reason="startup battery permanent failure")
|
|
241
|
+
break
|
|
242
|
+
if action == "crash_loop":
|
|
243
|
+
emit_crash_loop(
|
|
244
|
+
log_dir,
|
|
245
|
+
consecutive=consecutive_crashes,
|
|
246
|
+
exit_code=r.returncode,
|
|
247
|
+
log_path=round_log_path,
|
|
248
|
+
)
|
|
249
|
+
break
|
|
224
250
|
if args.once or stop["requested"]:
|
|
225
251
|
break
|
|
226
|
-
delay = (
|
|
227
|
-
cfg.runtime.restart_delay_s
|
|
228
|
-
if r.returncode == 0
|
|
229
|
-
else cfg.runtime.restart_delay_s * 2
|
|
230
|
-
)
|
|
231
252
|
time.sleep(delay)
|
|
232
253
|
finally:
|
|
233
254
|
pid_file.unlink()
|
|
@@ -83,8 +83,18 @@ def catalog(cfg: Config) -> list[Defense]:
|
|
|
83
83
|
Defense(
|
|
84
84
|
name="startup_smoke_check",
|
|
85
85
|
value="6 checks (config / log_dir / agent_cli / git / prompt_file / prompt_smoke)",
|
|
86
|
-
codifies=
|
|
87
|
-
|
|
86
|
+
codifies=(
|
|
87
|
+
"R721 + #446 — _common.md frontmatter caused 4h/123-round silent burn; "
|
|
88
|
+
"now halts serve (config_broken) instead of respawning a broken config"
|
|
89
|
+
),
|
|
90
|
+
guarded_by=Path("tests/unit/test_serve_config_broken.py"),
|
|
91
|
+
current_state="active",
|
|
92
|
+
),
|
|
93
|
+
Defense(
|
|
94
|
+
name="crash_loop_breaker",
|
|
95
|
+
value="stop after 5 consecutive short crashes; exp-escalating delay",
|
|
96
|
+
codifies="Run 6 — crashing agent respawned ~100 empty rounds at a fixed 2x delay",
|
|
97
|
+
guarded_by=Path("tests/unit/test_serve_crash_loop.py"),
|
|
88
98
|
current_state="active",
|
|
89
99
|
),
|
|
90
100
|
Defense(
|
|
@@ -32,6 +32,8 @@ ANOMALY_REPETITIVE_TOOL = "anomaly_repetitive_tool"
|
|
|
32
32
|
AGENT_NETWORK_BLIP = "agent_network_blip"
|
|
33
33
|
AGENT_SPAWN = "agent_spawn"
|
|
34
34
|
AGENT_USAGE_RECORDED = "agent_usage_recorded"
|
|
35
|
+
CONFIG_BROKEN = "config_broken"
|
|
36
|
+
CRASH_LOOP = "crash_loop"
|
|
35
37
|
DIRTY_COMMIT_FAILED = "dirty_commit_failed"
|
|
36
38
|
DIRTY_DETECTED = "dirty_detected"
|
|
37
39
|
FRESH_EYES_ROUND_TRIGGERED = "fresh_eyes_round_triggered"
|
|
@@ -49,7 +49,6 @@ KNOWN_ALERT_KINDS: frozenset[str] = frozenset(
|
|
|
49
49
|
"disk_warning",
|
|
50
50
|
"disk_critical",
|
|
51
51
|
"mem_pressure",
|
|
52
|
-
"smoke_fail_rate",
|
|
53
52
|
"oauth_fail",
|
|
54
53
|
"network_fail",
|
|
55
54
|
"rate_limit_active",
|
|
@@ -265,29 +264,6 @@ def detect_mem_pressure(metrics: list[dict[str, Any]], *, threshold_mb: int = 20
|
|
|
265
264
|
)
|
|
266
265
|
|
|
267
266
|
|
|
268
|
-
def detect_smoke_fail_rate(
|
|
269
|
-
events: list[dict[str, Any]], *, window: int = 10, threshold: float = 0.1
|
|
270
|
-
) -> Alert | None:
|
|
271
|
-
ends = [e for e in events if e.get("event") == "round_end"]
|
|
272
|
-
if len(ends) < window:
|
|
273
|
-
return None
|
|
274
|
-
recent_round_nums = [e.get("round_num") for e in ends[-window:]]
|
|
275
|
-
fails = sum(
|
|
276
|
-
1
|
|
277
|
-
for e in events
|
|
278
|
-
if e.get("event") == "smoke_check_failed" and e.get("round_num") in recent_round_nums
|
|
279
|
-
)
|
|
280
|
-
rate = fails / window
|
|
281
|
-
if rate < threshold:
|
|
282
|
-
return None
|
|
283
|
-
return _alert(
|
|
284
|
-
"smoke_fail_rate",
|
|
285
|
-
"warning",
|
|
286
|
-
f"{fails}/{window} recent rounds had smoke_check_failed",
|
|
287
|
-
{"rate": rate, "threshold": threshold, "hint": "Inspect events.jsonl for failure reasons"},
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
|
|
291
267
|
def detect_oauth_fail(
|
|
292
268
|
events: list[dict[str, Any]],
|
|
293
269
|
log_tails: dict[int, str],
|
|
@@ -603,7 +579,6 @@ def run_all_detectors(
|
|
|
603
579
|
),
|
|
604
580
|
detect_disk_critical(metrics, threshold_pct=disk_critical_pct),
|
|
605
581
|
detect_mem_pressure(metrics, threshold_mb=mem_avail_min_mb),
|
|
606
|
-
detect_smoke_fail_rate(events),
|
|
607
582
|
detect_oauth_fail(events, log_tails, patterns=compiled_auth_pats, hint=auth_fail_hint),
|
|
608
583
|
detect_network_fail(events, log_tails),
|
|
609
584
|
detect_rate_limit_active(events, now=now.timestamp()),
|
|
@@ -369,7 +369,7 @@ def run_one_round(cfg: Config, *, phase_override: str | None = None) -> RoundRes
|
|
|
369
369
|
file=sys.stderr,
|
|
370
370
|
)
|
|
371
371
|
events.emit(log_dir, "smoke_check_failed", reason=f"{r.name}: {r.reason}")
|
|
372
|
-
sys.exit(
|
|
372
|
+
sys.exit(api.PERMANENT_CONFIG_EXIT)
|
|
373
373
|
|
|
374
374
|
# Concurrency lock (per-project)
|
|
375
375
|
lock_path = log_dir / "agent-runner.lock"
|
|
@@ -521,6 +521,7 @@ def _run_one_round_inner(cfg: Config, *, phase_override: str | None = None) -> R
|
|
|
521
521
|
round_num=round_num,
|
|
522
522
|
phase=phase,
|
|
523
523
|
idempotency_s=cfg.vcs.stash_idempotency_s,
|
|
524
|
+
log_dir=cfg.runtime.log_dir,
|
|
524
525
|
)
|
|
525
526
|
if ref is not None:
|
|
526
527
|
context_store.write_orphan_state(
|
|
@@ -546,7 +547,9 @@ def _run_one_round_inner(cfg: Config, *, phase_override: str | None = None) -> R
|
|
|
546
547
|
# Leave tree dirty for next round; dirty_detected already emitted
|
|
547
548
|
pass
|
|
548
549
|
elif action == "auto_commit":
|
|
549
|
-
err = vcs_state.try_auto_commit(
|
|
550
|
+
err = vcs_state.try_auto_commit(
|
|
551
|
+
cfg.runtime.work_dir, round_num, phase, log_dir=cfg.runtime.log_dir
|
|
552
|
+
)
|
|
550
553
|
if err is not None:
|
|
551
554
|
events.emit(
|
|
552
555
|
log_dir,
|
|
@@ -223,12 +223,18 @@ def stash_orphan(
|
|
|
223
223
|
round_num: int,
|
|
224
224
|
phase: str | None,
|
|
225
225
|
idempotency_s: int = 5,
|
|
226
|
+
log_dir: Path | None = None,
|
|
226
227
|
) -> StashRef | None:
|
|
227
228
|
"""Stash dirty tree as ORPHAN entry, SHA-locked.
|
|
228
229
|
|
|
229
230
|
Returns existing ref if a matching ORPHAN was created within ``idempotency_s``
|
|
230
231
|
(R820 lesson — same-second multiple calls would otherwise pile up duplicate
|
|
231
232
|
stashes). Returns None if tree is clean.
|
|
233
|
+
|
|
234
|
+
``log_dir`` (when under ``repo``) is excluded from the stash so ``git stash
|
|
235
|
+
push -u`` does not sweep the runner's own bookkeeping (lock / pid / event
|
|
236
|
+
logs) out of the work tree. If only ``log_dir`` churned, nothing is stashed
|
|
237
|
+
and this returns None.
|
|
232
238
|
"""
|
|
233
239
|
if not detect_dirty_files(repo):
|
|
234
240
|
return None
|
|
@@ -238,7 +244,8 @@ def stash_orphan(
|
|
|
238
244
|
ts = time.strftime("%Y-%m-%dT%H:%M:%S")
|
|
239
245
|
phase_part = f" phase={phase}" if phase else ""
|
|
240
246
|
msg = f"ORPHAN R{round_num}{phase_part} ts={ts}"
|
|
241
|
-
|
|
247
|
+
exclude = _log_dir_exclude_pathspec(repo, log_dir)
|
|
248
|
+
push = _git(repo, "stash", "push", "-u", "-m", msg, *exclude, timeout=30)
|
|
242
249
|
if push.returncode != 0:
|
|
243
250
|
return None
|
|
244
251
|
listing = _git(repo, "stash", "list", "-1", "--format=%H %s")
|
|
@@ -287,22 +294,63 @@ def pop_stash(repo: Path, sha: str) -> bool:
|
|
|
287
294
|
return _git(repo, "stash", "pop", sel).returncode == 0
|
|
288
295
|
|
|
289
296
|
|
|
290
|
-
def
|
|
297
|
+
def _log_dir_exclude_pathspec(root: Path, log_dir: Path | None) -> list[str]:
|
|
298
|
+
"""Git pathspec args excluding the runner's own ``log_dir`` from an add/stash,
|
|
299
|
+
applied only when it lives inside the work tree AND is not already gitignored.
|
|
300
|
+
Empty otherwise: an outside or gitignored log_dir is skipped by git's own
|
|
301
|
+
handling, and folding an ignored path into a stash pathspec breaks untracked
|
|
302
|
+
capture (git refuses the ignored path).
|
|
303
|
+
|
|
304
|
+
Keeps supervisor bookkeeping (lock / pid / event logs) out of the agent's
|
|
305
|
+
dirty-tree handling: without it a zero-work round's log churn lands in a
|
|
306
|
+
commit (``git_head`` lies) or a ``git stash push -u`` (the logs vanish).
|
|
307
|
+
"""
|
|
308
|
+
if log_dir is None:
|
|
309
|
+
return []
|
|
310
|
+
try:
|
|
311
|
+
rel = log_dir.resolve().relative_to(root.resolve()).as_posix()
|
|
312
|
+
except ValueError:
|
|
313
|
+
return [] # log_dir outside work_dir → nothing to exclude
|
|
314
|
+
if _git(root, "check-ignore", "-q", rel).returncode == 0:
|
|
315
|
+
return [] # already gitignored → git skips it; pathspec would misfire
|
|
316
|
+
return ["--", f":(exclude){rel}"]
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def try_auto_commit(
|
|
320
|
+
work_dir: Path,
|
|
321
|
+
round_num: int,
|
|
322
|
+
phase: str | None,
|
|
323
|
+
*,
|
|
324
|
+
log_dir: Path | None = None,
|
|
325
|
+
) -> str | None:
|
|
291
326
|
"""Auto-commit dirty tree with hardcoded subject. Return None on success, error on failure.
|
|
292
327
|
|
|
293
328
|
Subject: ``agent-runner auto-commit: R<N> <phase>`` (phase part omitted if None).
|
|
294
329
|
Uses ``git -c commit.gpgsign=false`` to skip GPG; honors pre-commit hooks
|
|
295
330
|
(no ``--no-verify``). DOES NOT push — local commit only.
|
|
296
331
|
|
|
332
|
+
``log_dir`` (when under ``work_dir``) is excluded from the add so a zero-work
|
|
333
|
+
round that only churned the runner's own bookkeeping (lock/pid/event logs)
|
|
334
|
+
does not advance ``git_head``. The agent's work and ``.evolving/`` live
|
|
335
|
+
outside ``log_dir`` and are still committed. If nothing remains staged after
|
|
336
|
+
the exclusion, this is a no-op (returns None, leaves HEAD untouched).
|
|
337
|
+
|
|
297
338
|
Callers (runner.py) emit ``dirty_commit_failed`` event when return value is not None.
|
|
298
339
|
"""
|
|
299
340
|
phase_part = f" {phase}" if phase else ""
|
|
300
341
|
subject = f"agent-runner auto-commit: R{round_num}{phase_part}"
|
|
301
342
|
|
|
302
|
-
|
|
343
|
+
exclude = _log_dir_exclude_pathspec(work_dir, log_dir)
|
|
344
|
+
add_result = _git(work_dir, "add", "-A", *exclude)
|
|
303
345
|
if add_result.returncode != 0:
|
|
304
346
|
return (add_result.stderr or "git add failed")[:200]
|
|
305
347
|
|
|
348
|
+
# Only the exclusion can leave nothing staged (a zero-work round that churned
|
|
349
|
+
# only log_dir); without it the tree was dirty so there is always something to
|
|
350
|
+
# commit. Skip the extra git call on the common (no-exclusion) path.
|
|
351
|
+
if exclude and _git(work_dir, "diff", "--cached", "--quiet").returncode == 0:
|
|
352
|
+
return None
|
|
353
|
+
|
|
306
354
|
commit_result = _git(
|
|
307
355
|
work_dir,
|
|
308
356
|
"commit",
|
|
@@ -34,7 +34,7 @@ All three accept the same drill-down flags: `--round N`, `--log`, `--events N`,
|
|
|
34
34
|
|
|
35
35
|
## Defenses-as-data
|
|
36
36
|
|
|
37
|
-
`agent_runner.defenses.catalog(cfg)` returns
|
|
37
|
+
`agent_runner.defenses.catalog(cfg)` returns 12 structured `Defense` entries.
|
|
38
38
|
Each entry carries:
|
|
39
39
|
|
|
40
40
|
- `name` — stable identifier
|
|
@@ -59,13 +59,14 @@ surfacing everywhere.
|
|
|
59
59
|
| `sha_locked_stash` | §9 IMMUTABLE — batch drop by index breaks under concurrent stash | `tests/invariants/test_stash_uses_sha_not_index.py` |
|
|
60
60
|
| `set_diff_classification` | R2110 — rotation-only diff via +-line scan misclassifies | `—` |
|
|
61
61
|
| `critical_envs_injection` | Env injection via [agent.env] block — preset-supplied per CLI (e.g. DISABLE_AUTOUPDATER for claude prevents mid-loop self-updates) | `—` |
|
|
62
|
-
| `startup_smoke_check` | R721 + #446 — _common.md frontmatter caused 4h/123-round silent burn |
|
|
62
|
+
| `startup_smoke_check` | R721 + #446 — _common.md frontmatter caused 4h/123-round silent burn; now halts serve (config_broken) instead of respawning a broken config | `tests/unit/test_serve_config_broken.py` |
|
|
63
|
+
| `crash_loop_breaker` | Run 6 — crashing agent respawned ~100 empty rounds at a fixed 2x delay | `tests/unit/test_serve_crash_loop.py` |
|
|
63
64
|
| `flock_concurrency` | Architectural — prevent concurrent supervisors corrupting state | `—` |
|
|
64
65
|
| `atomic_state_writes` | Data integrity — crashes never leave half-written state files | `tests/invariants/test_atomic_write_enforced.py` |
|
|
65
66
|
| `event_kind_registry` | Prevent events.emit() typos / unregistered kinds slipping past CI | `tests/invariants/test_event_kind_registry.py` |
|
|
66
67
|
<!-- /gen:defenses-table -->
|
|
67
68
|
|
|
68
|
-
## Monitor:
|
|
69
|
+
## Monitor: 11 detectors
|
|
69
70
|
|
|
70
71
|
Three categories by `auto_action`:
|
|
71
72
|
|
|
@@ -88,7 +89,6 @@ API quota / writing to a near-full disk).
|
|
|
88
89
|
- `oauth_fail` — **auto-stop**
|
|
89
90
|
- `orphan_chain`
|
|
90
91
|
- `rate_limit_active`
|
|
91
|
-
- `smoke_fail_rate`
|
|
92
92
|
- `supervisor_stale`
|
|
93
93
|
- `timeout_rate`
|
|
94
94
|
<!-- /gen:detector-list -->
|
|
@@ -151,6 +151,8 @@ hook (vs ALL pre-round hooks), use `[plugins] disable = ["that_entry_point_name"
|
|
|
151
151
|
- `agent_spawn`
|
|
152
152
|
- `agent_usage_recorded`
|
|
153
153
|
- `anomaly_repetitive_tool`
|
|
154
|
+
- `config_broken`
|
|
155
|
+
- `crash_loop`
|
|
154
156
|
- `dirty_commit_failed`
|
|
155
157
|
- `dirty_detected`
|
|
156
158
|
- `fresh_eyes_round_triggered`
|
|
@@ -192,4 +194,4 @@ hook (vs ALL pre-round hooks), use `[plugins] disable = ["that_entry_point_name"
|
|
|
192
194
|
|
|
193
195
|
三层架构:Round(一轮 agent)/ Loop(serve 薄壳)/ Witness(monitor)。
|
|
194
196
|
三视角对称:peek(快照)/ watch(快照循环)/ monitor(异常检测),共用下钻参数。
|
|
195
|
-
防御以结构化目录形式存在(
|
|
197
|
+
防御以结构化目录形式存在(12 条),每条防御自描述「防的是哪条历史教训、被哪个 invariant test 守、当前状态」。
|
|
@@ -145,7 +145,7 @@ agent-runner events --kind transient_error_backoff_capped --tail
|
|
|
145
145
|
|
|
146
146
|
### `agent-runner monitor [--host SSH-ALIAS] [--interval N] [--mode MODE] [--port PORT] [--json]`
|
|
147
147
|
|
|
148
|
-
Anomaly-detection daemon. Runs the
|
|
148
|
+
Anomaly-detection daemon. Runs the 11 detectors against the live state on every
|
|
149
149
|
poll. Without `--host`, watches local logs at default 30s interval. With
|
|
150
150
|
`--host`, watches a remote agent-runner over plain ssh at default 60s interval.
|
|
151
151
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Migrating to 0.1.42
|
|
2
|
+
|
|
3
|
+
## TL;DR
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install --upgrade cli-agent-runner==0.1.42
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Two new always-on serve defenses (`crash_loop`, `config_broken`), one removed
|
|
10
|
+
(inert) monitor alert, and an `auto_commit` scope fix. No config-schema change;
|
|
11
|
+
no action required for a healthy deployment.
|
|
12
|
+
|
|
13
|
+
## Behavior change: serve now STOPS on two harmful states (instead of respawning)
|
|
14
|
+
|
|
15
|
+
Both fire in the always-on path (no `monitor` process required):
|
|
16
|
+
|
|
17
|
+
- **`config_broken`** — if the startup battery fails (broken config: missing
|
|
18
|
+
prompt, non-git `work_dir`, agent CLI not on PATH, sub-500-byte prompt, …),
|
|
19
|
+
the round exits with the no-retry code `78` and serve emits `config_broken`
|
|
20
|
+
and stops. Previously the round exited `1` and serve respawned the broken
|
|
21
|
+
config forever. The specific cause is in the round's `smoke_check_failed`
|
|
22
|
+
event. Fix the config and restart.
|
|
23
|
+
- **`crash_loop`** — after **5 consecutive** *unknown short crashes* (a round
|
|
24
|
+
that exits non-zero in under 60s with no classified transient error), serve
|
|
25
|
+
emits `crash_loop` (carrying `consecutive`, `exit_code`, and a redacted reason
|
|
26
|
+
tail) and stops, escalating the restart delay along the way. Previously such a
|
|
27
|
+
round respawned forever at a fixed 2× delay (the Run 6 ~100-empty-rounds
|
|
28
|
+
incident).
|
|
29
|
+
|
|
30
|
+
Recoverable-slow failures are unaffected: rate-limit / 5h-quota / 5xx / timeout
|
|
31
|
+
are classified as transient errors and still ride the existing
|
|
32
|
+
`transient_error_*` backoff (`rate_limit_account` waits the server's exact
|
|
33
|
+
`resetsAt`). They never count toward the crash-loop breaker.
|
|
34
|
+
|
|
35
|
+
To watch for these: `grep -E '"event": "(crash_loop|config_broken)"' events-*.jsonl`.
|
|
36
|
+
|
|
37
|
+
## Removed: the `smoke_fail_rate` monitor alert
|
|
38
|
+
|
|
39
|
+
It could never fire (it matched on `round_num`, which `smoke_check_failed` never
|
|
40
|
+
carried) and is now superseded by the always-on `config_broken` stop. If you
|
|
41
|
+
subscribed to `smoke_fail_rate` (it never emitted), switch to `config_broken`.
|
|
42
|
+
`monitor` now reports **11** detectors.
|
|
43
|
+
|
|
44
|
+
## Fixed: dirty-tree handling no longer sweeps the runner's `log_dir`
|
|
45
|
+
|
|
46
|
+
When `log_dir` is inside `work_dir`, **both** VCS dirty-actions now exclude the
|
|
47
|
+
supervisor's own bookkeeping (lock, pid, `events-*.jsonl`, round logs):
|
|
48
|
+
|
|
49
|
+
- `dirty_action = "auto_commit"` excludes it from the commit — previously the
|
|
50
|
+
per-round churn produced a non-empty commit even on a zero-work round,
|
|
51
|
+
advancing `git_head` and making the progress signal lie.
|
|
52
|
+
- `dirty_action = "stash"` (the default) excludes it from `git stash push -u` —
|
|
53
|
+
previously the logs (and the events file being written) were swept into the
|
|
54
|
+
stash and vanished from the work tree each round.
|
|
55
|
+
|
|
56
|
+
Your agent's work and the `.evolving/` ledger live outside `log_dir` and are
|
|
57
|
+
unaffected. Default deployments (`log_dir` at `~/.agent-runner/{project}/logs`,
|
|
58
|
+
outside `work_dir`) were never affected by either.
|