cli-agent-runner 0.2.22__tar.gz → 0.2.23__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.22 → cli_agent_runner-0.2.23}/.vulture-whitelist.py +17 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/CHANGELOG.md +15 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/PKG-INFO +2 -2
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/README.md +1 -1
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_emit/__init__.py +2 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_emit/serve.py +20 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_monitor_state.py +26 -6
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/agent_runtime.py +17 -10
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/api.py +2 -10
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/__init__.py +2 -0
- cli_agent_runner-0.2.23/agent_runner/cli/doctor_cmd.py +77 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/serve_cmd.py +11 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/config/models.py +11 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/config/parsers.py +3 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/events.py +1 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/lifecycle.py +27 -12
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/phase_select.py +63 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/presets/claude.toml +1 -1
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/presets/codewhale.toml +3 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/presets/pi.toml +3 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/runner.py +5 -6
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/startup_check.py +93 -83
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/architecture.md +1 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/commands.md +13 -2
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/configuration.md +16 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/memory-efficiency.md +48 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/recipes/codewhale.md +3 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/runbook.md +1 -1
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/contract/test_public_api_surface.py +1 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_serve_loop.py +63 -2
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_architecture.py +11 -2
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_import_footprint.py +5 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime_grace.py +102 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_config.py +28 -0
- cli_agent_runner-0.2.23/tests/unit/test_doctor_cmd.py +86 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_events.py +4 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_lifecycle.py +31 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_assembly.py +29 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_phase_select.py +115 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_startup_check.py +63 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/.codecov.yml +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/.github/dependabot.yml +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/.gitignore +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/BACKLOG.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/LICENSE +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/README.zh.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/SECURITY.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_emit/agent.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_emit/memory.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_emit/rounds.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_plugin_scan.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_round_outcome.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_serve_policy.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/_serve_cgroup.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/_serve_round.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/config/__init__.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/config/loader.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/config/validators.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/host_health.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/build.sh +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/deploy/Dockerfile +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/deploy/docker-compose.yml +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/README.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/events.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/migrations/0.2.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/plugins.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/recipes/container-pi.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/recipes/deploy-all-in-one.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/docs/thesis.md +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/pyproject.toml +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/__init__.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/_clock.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/conftest.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_auto_stop_e2e.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_spawn_round_mem_floor.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_spawn_round_wedged.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_bdd_test_conventions.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_container_stop_bound.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_no_price_logic.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_no_vacuous_invariants.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_no_version_refs.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_round_alloc_growth.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_timeout_budget_invariant.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime_container.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime_reap.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_cgroup_probe.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_config_nested_strictness.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_example_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_exec_prefix.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_git_timeout.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_host_health.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_http_progress_epoch.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_metrics_cgroup_usage.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_metrics_swap.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_dedup_rearm.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_loop.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_on_alert.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_plugin_scan_parity.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_primary_prompt_file_home.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_round_outcome.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_round_outcome_agent_axis.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_policy.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_round.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_round_oom_killed.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_round_terminate.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_throttle_epoch_bound.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.2.22 → cli_agent_runner-0.2.23}/tests/unit/test_vcs_state.py +0 -0
|
@@ -128,6 +128,11 @@ agent
|
|
|
128
128
|
since_round
|
|
129
129
|
phase
|
|
130
130
|
|
|
131
|
+
# agent_runner.cli.doctor_cmd.DoctorReport
|
|
132
|
+
checks
|
|
133
|
+
overlaps
|
|
134
|
+
plan
|
|
135
|
+
|
|
131
136
|
# agent_runner.config.models.AgentConfig
|
|
132
137
|
command
|
|
133
138
|
prompt_arg_template
|
|
@@ -135,6 +140,7 @@ name
|
|
|
135
140
|
env
|
|
136
141
|
prompt_delivery
|
|
137
142
|
exec_prefix
|
|
143
|
+
terminal_marker
|
|
138
144
|
|
|
139
145
|
# agent_runner.config.models.Config
|
|
140
146
|
agent
|
|
@@ -296,6 +302,12 @@ resume_timezone
|
|
|
296
302
|
skipped
|
|
297
303
|
active_window
|
|
298
304
|
|
|
305
|
+
# agent_runner.phase_select.WindowOverlap
|
|
306
|
+
phase_a
|
|
307
|
+
phase_b
|
|
308
|
+
window_a
|
|
309
|
+
window_b
|
|
310
|
+
|
|
299
311
|
# agent_runner.round_log.PruneOutcome
|
|
300
312
|
deleted
|
|
301
313
|
deferred
|
|
@@ -319,6 +331,11 @@ reason
|
|
|
319
331
|
how_to_fix
|
|
320
332
|
permanent
|
|
321
333
|
|
|
334
|
+
# agent_runner.startup_check.CheckSpec
|
|
335
|
+
kind
|
|
336
|
+
scope
|
|
337
|
+
run
|
|
338
|
+
|
|
322
339
|
# agent_runner.vcs_state.StashRef
|
|
323
340
|
sha
|
|
324
341
|
message
|
|
@@ -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.2.23] - 2026-09-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `[agent] terminal_marker` — per-preset grace-kill result marker (default = Claude's token; pi/codewhale set their own; kimi/aider have no marker and run to the wall-clock ceiling).
|
|
12
|
+
- `doctor` — read-only pre-flight (never launches the agent): startup checks, phase-window overlap warnings, and an N-round phase-plan preview.
|
|
13
|
+
- `phase_window_overlap` event — emitted at serve boot when two agent-overriding phases' windows collide in the same timezone.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Startup-check dispatch and the catalog's kind list now derive from a single descriptor table (no behavior change).
|
|
17
|
+
- The lock-holder pid create_time verify now shares one `lifecycle` helper with `serve.pid` (no behavior change).
|
|
18
|
+
- Default `claude` preset model updated `claude-opus-4-7` → `claude-opus-5`.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- `peek` no longer prints spurious "cannot coerce None" warnings on a freshly-initialized project; absent metric samples fall back to defaults silently (a genuinely malformed value still warns).
|
|
22
|
+
|
|
8
23
|
## [0.2.22] - 2026-09-11
|
|
9
24
|
|
|
10
25
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.23
|
|
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
|
|
@@ -82,7 +82,7 @@ agent-runner monitor # live anomaly detection
|
|
|
82
82
|
|
|
83
83
|
Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
|
|
84
84
|
|
|
85
|
-
##
|
|
85
|
+
## 17 verbs
|
|
86
86
|
|
|
87
87
|
| Lifecycle | Observation |
|
|
88
88
|
|---|---|
|
|
@@ -57,6 +57,7 @@ from agent_runner._emit.serve import ( # noqa: F401 — public re-export
|
|
|
57
57
|
emit_max_rounds_reached,
|
|
58
58
|
emit_mem_loop,
|
|
59
59
|
emit_mem_loop_persistent,
|
|
60
|
+
emit_phase_window_overlap,
|
|
60
61
|
emit_rate_limit_stop,
|
|
61
62
|
emit_schedule_paused,
|
|
62
63
|
emit_schedule_phase_skipped,
|
|
@@ -78,6 +79,7 @@ __all__ = [
|
|
|
78
79
|
"emit_mem_loop",
|
|
79
80
|
"emit_mem_loop_persistent",
|
|
80
81
|
"emit_mem_pressure_deferred_to_cgroup",
|
|
82
|
+
"emit_phase_window_overlap",
|
|
81
83
|
"emit_rate_limit_stop",
|
|
82
84
|
"emit_round_cgroup_memory",
|
|
83
85
|
"emit_round_container_orphan_risk",
|
|
@@ -191,3 +191,23 @@ def emit_schedule_resumed(log_dir: Path, *, paused_for_s: int) -> None:
|
|
|
191
191
|
from agent_runner.events import SCHEDULE_RESUMED, emit
|
|
192
192
|
|
|
193
193
|
emit(log_dir, SCHEDULE_RESUMED, paused_for_s=paused_for_s)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def emit_phase_window_overlap(
|
|
197
|
+
log_dir: Path, *, phase_a: str, phase_b: str, window_a: str, window_b: str
|
|
198
|
+
) -> None:
|
|
199
|
+
"""Emit phase_window_overlap: two agent-overriding phases' own run_windows
|
|
200
|
+
collide (a rotation footgun; a later release promotes this exact detection
|
|
201
|
+
to a hard error). Called once at serve boot -- the collision is a property of the
|
|
202
|
+
static config, not of any one round, so (like emit_max_rounds_reached's
|
|
203
|
+
cgroup-probe sibling) this must not be called from the per-round path."""
|
|
204
|
+
from agent_runner.events import PHASE_WINDOW_OVERLAP, emit
|
|
205
|
+
|
|
206
|
+
emit(
|
|
207
|
+
log_dir,
|
|
208
|
+
PHASE_WINDOW_OVERLAP,
|
|
209
|
+
phase_a=phase_a,
|
|
210
|
+
phase_b=phase_b,
|
|
211
|
+
window_a=window_a,
|
|
212
|
+
window_b=window_b,
|
|
213
|
+
)
|
|
@@ -154,21 +154,41 @@ def _latest_metric_dict(metrics: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
154
154
|
return metrics[-1] if metrics else {}
|
|
155
155
|
|
|
156
156
|
|
|
157
|
+
def _metric_int(latest: dict[str, Any], key: str, default: int) -> int:
|
|
158
|
+
"""``_coerce_int`` for a metric field, silent on absence.
|
|
159
|
+
|
|
160
|
+
A fresh project has no metric events yet, so ``latest`` is ``{}`` and
|
|
161
|
+
every field reads as None -- "no sample yet", not malformed data. Only a
|
|
162
|
+
present-but-non-numeric value should still warn (delegated to
|
|
163
|
+
``_coerce_int``, which owns that contract)."""
|
|
164
|
+
from agent_runner._throttle import _coerce_int
|
|
165
|
+
|
|
166
|
+
value = latest.get(key)
|
|
167
|
+
return default if value is None else _coerce_int(value, default)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _metric_float(latest: dict[str, Any], key: str, default: float) -> float:
|
|
171
|
+
"""Float sibling of ``_metric_int`` -- see its docstring."""
|
|
172
|
+
from agent_runner._throttle import _coerce_float
|
|
173
|
+
|
|
174
|
+
value = latest.get(key)
|
|
175
|
+
return default if value is None else _coerce_float(value, default)
|
|
176
|
+
|
|
177
|
+
|
|
157
178
|
def assemble_project_state(source: LocalSource, *, project: str) -> ProjectState:
|
|
158
179
|
metrics = parse_events_from_jsonl_files(source.metrics_files())
|
|
159
180
|
status = read_json(source.status_path()) or {}
|
|
160
181
|
orphan = read_json(source.orphan_path())
|
|
161
182
|
latest = _latest_metric_dict(metrics)
|
|
162
|
-
from agent_runner._throttle import _coerce_float, _coerce_int
|
|
163
183
|
|
|
164
184
|
system = SystemMetrics(
|
|
165
|
-
mem_total_mb=
|
|
166
|
-
mem_available_mb=
|
|
167
|
-
disk_used_pct=
|
|
168
|
-
disk_free_gb=
|
|
185
|
+
mem_total_mb=_metric_int(latest, "mem_total_mb", 0),
|
|
186
|
+
mem_available_mb=_metric_int(latest, "mem_available_mb", 0),
|
|
187
|
+
disk_used_pct=_metric_float(latest, "disk_used_pct", 0.0),
|
|
188
|
+
disk_free_gb=_metric_float(latest, "disk_free_gb", 0.0),
|
|
169
189
|
load_1m=latest.get("load_1m"),
|
|
170
190
|
cpu_pct=latest.get("cpu_pct"),
|
|
171
|
-
agent_process_count=
|
|
191
|
+
agent_process_count=_metric_int(latest, "agent_process_count", 0),
|
|
172
192
|
)
|
|
173
193
|
return ProjectState(
|
|
174
194
|
project=project,
|
|
@@ -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.23'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 23)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -247,11 +247,6 @@ def _live_children(
|
|
|
247
247
|
return live, ignored
|
|
248
248
|
|
|
249
249
|
|
|
250
|
-
# Exact compact bytes — matches claude CLI's no-whitespace JSONL output.
|
|
251
|
-
# A future CLI variant emitting `{"type": "result", ...}` (with space) would
|
|
252
|
-
# bypass this scan; revisit if that happens.
|
|
253
|
-
_RESULT_MARKER = b'"type":"result"'
|
|
254
|
-
|
|
255
250
|
# Decoupled from the 0.2s poll tick: the marker scan only needs to notice the
|
|
256
251
|
# result within max_grace_after_result_s (seconds, integer), so scanning more
|
|
257
252
|
# often than this buys nothing but re-read cost on a growing round log.
|
|
@@ -573,6 +568,7 @@ def run(
|
|
|
573
568
|
log_path: Path,
|
|
574
569
|
env_extra: dict[str, str],
|
|
575
570
|
max_grace_after_result_s: int = 0,
|
|
571
|
+
terminal_marker: str = '"type":"result"', # mirrors config.DEFAULT_TERMINAL_MARKER
|
|
576
572
|
progress_callback: Callable[[dict], None] | None = None,
|
|
577
573
|
progress_interval_s: int = 0,
|
|
578
574
|
on_grace_extended: Callable[[list[dict], list[dict]], None] | None = None,
|
|
@@ -588,13 +584,19 @@ def run(
|
|
|
588
584
|
already-absolute cfg.runtime.work_dir. CLIs with no --cwd flag of their
|
|
589
585
|
own (e.g. pi) depend on this.
|
|
590
586
|
|
|
591
|
-
max_grace_after_result_s: when > 0, start a countdown after
|
|
592
|
-
|
|
587
|
+
max_grace_after_result_s: when > 0, start a countdown after
|
|
588
|
+
``terminal_marker`` is first detected in the log. After it elapses, reap the
|
|
593
589
|
process group only if the agent has no live worker processes left (a
|
|
594
590
|
genuine hang). If a worker is still running (e.g. a backgrounded build),
|
|
595
591
|
do not reap — invoke ``on_grace_extended`` once and keep waiting until the
|
|
596
592
|
round finishes or hits the wall-clock ``timeout_s`` ceiling. 0 = disabled.
|
|
597
593
|
|
|
594
|
+
terminal_marker: exact compact byte substring the grace-kill scan looks
|
|
595
|
+
for (default matches claude CLI's no-whitespace JSONL ``{"type":"result",
|
|
596
|
+
...}`` output). An empty string disables the marker scan outright, even
|
|
597
|
+
with max_grace_after_result_s > 0 (honest opt-out, not an
|
|
598
|
+
empty-substring-matches-everything trap).
|
|
599
|
+
|
|
598
600
|
progress_callback: when not None and progress_interval_s > 0, called every
|
|
599
601
|
progress_interval_s seconds with a dict of log stats (log_size_kb,
|
|
600
602
|
last_write_age_s, wall_age_s). Keeps agent_runtime event-free; callers
|
|
@@ -619,6 +621,7 @@ def run(
|
|
|
619
621
|
attempt where covered; it just isn't reported).
|
|
620
622
|
"""
|
|
621
623
|
stdin_mode = prompt_delivery == "stdin"
|
|
624
|
+
marker_bytes = terminal_marker.encode("utf-8")
|
|
622
625
|
# Raising work (mkdir can fail on disk-full/perms/removed log_dir) must
|
|
623
626
|
# happen BEFORE the container-detection block below creates the
|
|
624
627
|
# host-private cidfile tempdir -- otherwise a raise here would skip the
|
|
@@ -749,7 +752,7 @@ def run(
|
|
|
749
752
|
exit_code=exit_code, duration_s=duration, timed_out=True, pid=proc.pid
|
|
750
753
|
)
|
|
751
754
|
# Grace kill: result emitted but subprocess still running.
|
|
752
|
-
if max_grace_after_result_s > 0:
|
|
755
|
+
if max_grace_after_result_s > 0 and marker_bytes:
|
|
753
756
|
if result_seen_at is None and now - last_result_scan >= _RESULT_SCAN_INTERVAL_S:
|
|
754
757
|
last_result_scan = now
|
|
755
758
|
try:
|
|
@@ -758,12 +761,16 @@ def run(
|
|
|
758
761
|
chunk = f.read()
|
|
759
762
|
result_scan_offset += len(chunk)
|
|
760
763
|
haystack = result_scan_carry + chunk
|
|
761
|
-
if
|
|
764
|
+
if marker_bytes in haystack:
|
|
762
765
|
result_seen_at = now
|
|
763
766
|
else:
|
|
764
767
|
# Keep the tail so a marker split across two read
|
|
765
768
|
# chunks (this scan vs. the next) still re-forms.
|
|
766
|
-
|
|
769
|
+
# A 1-byte marker can't be split, so carry nothing
|
|
770
|
+
# -- haystack[-0:] is the WHOLE buffer, not empty,
|
|
771
|
+
# so the zero case needs its own branch.
|
|
772
|
+
_carry = len(marker_bytes) - 1
|
|
773
|
+
result_scan_carry = haystack[-_carry:] if _carry else b""
|
|
767
774
|
except OSError:
|
|
768
775
|
pass # log not flushed yet; retry next interval
|
|
769
776
|
if result_seen_at is not None and now - result_seen_at > max_grace_after_result_s:
|
|
@@ -357,8 +357,6 @@ def _round_holder_pid(log_dir: Path) -> int | None:
|
|
|
357
357
|
alive. This is the ONLY way ``kill()`` can reach an in-flight round: the
|
|
358
358
|
round is ``start_new_session=True`` (its own session, its own pgid), so it
|
|
359
359
|
sits outside whatever process group serve itself belongs to."""
|
|
360
|
-
import psutil
|
|
361
|
-
|
|
362
360
|
from agent_runner.context_store import read_json
|
|
363
361
|
|
|
364
362
|
data = read_json(log_dir / "agent-runner.lock.holder")
|
|
@@ -370,14 +368,7 @@ def _round_holder_pid(log_dir: Path) -> int | None:
|
|
|
370
368
|
if not pid_alive(pid):
|
|
371
369
|
return None
|
|
372
370
|
recorded = data.get("create_time")
|
|
373
|
-
if recorded
|
|
374
|
-
return pid # legacy sidecar without a token: best-effort, mirrors serve.pid's read
|
|
375
|
-
try:
|
|
376
|
-
if abs(psutil.Process(pid).create_time() - recorded) < 1.0:
|
|
377
|
-
return pid
|
|
378
|
-
except psutil.Error:
|
|
379
|
-
return None
|
|
380
|
-
return None # recorded create_time != live process: a recycled pid, do not signal it
|
|
371
|
+
return pid if lifecycle.create_time_matches(pid, recorded) else None
|
|
381
372
|
|
|
382
373
|
|
|
383
374
|
def _terminate_round_pid(pid: int) -> None:
|
|
@@ -854,6 +845,7 @@ from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-ex
|
|
|
854
845
|
emit_mem_loop,
|
|
855
846
|
emit_mem_loop_persistent,
|
|
856
847
|
emit_mem_pressure_deferred_to_cgroup,
|
|
848
|
+
emit_phase_window_overlap,
|
|
857
849
|
emit_rate_limit_stop,
|
|
858
850
|
emit_round_cgroup_memory,
|
|
859
851
|
emit_round_container_orphan_risk,
|
|
@@ -17,6 +17,7 @@ from pathlib import Path
|
|
|
17
17
|
|
|
18
18
|
from agent_runner import __version__
|
|
19
19
|
from agent_runner.cli import (
|
|
20
|
+
doctor_cmd,
|
|
20
21
|
events_cmd,
|
|
21
22
|
init_cmd,
|
|
22
23
|
install_cmd,
|
|
@@ -71,6 +72,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
71
72
|
round_cmd.add_parser(sub, parent)
|
|
72
73
|
upgrade_cmd.add_parser(sub, parent)
|
|
73
74
|
migrate_cmd.add_parser(sub, parent)
|
|
75
|
+
doctor_cmd.add_parser(sub, parent)
|
|
74
76
|
|
|
75
77
|
return parser
|
|
76
78
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""doctor subcommand — never launches the configured agent; runs the same
|
|
2
|
+
read-only-ish boot checks serve does (it may create/probe the log dir to
|
|
3
|
+
verify it's writable), then reports window overlaps + phase plan."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from agent_runner import phase_select, startup_check
|
|
10
|
+
from agent_runner.cli.common import cfg_from_args, emit
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class DoctorReport:
|
|
15
|
+
checks: list[startup_check.CheckResult]
|
|
16
|
+
overlaps: list[phase_select.WindowOverlap]
|
|
17
|
+
plan: list[dict]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def add_parser(sub, parent) -> None:
|
|
21
|
+
p = sub.add_parser("doctor", parents=[parent], help="Read-only pre-flight: checks + phase plan")
|
|
22
|
+
p.add_argument("--rounds", type=int, default=3, help="Phase-plan preview depth (default 3)")
|
|
23
|
+
p.set_defaults(func=cmd_doctor)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _plan(cfg, rounds: int) -> list[dict]:
|
|
27
|
+
out = []
|
|
28
|
+
for r in range(1, rounds + 1):
|
|
29
|
+
sel = phase_select.select_phase(cfg, r)
|
|
30
|
+
out.append(
|
|
31
|
+
{
|
|
32
|
+
"round": r,
|
|
33
|
+
"phase": sel.phase,
|
|
34
|
+
"paused": sel.paused,
|
|
35
|
+
"resume_phase": sel.resume_phase,
|
|
36
|
+
"skipped": sel.skipped,
|
|
37
|
+
"active_window": sel.active_window,
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
return out
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def cmd_doctor(args) -> int:
|
|
44
|
+
cfg = cfg_from_args(args)
|
|
45
|
+
report = DoctorReport(
|
|
46
|
+
checks=startup_check.run_battery(cfg),
|
|
47
|
+
overlaps=phase_select.find_phase_window_overlaps(cfg),
|
|
48
|
+
plan=_plan(cfg, args.rounds),
|
|
49
|
+
)
|
|
50
|
+
json_mode = getattr(args, "json", False)
|
|
51
|
+
if json_mode:
|
|
52
|
+
emit(report, json_mode=True)
|
|
53
|
+
else:
|
|
54
|
+
print(_format(report))
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _format(report: DoctorReport) -> str:
|
|
59
|
+
# Hand-formatted rather than common._pretty: that helper repr()s nested
|
|
60
|
+
# lists/dicts, which would dump report.plan's per-round dicts as Python
|
|
61
|
+
# reprs instead of the readable "round N: phase" lines below.
|
|
62
|
+
lines = ["checks:"]
|
|
63
|
+
for c in report.checks:
|
|
64
|
+
mark = "ok" if c.ok else "FAIL"
|
|
65
|
+
detail = ""
|
|
66
|
+
if not c.ok:
|
|
67
|
+
detail = f" — {c.reason}" + (f" (fix: {c.how_to_fix})" if c.how_to_fix else "")
|
|
68
|
+
lines.append(f" [{mark}] {c.name}{detail}")
|
|
69
|
+
if report.overlaps:
|
|
70
|
+
lines.append("window overlaps:")
|
|
71
|
+
for o in report.overlaps:
|
|
72
|
+
lines.append(f" {o.phase_a} ({o.window_a}) vs {o.phase_b} ({o.window_b})")
|
|
73
|
+
lines.append("phase plan:")
|
|
74
|
+
for step in report.plan:
|
|
75
|
+
where = "paused" if step["paused"] else (step["phase"] or "base")
|
|
76
|
+
lines.append(f" round {step['round']}: {where}")
|
|
77
|
+
return "\n".join(lines)
|
|
@@ -35,6 +35,7 @@ from agent_runner.api import (
|
|
|
35
35
|
emit_config_broken,
|
|
36
36
|
emit_fresh_eyes_round_triggered,
|
|
37
37
|
emit_max_rounds_reached,
|
|
38
|
+
emit_phase_window_overlap,
|
|
38
39
|
emit_rate_limit_stop,
|
|
39
40
|
emit_round_logs_prune_deferred,
|
|
40
41
|
emit_round_substrate_after,
|
|
@@ -615,6 +616,16 @@ def cmd(args) -> int:
|
|
|
615
616
|
# Emitted exactly once per serve lifetime -- the per-round defer decision below
|
|
616
617
|
# reuses this bool rather than re-probing/re-emitting every round.
|
|
617
618
|
cgroup_probe_defer = _probe_and_emit_cgroup_defer(log_dir)
|
|
619
|
+
# Static config check -- phase-window collisions can't change mid-lifetime, so
|
|
620
|
+
# (like the cgroup probe above) this runs once at boot, not once per round.
|
|
621
|
+
for ov in phase_select.find_phase_window_overlaps(cfg):
|
|
622
|
+
emit_phase_window_overlap(
|
|
623
|
+
log_dir,
|
|
624
|
+
phase_a=ov.phase_a,
|
|
625
|
+
phase_b=ov.phase_b,
|
|
626
|
+
window_a=ov.window_a,
|
|
627
|
+
window_b=ov.window_b,
|
|
628
|
+
)
|
|
618
629
|
rounds_completed = 0
|
|
619
630
|
# Three independent consecutive-failure counters, one per breaker: b12
|
|
620
631
|
# crash-loop (unknown short crashes), mem-loop (mem-terminated
|
|
@@ -22,6 +22,12 @@ _VALID_DIRTY_ACTIONS: frozenset[str] = frozenset({"stash", "ignore", "auto_commi
|
|
|
22
22
|
_VALID_TRANSIENT_ERROR_ACTIONS: frozenset[str] = frozenset({"back_off", "skip", "stop"})
|
|
23
23
|
_VALID_PROMPT_DELIVERY: frozenset[str] = frozenset({"argv", "stdin"})
|
|
24
24
|
|
|
25
|
+
# claude's own compact JSONL terminal token — the default `[agent] terminal_marker`
|
|
26
|
+
# so existing configs are unaffected. Single source for the field default and the
|
|
27
|
+
# TOML-parse fallback (parsers.py); `agent_runtime.run`'s own kwarg default mirrors
|
|
28
|
+
# this literal but is decoupled to avoid an agent_runtime -> config import.
|
|
29
|
+
DEFAULT_TERMINAL_MARKER = '"type":"result"'
|
|
30
|
+
|
|
25
31
|
|
|
26
32
|
@dataclass(frozen=True)
|
|
27
33
|
class AgentConfig:
|
|
@@ -31,6 +37,11 @@ class AgentConfig:
|
|
|
31
37
|
env: dict[str, str] = field(default_factory=dict)
|
|
32
38
|
prompt_delivery: Literal["argv", "stdin"] = "argv"
|
|
33
39
|
exec_prefix: list[str] = field(default_factory=list)
|
|
40
|
+
terminal_marker: str = DEFAULT_TERMINAL_MARKER
|
|
41
|
+
"""Byte substring the grace-kill scan looks for in the round log to detect
|
|
42
|
+
the agent's terminal record (see ``agent_runtime.run``'s marker scan).
|
|
43
|
+
Defaults to claude's own JSONL token so existing configs are unaffected;
|
|
44
|
+
an empty string opts out of the marker scan entirely (wall-clock-ceiling-only)."""
|
|
34
45
|
|
|
35
46
|
@property
|
|
36
47
|
def binary(self) -> str | None:
|
|
@@ -32,6 +32,7 @@ from agent_runner.config.models import (
|
|
|
32
32
|
_VALID_PROMPT_DELIVERY,
|
|
33
33
|
_VALID_TRANSIENT_ERROR_ACTIONS,
|
|
34
34
|
_VCS_ALLOWED_FIELDS,
|
|
35
|
+
DEFAULT_TERMINAL_MARKER,
|
|
35
36
|
AgentConfig,
|
|
36
37
|
MonitorConfig,
|
|
37
38
|
MonitorHostHealthConfig,
|
|
@@ -109,6 +110,7 @@ def _parse_agent(
|
|
|
109
110
|
exec_prefix = _require_str_list(
|
|
110
111
|
agent_d.get("exec_prefix", []), field=f"{field_prefix} exec_prefix"
|
|
111
112
|
)
|
|
113
|
+
terminal_marker = str(agent_d.get("terminal_marker", DEFAULT_TERMINAL_MARKER))
|
|
112
114
|
return AgentConfig(
|
|
113
115
|
command=command,
|
|
114
116
|
prompt_arg_template=prompt_arg_template,
|
|
@@ -116,6 +118,7 @@ def _parse_agent(
|
|
|
116
118
|
env={str(k): str(v) for k, v in env_d.items()},
|
|
117
119
|
prompt_delivery=prompt_delivery, # type: ignore[arg-type] # narrowed above
|
|
118
120
|
exec_prefix=exec_prefix,
|
|
121
|
+
terminal_marker=terminal_marker,
|
|
119
122
|
)
|
|
120
123
|
|
|
121
124
|
|
|
@@ -60,6 +60,7 @@ ORPHAN_IDEMPOTENT_SKIP = "orphan_idempotent_skip"
|
|
|
60
60
|
ORPHAN_STASH_FAILED = "orphan_stash_failed"
|
|
61
61
|
ORPHAN_STASHED = "orphan_stashed"
|
|
62
62
|
PACKAGE_UPGRADED = "package_upgraded"
|
|
63
|
+
PHASE_WINDOW_OVERLAP = "phase_window_overlap"
|
|
63
64
|
PROMPT_OVERWRITTEN = "prompt_overwritten"
|
|
64
65
|
ROUND_CGROUP_MEMORY = "round_cgroup_memory"
|
|
65
66
|
ROUND_CONTAINER_ORPHAN_RISK = "round_container_orphan_risk"
|
|
@@ -89,6 +89,29 @@ def _valid_pid(value: object) -> bool:
|
|
|
89
89
|
return isinstance(value, int) and not isinstance(value, bool) and value > 1
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
def create_time_of(pid: int) -> float | None:
|
|
93
|
+
"""The process start-time identity token for ``pid``, or None if it can't be
|
|
94
|
+
read (no such process / no permission). Recorded when writing a pid so a
|
|
95
|
+
later read can tell the same process from a recycled PID."""
|
|
96
|
+
try:
|
|
97
|
+
return psutil.Process(pid).create_time()
|
|
98
|
+
except psutil.Error:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def create_time_matches(pid: int, recorded: float | None) -> bool:
|
|
103
|
+
"""True iff ``pid`` still names the process whose start-time was ``recorded``.
|
|
104
|
+
``recorded is None`` (a legacy sidecar without a token) -> True, best-effort
|
|
105
|
+
unverified, matching the historical behaviour. A live start-time differing by
|
|
106
|
+
>= 1s, or an unreadable process, -> False (a recycled PID: do not signal it)."""
|
|
107
|
+
if recorded is None:
|
|
108
|
+
return True
|
|
109
|
+
try:
|
|
110
|
+
return abs(psutil.Process(pid).create_time() - recorded) < 1.0
|
|
111
|
+
except psutil.Error:
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
|
|
92
115
|
@dataclass(frozen=True)
|
|
93
116
|
class PIDFile:
|
|
94
117
|
path: Path
|
|
@@ -99,10 +122,9 @@ class PIDFile:
|
|
|
99
122
|
atomically (tmp + rename) so a concurrent ``stop``/``kill`` read never sees a
|
|
100
123
|
torn file."""
|
|
101
124
|
payload: dict[str, object] = {"pid": pid}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
pass # token is best-effort; a read without it falls back to unverified
|
|
125
|
+
ct = create_time_of(pid)
|
|
126
|
+
if ct is not None:
|
|
127
|
+
payload["create_time"] = ct
|
|
106
128
|
atomic_write_json(self.path, payload)
|
|
107
129
|
|
|
108
130
|
def read(self) -> int | None:
|
|
@@ -124,14 +146,7 @@ class PIDFile:
|
|
|
124
146
|
return None
|
|
125
147
|
pid = data["pid"]
|
|
126
148
|
recorded = data.get("create_time")
|
|
127
|
-
if recorded
|
|
128
|
-
return pid # write couldn't capture a token; best-effort unverified
|
|
129
|
-
try:
|
|
130
|
-
if abs(psutil.Process(pid).create_time() - recorded) < 1.0:
|
|
131
|
-
return pid
|
|
132
|
-
except psutil.Error:
|
|
133
|
-
pass
|
|
134
|
-
return None # process gone, or a different (recycled) process now holds the PID
|
|
149
|
+
return pid if create_time_matches(pid, recorded) else None
|
|
135
150
|
|
|
136
151
|
def unlink(self) -> None:
|
|
137
152
|
self.path.unlink(missing_ok=True)
|
|
@@ -12,6 +12,7 @@ the module never reads throttle/event state itself, which is what keeps it pure.
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
+
import itertools
|
|
15
16
|
from dataclasses import dataclass
|
|
16
17
|
from datetime import datetime
|
|
17
18
|
|
|
@@ -49,6 +50,68 @@ class Selection:
|
|
|
49
50
|
active_window: str | None
|
|
50
51
|
|
|
51
52
|
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class WindowOverlap:
|
|
55
|
+
"""Two agent-overriding phases whose own run-windows collide in the same
|
|
56
|
+
timezone — a rotation footgun (serve alternates between them round-to-round).
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
phase_a: str
|
|
60
|
+
phase_b: str
|
|
61
|
+
window_a: str
|
|
62
|
+
window_b: str
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _windows_collide(a: schedule.Window, b: schedule.Window) -> bool:
|
|
66
|
+
"""Two windows collide iff some (weekday, minute) is inside BOTH. Delegates
|
|
67
|
+
to ``Window.contains`` — the scheduler's own day-shift/midnight-wrap
|
|
68
|
+
semantics — instead of re-deriving interval/day math, so this can't drift
|
|
69
|
+
from what actually decides whether a round runs (a wrapped window's
|
|
70
|
+
past-midnight tail is attributed to its START day, not the calendar day
|
|
71
|
+
the tail's clock-time falls on — re-deriving that independently is exactly
|
|
72
|
+
what produced the false positive/negative this replaced). Config-time,
|
|
73
|
+
once per boot, over a handful of windows; ``any`` short-circuits, so the
|
|
74
|
+
O(7*1440) scan per pair is negligible.
|
|
75
|
+
"""
|
|
76
|
+
return any(a.contains(d, m) and b.contains(d, m) for d in range(7) for m in range(1440))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def find_phase_window_overlaps(cfg) -> list[WindowOverlap]:
|
|
80
|
+
"""Pure config check: pairs of phases that BOTH override ``agent`` AND BOTH
|
|
81
|
+
define their own non-empty ``run_windows`` AND share an effective timezone AND
|
|
82
|
+
whose windows intersect. Timezone-mismatched pairs are skipped (no cross-tz
|
|
83
|
+
math in a warning). ``pause_windows`` are ignored for now — a pause that carves
|
|
84
|
+
out the overlap can over-warn, acceptable for a warning; the eventual
|
|
85
|
+
hard-error form must model pauses before rejecting.
|
|
86
|
+
"""
|
|
87
|
+
phases = cfg.phases
|
|
88
|
+
if phases is None:
|
|
89
|
+
return []
|
|
90
|
+
candidates = []
|
|
91
|
+
for p in phases.list or []:
|
|
92
|
+
ov = phases.overrides.get(p)
|
|
93
|
+
if ov and ov.agent and ov.schedule is not None and ov.schedule.run_windows:
|
|
94
|
+
sched = cfg.profile_for(p).schedule # effective tz (inherits global), own windows
|
|
95
|
+
candidates.append((p, sched))
|
|
96
|
+
overlaps: list[WindowOverlap] = []
|
|
97
|
+
for (pa, sa), (pb, sb) in itertools.combinations(candidates, 2):
|
|
98
|
+
if sa.timezone != sb.timezone:
|
|
99
|
+
continue
|
|
100
|
+
hit = next(
|
|
101
|
+
(
|
|
102
|
+
(wa, wb)
|
|
103
|
+
for wa in sa.run_windows
|
|
104
|
+
for wb in sb.run_windows
|
|
105
|
+
if _windows_collide(wa, wb)
|
|
106
|
+
),
|
|
107
|
+
None,
|
|
108
|
+
)
|
|
109
|
+
if hit is not None:
|
|
110
|
+
wa, wb = hit
|
|
111
|
+
overlaps.append(WindowOverlap(pa, pb, wa.label, wb.label))
|
|
112
|
+
return overlaps
|
|
113
|
+
|
|
114
|
+
|
|
52
115
|
def candidate_phases(cfg, round_num: int) -> list[str | None]:
|
|
53
116
|
"""Phases to consider this round, in preference order.
|
|
54
117
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# agent-runner.toml — generated by `agent-runner init --preset claude`.
|
|
2
2
|
|
|
3
3
|
[agent]
|
|
4
|
-
command = ["claude", "--model", "claude-opus-
|
|
4
|
+
command = ["claude", "--model", "claude-opus-5",
|
|
5
5
|
"--dangerously-skip-permissions",
|
|
6
6
|
"--verbose", "--output-format", "stream-json"]
|
|
7
7
|
prompt_arg_template = ["-p"]
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
command = ["codewhale", "exec", "--auto", "--output-format", "stream-json"]
|
|
12
12
|
prompt_arg_template = ["{prompt}"]
|
|
13
13
|
name = "codewhale"
|
|
14
|
+
# Grace-kill result-detection token: codewhale's terminal JSONL record (vs.
|
|
15
|
+
# claude's default "type":"result").
|
|
16
|
+
terminal_marker = "\"type\":\"metadata\""
|
|
14
17
|
# [agent.env] omitted — DeepSeek key is ambient (env or codewhale keyring).
|
|
15
18
|
|
|
16
19
|
[runtime]
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
command = ["pi", "-p", "-na", "--mode", "json", "--model", "PROVIDER/MODEL"]
|
|
20
20
|
prompt_arg_template = ["{prompt}"]
|
|
21
21
|
name = "pi"
|
|
22
|
+
# Grace-kill result-detection token: pi's terminal JSONL record (vs. claude's
|
|
23
|
+
# default "type":"result").
|
|
24
|
+
terminal_marker = "\"type\":\"agent_end\""
|
|
22
25
|
|
|
23
26
|
[agent.env]
|
|
24
27
|
PI_OFFLINE = "1"
|