cli-agent-runner 0.3.0__tar.gz → 0.3.1__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.3.0 → cli_agent_runner-0.3.1}/.vulture-whitelist.py +2 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/CHANGELOG.md +6 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/PKG-INFO +1 -1
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_emit/__init__.py +2 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_emit/memory.py +30 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_observe.py +6 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/agent_runtime.py +20 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/api.py +1 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/api_types.py +1 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/_serve_round.py +61 -2
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/common.py +1 -1
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/config/models.py +4 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/config/parsers.py +5 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/config/validators.py +13 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/events.py +1 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/host_health.py +22 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/architecture.md +1 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/configuration.md +3 -2
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/memory-efficiency.md +11 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/plugins.md +2 -2
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/contract/test_public_api_surface.py +1 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_architecture.py +5 -1
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_peek_schema_version.py +1 -1
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime_grace.py +63 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_observation.py +33 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config.py +38 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_events.py +27 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_host_health.py +53 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_round_cgroup_spine.py +168 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.codecov.yml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/dependabot.yml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/.gitignore +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/BACKLOG.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/LICENSE +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/README.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/README.zh.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/SECURITY.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_emit/agent.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_emit/rounds.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_emit/serve.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_install.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_lifecycle.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_monitor_state.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_plugin_scan.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_round_outcome.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_serve_policy.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/_serve_cgroup.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/doctor_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/serve_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/config/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/config/loader.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/phase_select.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/runner.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/build.sh +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/deploy/Dockerfile +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/deploy/docker-compose.yml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/README.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/commands.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/events.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/migrations/0.2.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/migrations/0.3.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/recipes/container-pi.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/recipes/deploy-all-in-one.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/runbook.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/docs/thesis.md +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/pyproject.toml +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/_clock.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/conftest.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_auto_stop_e2e.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_spawn_round_mem_floor.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_spawn_round_wedged.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_bdd_test_conventions.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_container_stop_bound.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_import_footprint.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_price_logic.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_vacuous_invariants.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_no_version_refs.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_round_alloc_growth.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_timeout_budget_invariant.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime_container.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime_reap.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cgroup_probe.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_nested_strictness.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_doctor_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_example_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_exec_prefix.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_git_timeout.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_http_progress_epoch.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_metrics_cgroup_usage.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_metrics_swap.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_dedup_rearm.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_loop.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_on_alert.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_phase_select.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_plugin_scan_parity.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_primary_prompt_file_home.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_round_outcome.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_round_outcome_agent_axis.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_policy.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_round.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_round_oom_killed.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_round_terminate.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_throttle_epoch_bound.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/unit/test_vcs_state.py +0 -0
|
@@ -77,6 +77,7 @@ service
|
|
|
77
77
|
recent_events
|
|
78
78
|
recent_hook_failures
|
|
79
79
|
recent_blips
|
|
80
|
+
recent_cgroup_growth_warnings
|
|
80
81
|
schedule
|
|
81
82
|
|
|
82
83
|
# agent_runner.api_types.RateLimitState
|
|
@@ -248,6 +249,7 @@ full_avg10_critical
|
|
|
248
249
|
some_avg10_warning
|
|
249
250
|
critical_consecutive_samples
|
|
250
251
|
in_round_terminate
|
|
252
|
+
cgroup_growth_rate_warning_mb_per_min
|
|
251
253
|
|
|
252
254
|
# agent_runner.context_store.OrphanState
|
|
253
255
|
round_num
|
|
@@ -5,6 +5,12 @@ 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.3.1] - 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `monitor.host_health.pressure.cgroup_growth_rate_warning_mb_per_min` (default 512.0): a mid-round memory-growth-rate detector gives an earlier growth signal than the existing PSI/MemFree floors catch, ahead of a runaway climb reaching them. Observability only — no round action.
|
|
12
|
+
- `cgroup_growth_rate_warning` event, surfaced in `peek --json` as `recent_cgroup_growth_warnings` (schema `2.1`).
|
|
13
|
+
|
|
8
14
|
## [0.3.0] - 2026-09-13
|
|
9
15
|
|
|
10
16
|
**The one breaking release of the 0.3 line.** Run `agent-runner migrate` before upgrading — it rewrites everything below in one pass.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
@@ -29,6 +29,7 @@ from agent_runner._emit.agent import ( # noqa: F401 — public re-export
|
|
|
29
29
|
emit_transient_error_recovered,
|
|
30
30
|
)
|
|
31
31
|
from agent_runner._emit.memory import ( # noqa: F401 — public re-export
|
|
32
|
+
emit_cgroup_growth_rate_warning,
|
|
32
33
|
emit_host_cgroup_memory_limit,
|
|
33
34
|
emit_mem_pressure_deferred_to_cgroup,
|
|
34
35
|
emit_round_cgroup_memory,
|
|
@@ -69,6 +70,7 @@ __all__ = [
|
|
|
69
70
|
"emit_agent_auth_error_detected",
|
|
70
71
|
"emit_agent_usage_recorded",
|
|
71
72
|
"emit_anomaly_repetitive_tool",
|
|
73
|
+
"emit_cgroup_growth_rate_warning",
|
|
72
74
|
"emit_config_broken",
|
|
73
75
|
"emit_config_migrated",
|
|
74
76
|
"emit_crash_loop",
|
|
@@ -238,3 +238,33 @@ def emit_mem_pressure_deferred_to_cgroup(
|
|
|
238
238
|
from agent_runner.events import MEM_PRESSURE_DEFERRED_TO_CGROUP, emit
|
|
239
239
|
|
|
240
240
|
emit(log_dir, MEM_PRESSURE_DEFERRED_TO_CGROUP, pid=pid, signal=signal, message=message)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def emit_cgroup_growth_rate_warning(
|
|
244
|
+
log_dir: Path,
|
|
245
|
+
*,
|
|
246
|
+
round_num: int,
|
|
247
|
+
rate_mb_per_min: float,
|
|
248
|
+
threshold_mb_per_min: float,
|
|
249
|
+
source: str,
|
|
250
|
+
context: dict,
|
|
251
|
+
) -> None:
|
|
252
|
+
"""Emit on a False->True crossing of _spawn_round's mid-round growth-rate
|
|
253
|
+
derivative (host_health.cgroup_growth_rate_pressure) -- observability
|
|
254
|
+
only, no action path. ``source`` names which per-tick reading the
|
|
255
|
+
derivative used: "cgroup" (a finite cgroup memory.max bound exists,
|
|
256
|
+
memory.current is read) or "rss_sum" (no finite bound -- the psutil
|
|
257
|
+
children_rss_sum_bytes tree sum is used instead). Edge-triggered: fires
|
|
258
|
+
once per crossing episode, not once per tick, so a sustained climb does
|
|
259
|
+
not spam the event stream."""
|
|
260
|
+
from agent_runner.events import CGROUP_GROWTH_RATE_WARNING, emit
|
|
261
|
+
|
|
262
|
+
emit(
|
|
263
|
+
log_dir,
|
|
264
|
+
CGROUP_GROWTH_RATE_WARNING,
|
|
265
|
+
round_num=round_num,
|
|
266
|
+
rate_mb_per_min=rate_mb_per_min,
|
|
267
|
+
threshold_mb_per_min=threshold_mb_per_min,
|
|
268
|
+
source=source,
|
|
269
|
+
context=context,
|
|
270
|
+
)
|
|
@@ -21,12 +21,14 @@ from agent_runner.clock import SYSTEM_CLOCK
|
|
|
21
21
|
from agent_runner.config import load_config
|
|
22
22
|
from agent_runner.events import (
|
|
23
23
|
AGENT_NETWORK_BLIP,
|
|
24
|
+
CGROUP_GROWTH_RATE_WARNING,
|
|
24
25
|
HOOK_FAILED,
|
|
25
26
|
MONITOR_STARTED,
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
_RECENT_HOOK_FAILURES_LIMIT = 10
|
|
29
30
|
_RECENT_BLIPS_LIMIT = 5
|
|
31
|
+
_RECENT_GROWTH_LIMIT = 5
|
|
30
32
|
_MONITOR_SEEN_CAP = 512
|
|
31
33
|
"""Bound on `_monitor_loop_iter`'s dedup set — an unbounded set of alert-identity
|
|
32
34
|
keys would grow forever across a long-lived monitor process; oldest-episode
|
|
@@ -80,6 +82,9 @@ def peek(
|
|
|
80
82
|
parsed_events, HOOK_FAILED, _RECENT_HOOK_FAILURES_LIMIT
|
|
81
83
|
)
|
|
82
84
|
recent_blips = _recent_events_of_kind(parsed_events, AGENT_NETWORK_BLIP, _RECENT_BLIPS_LIMIT)
|
|
85
|
+
recent_cgroup_growth_warnings = _recent_events_of_kind(
|
|
86
|
+
parsed_events, CGROUP_GROWTH_RATE_WARNING, _RECENT_GROWTH_LIMIT
|
|
87
|
+
)
|
|
83
88
|
|
|
84
89
|
from agent_runner._throttle import effective_throttle_view
|
|
85
90
|
|
|
@@ -121,6 +126,7 @@ def peek(
|
|
|
121
126
|
recent_events=recent,
|
|
122
127
|
recent_hook_failures=recent_hook_failures,
|
|
123
128
|
recent_blips=recent_blips,
|
|
129
|
+
recent_cgroup_growth_warnings=recent_cgroup_growth_warnings,
|
|
124
130
|
schedule=monitor.latest_schedule_state(parsed_events),
|
|
125
131
|
)
|
|
126
132
|
return state if select is None else select_path(state, select)
|
|
@@ -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.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
21
|
+
__version__ = version = '0.3.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -247,6 +247,26 @@ def _live_children(
|
|
|
247
247
|
return live, ignored
|
|
248
248
|
|
|
249
249
|
|
|
250
|
+
def children_rss_sum_bytes(proc: subprocess.Popen) -> int | None:
|
|
251
|
+
"""Sum RSS (bytes) across the process tree (self + all recursive children)
|
|
252
|
+
-- the no-finite-cgroup-bound fallback for the mid-round growth-rate
|
|
253
|
+
derivative (see cli/_serve_round.py). Uncapped, unlike _live_children
|
|
254
|
+
(a 5-child cap would undercount a wider tree); fail-open on the same
|
|
255
|
+
PID-race guards _live_children uses: None when the leader itself is gone
|
|
256
|
+
or unreadable, per-child skip (not abort) when a child vanishes mid-walk."""
|
|
257
|
+
try:
|
|
258
|
+
parent = psutil.Process(proc.pid)
|
|
259
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
260
|
+
return None
|
|
261
|
+
total = 0
|
|
262
|
+
for p in [parent, *parent.children(recursive=True)]:
|
|
263
|
+
try:
|
|
264
|
+
total += p.memory_info().rss
|
|
265
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
266
|
+
continue
|
|
267
|
+
return total
|
|
268
|
+
|
|
269
|
+
|
|
250
270
|
# Decoupled from the 0.2s poll tick: the marker scan only needs to notice the
|
|
251
271
|
# result within max_grace_after_result_s (seconds, integer), so scanning more
|
|
252
272
|
# often than this buys nothing but re-read cost on a growing round log.
|
|
@@ -31,6 +31,7 @@ from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-ex
|
|
|
31
31
|
emit_agent_auth_error_detected,
|
|
32
32
|
emit_agent_usage_recorded,
|
|
33
33
|
emit_anomaly_repetitive_tool,
|
|
34
|
+
emit_cgroup_growth_rate_warning,
|
|
34
35
|
emit_config_broken,
|
|
35
36
|
emit_config_migrated,
|
|
36
37
|
emit_crash_loop,
|
|
@@ -114,6 +114,7 @@ class ProjectState:
|
|
|
114
114
|
recent_events: list[dict[str, Any]] = field(default_factory=list)
|
|
115
115
|
recent_hook_failures: list[dict[str, Any]] = field(default_factory=list)
|
|
116
116
|
recent_blips: list[dict[str, Any]] = field(default_factory=list)
|
|
117
|
+
recent_cgroup_growth_warnings: list[dict[str, Any]] = field(default_factory=list)
|
|
117
118
|
schedule: dict | None = None
|
|
118
119
|
|
|
119
120
|
|
|
@@ -44,8 +44,13 @@ from agent_runner._throttle import (
|
|
|
44
44
|
pending_recovered,
|
|
45
45
|
round_had_no_progress,
|
|
46
46
|
)
|
|
47
|
-
from agent_runner.agent_runtime import
|
|
47
|
+
from agent_runner.agent_runtime import (
|
|
48
|
+
_kill_stray_descendants,
|
|
49
|
+
_snapshot_stray_descendants,
|
|
50
|
+
children_rss_sum_bytes,
|
|
51
|
+
)
|
|
48
52
|
from agent_runner.api import (
|
|
53
|
+
emit_cgroup_growth_rate_warning,
|
|
49
54
|
emit_config_broken,
|
|
50
55
|
emit_crash_loop,
|
|
51
56
|
emit_mem_loop,
|
|
@@ -337,7 +342,14 @@ def _spawn_round(
|
|
|
337
342
|
run (cgroup-defer, or the off switch) would otherwise write one event
|
|
338
343
|
per ~10s tick for up to a whole ``round_budget_s``. The streak still
|
|
339
344
|
resets to 0 on any non-critical tick, so the cap is per streak-episode:
|
|
340
|
-
sampling resumes from 1 the next time critical pressure recurs.
|
|
345
|
+
sampling resumes from 1 the next time critical pressure recurs.
|
|
346
|
+
|
|
347
|
+
A second, independent derivative rides the SAME mem-check tick: growth
|
|
348
|
+
RATE (MB/min) of the round's own memory (cgroup memory.current when a
|
|
349
|
+
finite bound exists, else a psutil RSS-sum over the process tree),
|
|
350
|
+
evaluated by host_health.cgroup_growth_rate_pressure and, on a crossing,
|
|
351
|
+
emitting cgroup_growth_rate_warning once per episode -- observability
|
|
352
|
+
only, no termination action."""
|
|
341
353
|
log_dir = round_log_path.parent
|
|
342
354
|
with round_log_path.open("w") as f:
|
|
343
355
|
proc = subprocess.Popen(
|
|
@@ -362,6 +374,9 @@ def _spawn_round(
|
|
|
362
374
|
prev_tick_sample: dict | None = None
|
|
363
375
|
critical_streak = 0
|
|
364
376
|
cgroup_defer_notified = False
|
|
377
|
+
prev_mem_bytes: int | None = None
|
|
378
|
+
prev_mem_mono: float | None = None
|
|
379
|
+
growth_warned = False
|
|
365
380
|
# cgroup pressure spine (round_cgroup_memory): baseline read
|
|
366
381
|
# once at spawn start, empty ({}) when this host has no finite
|
|
367
382
|
# cgroup bound -- cg_base then stays falsy and _stash_cgroup
|
|
@@ -404,6 +419,50 @@ def _spawn_round(
|
|
|
404
419
|
cg_now = metrics.cgroup_memory_usage(bounding_cgroup=cg_bounding)
|
|
405
420
|
cg_peak_current = max(cg_peak_current, cg_now.get("memory_current", 0))
|
|
406
421
|
cg_peak_swap = max(cg_peak_swap, cg_now.get("memory_swap_current", 0))
|
|
422
|
+
cur_mem_bytes = cg_now.get("memory_current") if cg_now else None
|
|
423
|
+
mem_source = "cgroup"
|
|
424
|
+
else:
|
|
425
|
+
cur_mem_bytes = children_rss_sum_bytes(proc)
|
|
426
|
+
mem_source = "rss_sum"
|
|
427
|
+
now_mono = clock.monotonic()
|
|
428
|
+
if cur_mem_bytes is not None:
|
|
429
|
+
rate_mb_per_min = None
|
|
430
|
+
if prev_mem_bytes is not None and prev_mem_mono is not None:
|
|
431
|
+
elapsed_s = max(now_mono - prev_mem_mono, 0.0)
|
|
432
|
+
rate_mb_per_min = (
|
|
433
|
+
0.0
|
|
434
|
+
if elapsed_s == 0
|
|
435
|
+
else max(
|
|
436
|
+
(cur_mem_bytes - prev_mem_bytes)
|
|
437
|
+
/ 1024
|
|
438
|
+
/ 1024
|
|
439
|
+
/ (elapsed_s / 60),
|
|
440
|
+
0.0,
|
|
441
|
+
)
|
|
442
|
+
)
|
|
443
|
+
growth_pressure = host_health.cgroup_growth_rate_pressure(
|
|
444
|
+
rate_mb_per_min, host_health_cfg
|
|
445
|
+
)
|
|
446
|
+
if growth_pressure is not None:
|
|
447
|
+
if not growth_warned:
|
|
448
|
+
growth_warned = True
|
|
449
|
+
emit_cgroup_growth_rate_warning(
|
|
450
|
+
log_dir,
|
|
451
|
+
round_num=round_num,
|
|
452
|
+
rate_mb_per_min=rate_mb_per_min,
|
|
453
|
+
threshold_mb_per_min=(
|
|
454
|
+
host_health_cfg.pressure.cgroup_growth_rate_warning_mb_per_min
|
|
455
|
+
),
|
|
456
|
+
source=mem_source,
|
|
457
|
+
context=growth_pressure.context,
|
|
458
|
+
)
|
|
459
|
+
else:
|
|
460
|
+
growth_warned = False
|
|
461
|
+
prev_mem_bytes, prev_mem_mono = cur_mem_bytes, now_mono
|
|
462
|
+
# else: fail-open -- cur_mem_bytes unavailable this tick
|
|
463
|
+
# (a {}/None source read). Keep prev_mem_bytes/prev_mem_mono
|
|
464
|
+
# untouched so the NEXT successful tick still diffs against
|
|
465
|
+
# the last real reading, not a spurious reset.
|
|
407
466
|
if pressure is not None and pressure.severity == "critical":
|
|
408
467
|
critical_streak += 1
|
|
409
468
|
if (
|
|
@@ -19,7 +19,7 @@ from agent_runner.hooks import plugin_context_enrichers, post_round_hooks, pre_r
|
|
|
19
19
|
from agent_runner.monitor import plugin_detectors
|
|
20
20
|
from agent_runner.vcs_state import plugin_owned_paths
|
|
21
21
|
|
|
22
|
-
PEEK_SCHEMA_VERSION = "2.
|
|
22
|
+
PEEK_SCHEMA_VERSION = "2.1"
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def cfg_from_args(args) -> Config:
|
|
@@ -200,6 +200,10 @@ class _HostHealthPressureConfig:
|
|
|
200
200
|
critical_consecutive_samples: int = 3
|
|
201
201
|
# Off switch for the mid-round hard floor's termination action only.
|
|
202
202
|
in_round_terminate: bool = True
|
|
203
|
+
# Mid-round growth-RATE floor (MB/min) -- observability only in this
|
|
204
|
+
# release: no critical/terminate action exists for it yet, see
|
|
205
|
+
# host_health.cgroup_growth_rate_pressure.
|
|
206
|
+
cgroup_growth_rate_warning_mb_per_min: float = 512.0
|
|
203
207
|
|
|
204
208
|
|
|
205
209
|
@dataclass(frozen=True)
|
|
@@ -57,6 +57,7 @@ from agent_runner.config.validators import (
|
|
|
57
57
|
_require_bool,
|
|
58
58
|
_require_non_negative_int,
|
|
59
59
|
_require_pct,
|
|
60
|
+
_require_positive_float,
|
|
60
61
|
_require_positive_int,
|
|
61
62
|
_require_positive_pct,
|
|
62
63
|
_require_str_list,
|
|
@@ -515,6 +516,10 @@ def _parse_monitor(monitor_d: dict) -> MonitorConfig:
|
|
|
515
516
|
pres_d.get("in_round_terminate", True),
|
|
516
517
|
field="monitor.host_health.pressure.in_round_terminate",
|
|
517
518
|
),
|
|
519
|
+
cgroup_growth_rate_warning_mb_per_min=_require_positive_float(
|
|
520
|
+
pres_d.get("cgroup_growth_rate_warning_mb_per_min", 512.0),
|
|
521
|
+
field="monitor.host_health.pressure.cgroup_growth_rate_warning_mb_per_min",
|
|
522
|
+
),
|
|
518
523
|
),
|
|
519
524
|
)
|
|
520
525
|
monitor = MonitorConfig(
|
|
@@ -166,6 +166,19 @@ def _require_positive_pct(value: Any, *, field: str) -> float:
|
|
|
166
166
|
return v
|
|
167
167
|
|
|
168
168
|
|
|
169
|
+
def _require_positive_float(value: Any, *, field: str) -> float:
|
|
170
|
+
"""Validate a TOML value is a positive float (int accepted too -- TOML's
|
|
171
|
+
bare integer form). Sibling of ``_require_positive_pct`` for a raw-unit
|
|
172
|
+
threshold (e.g. MB/min) that has no natural [0, 100] percent ceiling.
|
|
173
|
+
Rejects bool (subclass of int, would silently coerce ``true`` -> 1.0)."""
|
|
174
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
175
|
+
raise ConfigError(f"{field}: must be a number, got {type(value).__name__} ({value!r})")
|
|
176
|
+
v = float(value)
|
|
177
|
+
if not v > 0.0:
|
|
178
|
+
raise ConfigError(f"{field}: must be positive, got {v}")
|
|
179
|
+
return v
|
|
180
|
+
|
|
181
|
+
|
|
169
182
|
def _validate_regex_list(value: Any, *, field: str) -> list[str]:
|
|
170
183
|
"""Validate a list of regex pattern strings (each must compile). Returns the
|
|
171
184
|
raw strings unchanged; callers compile when they need ``re.Pattern`` objects."""
|
|
@@ -36,6 +36,7 @@ ANOMALY_REPETITIVE_TOOL = "anomaly_repetitive_tool"
|
|
|
36
36
|
AGENT_NETWORK_BLIP = "agent_network_blip"
|
|
37
37
|
AGENT_SPAWN = "agent_spawn"
|
|
38
38
|
AGENT_USAGE_RECORDED = "agent_usage_recorded"
|
|
39
|
+
CGROUP_GROWTH_RATE_WARNING = "cgroup_growth_rate_warning"
|
|
39
40
|
CONFIG_BROKEN = "config_broken"
|
|
40
41
|
CONFIG_MIGRATED = "config_migrated"
|
|
41
42
|
CRASH_LOOP = "crash_loop"
|
|
@@ -183,3 +183,25 @@ def configured_gate_inert(sample: dict[str, Any], prev_sample: dict[str, Any], c
|
|
|
183
183
|
return False # the gate would fire (or we can't evaluate it) -- not inert
|
|
184
184
|
pressure = memory_pressure(sample, prev_sample, cfg)
|
|
185
185
|
return pressure is not None and pressure.signal in ("psi", "swap_out_rate")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def cgroup_growth_rate_pressure(rate_mb_per_min: float | None, cfg: Any) -> Pressure | None:
|
|
189
|
+
"""WARNING when the round's memory-growth rate (MB/min) meets/exceeds the
|
|
190
|
+
configured warning floor. Independent of the memory_pressure ladder above
|
|
191
|
+
(NOT a 5th fall-through tier -- PSI's early return there would mask an
|
|
192
|
+
otherwise-live growth-rate signal). Observability only -- never returns
|
|
193
|
+
"critical" in this release; a growth-driven termination path needs
|
|
194
|
+
calibration on a constrained host before it can safely exist.
|
|
195
|
+
``rate_mb_per_min`` is None when no prior-tick sample pair is available
|
|
196
|
+
yet (the first tick of a round) -- returns None (no signal), not warning."""
|
|
197
|
+
if rate_mb_per_min is None:
|
|
198
|
+
return None
|
|
199
|
+
warn = cfg.pressure.cgroup_growth_rate_warning_mb_per_min
|
|
200
|
+
if rate_mb_per_min >= warn:
|
|
201
|
+
return Pressure(
|
|
202
|
+
severity="warning",
|
|
203
|
+
signal="cgroup_growth_rate",
|
|
204
|
+
message=f"memory growing {rate_mb_per_min:.0f} MB/min (>= {warn:.0f})",
|
|
205
|
+
context={"rate_mb_per_min": rate_mb_per_min, "threshold_mb_per_min": warn},
|
|
206
|
+
)
|
|
207
|
+
return None
|
|
@@ -211,6 +211,7 @@ See `docs/plugins.md` for the `DirtyHandler` protocol and override recipe.
|
|
|
211
211
|
- `agent_spawn`
|
|
212
212
|
- `agent_usage_recorded`
|
|
213
213
|
- `anomaly_repetitive_tool`
|
|
214
|
+
- `cgroup_growth_rate_warning`
|
|
214
215
|
- `config_broken`
|
|
215
216
|
- `config_migrated`
|
|
216
217
|
- `crash_loop`
|
|
@@ -81,7 +81,7 @@ running with newly-set `dirty_action = "auto_commit"` is undefined).
|
|
|
81
81
|
| `remote_failure_tolerance_s` | `int` | 90 |
|
|
82
82
|
| `anomaly_repetitive_window` | `int` | 0 |
|
|
83
83
|
| `anomaly_repetitive_threshold` | `int` | 0 |
|
|
84
|
-
| `host_health` | `MonitorHostHealthConfig` | MonitorHostHealthConfig(disk=_HostHealthDiskConfig(warning_pct=90.0, critical_pct=95.0), memory=_HostHealthMemoryConfig(avail_min_mb=200, free_low_mb=16, swap_out_noise_floor_mb=32), pressure=_HostHealthPressureConfig(full_avg10_critical=60.0, some_avg10_warning=5.0, critical_consecutive_samples=3, in_round_terminate=True)) |
|
|
84
|
+
| `host_health` | `MonitorHostHealthConfig` | MonitorHostHealthConfig(disk=_HostHealthDiskConfig(warning_pct=90.0, critical_pct=95.0), memory=_HostHealthMemoryConfig(avail_min_mb=200, free_low_mb=16, swap_out_noise_floor_mb=32), pressure=_HostHealthPressureConfig(full_avg10_critical=60.0, some_avg10_warning=5.0, critical_consecutive_samples=3, in_round_terminate=True, cgroup_growth_rate_warning_mb_per_min=512.0)) |
|
|
85
85
|
| `round_progress_interval_s` | `int` | 0 |
|
|
86
86
|
| `supervisor_stale_threshold_s` | `int \| None` | None |
|
|
87
87
|
|
|
@@ -91,7 +91,7 @@ running with newly-set `dirty_action = "auto_commit"` is undefined).
|
|
|
91
91
|
|---|---|---|
|
|
92
92
|
| `disk` | `_HostHealthDiskConfig` | _HostHealthDiskConfig(warning_pct=90.0, critical_pct=95.0) |
|
|
93
93
|
| `memory` | `_HostHealthMemoryConfig` | _HostHealthMemoryConfig(avail_min_mb=200, free_low_mb=16, swap_out_noise_floor_mb=32) |
|
|
94
|
-
| `pressure` | `_HostHealthPressureConfig` | _HostHealthPressureConfig(full_avg10_critical=60.0, some_avg10_warning=5.0, critical_consecutive_samples=3, in_round_terminate=True) |
|
|
94
|
+
| `pressure` | `_HostHealthPressureConfig` | _HostHealthPressureConfig(full_avg10_critical=60.0, some_avg10_warning=5.0, critical_consecutive_samples=3, in_round_terminate=True, cgroup_growth_rate_warning_mb_per_min=512.0) |
|
|
95
95
|
|
|
96
96
|
#### `[monitor.host_health.disk]`
|
|
97
97
|
|
|
@@ -116,6 +116,7 @@ running with newly-set `dirty_action = "auto_commit"` is undefined).
|
|
|
116
116
|
| `some_avg10_warning` | `float` | 5.0 |
|
|
117
117
|
| `critical_consecutive_samples` | `int` | 3 |
|
|
118
118
|
| `in_round_terminate` | `bool` | True |
|
|
119
|
+
| `cgroup_growth_rate_warning_mb_per_min` | `float` | 512.0 |
|
|
119
120
|
|
|
120
121
|
### `[phases]`
|
|
121
122
|
|
|
@@ -287,6 +287,17 @@ neutrality argument above (same objects, same import-graph shape, no new
|
|
|
287
287
|
per-round allocation), no regression is expected on that host either, but
|
|
288
288
|
that claim is unverified pending whoever runs the next prerelease build there.
|
|
289
289
|
|
|
290
|
+
## 0.3.0 → 0.3.1
|
|
291
|
+
|
|
292
|
+
Not independently re-measured this release. No import-time work changed:
|
|
293
|
+
`children_rss_sum_bytes` and `cgroup_growth_rate_pressure` are plain
|
|
294
|
+
functions added beside existing ones in already-imported modules, not new
|
|
295
|
+
imports. The mid-round tick loop's existing ~10s cadence gains a few extra
|
|
296
|
+
local-variable holders and, at most, one more psutil (or reused cgroup)
|
|
297
|
+
read on that SAME already-scheduled tick — no new per-round allocation
|
|
298
|
+
growth pattern. `tests/invariants/test_round_alloc_growth.py` stays green
|
|
299
|
+
unmodified.
|
|
300
|
+
|
|
290
301
|
## Methodology
|
|
291
302
|
|
|
292
303
|
Machine: macOS 26.6.2, arm64, 16 KB pages. Python 3.11.3 (CPython, pyenv),
|
|
@@ -188,7 +188,7 @@ The round itself continues — a broken plugin must not crash the supervisor.
|
|
|
188
188
|
|
|
189
189
|
```json
|
|
190
190
|
{
|
|
191
|
-
"schema_version": "2.
|
|
191
|
+
"schema_version": "2.1",
|
|
192
192
|
"plugins": {
|
|
193
193
|
"event_kinds": [...],
|
|
194
194
|
"context_enrichers": ["current_branch"],
|
|
@@ -537,7 +537,7 @@ detector. Other plugin detectors and all builtins still run normally.
|
|
|
537
537
|
|
|
538
538
|
```json
|
|
539
539
|
{
|
|
540
|
-
"schema_version": "2.
|
|
540
|
+
"schema_version": "2.1",
|
|
541
541
|
"plugins": {
|
|
542
542
|
"event_kinds": [...],
|
|
543
543
|
"context_enrichers": [...],
|
|
@@ -93,7 +93,10 @@ ALLOWED_SERVE_ROUND_IMPORTS = {
|
|
|
93
93
|
}
|
|
94
94
|
ALLOWED_SERVE_ROUND_FROM = [
|
|
95
95
|
("agent_runner", {"host_health", "metrics"}),
|
|
96
|
-
(
|
|
96
|
+
(
|
|
97
|
+
"agent_runner.agent_runtime",
|
|
98
|
+
{"_kill_stray_descendants", "_snapshot_stray_descendants", "children_rss_sum_bytes"},
|
|
99
|
+
),
|
|
97
100
|
(
|
|
98
101
|
"agent_runner._serve_policy",
|
|
99
102
|
{
|
|
@@ -118,6 +121,7 @@ ALLOWED_SERVE_ROUND_FROM = [
|
|
|
118
121
|
(
|
|
119
122
|
"agent_runner.api",
|
|
120
123
|
{
|
|
124
|
+
"emit_cgroup_growth_rate_warning",
|
|
121
125
|
"emit_config_broken",
|
|
122
126
|
"emit_crash_loop",
|
|
123
127
|
"emit_mem_loop",
|
{cli_agent_runner-0.3.0 → cli_agent_runner-0.3.1}/tests/invariants/test_peek_schema_version.py
RENAMED
|
@@ -72,7 +72,7 @@ def test_peek_json_should_include_schema_version_when_emitted(tmp_path: Path) ->
|
|
|
72
72
|
f"schema_version regressed: got {payload['schema_version']!r}, "
|
|
73
73
|
f"expected >= {PEEK_SCHEMA_VERSION!r}"
|
|
74
74
|
)
|
|
75
|
-
assert payload["schema_version"] == "2.
|
|
75
|
+
assert payload["schema_version"] == "2.1"
|
|
76
76
|
assert "plugins" in payload
|
|
77
77
|
assert isinstance(payload["plugins"], dict)
|
|
78
78
|
assert "event_kinds" in payload["plugins"]
|
|
@@ -9,6 +9,7 @@ import subprocess
|
|
|
9
9
|
import sys
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
+
import psutil
|
|
12
13
|
import pytest
|
|
13
14
|
|
|
14
15
|
from agent_runner import agent_runtime
|
|
@@ -284,6 +285,68 @@ def test_live_children_should_return_empty_when_process_already_exited():
|
|
|
284
285
|
assert _live_children(p) == ([], []) # NoSuchProcess swallowed
|
|
285
286
|
|
|
286
287
|
|
|
288
|
+
def test_children_rss_sum_bytes_should_return_none_when_process_already_exited():
|
|
289
|
+
from agent_runner.agent_runtime import children_rss_sum_bytes
|
|
290
|
+
|
|
291
|
+
p = subprocess.Popen(["true"])
|
|
292
|
+
p.wait()
|
|
293
|
+
|
|
294
|
+
assert children_rss_sum_bytes(p) is None # NoSuchProcess swallowed, mirrors _live_children
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def test_children_rss_sum_bytes_should_sum_parent_and_child_rss_when_tree_alive():
|
|
298
|
+
from agent_runner.agent_runtime import children_rss_sum_bytes
|
|
299
|
+
|
|
300
|
+
p = subprocess.Popen(["bash", "-c", "sleep 30 & wait"], start_new_session=True)
|
|
301
|
+
box: dict = {}
|
|
302
|
+
|
|
303
|
+
def _ready() -> bool:
|
|
304
|
+
box["total"] = children_rss_sum_bytes(p)
|
|
305
|
+
return box["total"] is not None and box["total"] > 0
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
assert poll_until(_ready, timeout_s=5.0), "rss sum never became positive"
|
|
309
|
+
assert box["total"] > 0
|
|
310
|
+
finally:
|
|
311
|
+
os.killpg(p.pid, signal.SIGKILL)
|
|
312
|
+
p.wait()
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def test_children_rss_sum_bytes_should_skip_a_vanished_child_when_others_remain(monkeypatch):
|
|
316
|
+
"""A child that raises NoSuchProcess mid-walk (PID race) must not abort the
|
|
317
|
+
whole sum -- the remaining process(es) still contribute."""
|
|
318
|
+
from agent_runner.agent_runtime import children_rss_sum_bytes
|
|
319
|
+
|
|
320
|
+
p = subprocess.Popen(["sleep", "3"], start_new_session=True)
|
|
321
|
+
|
|
322
|
+
class _FailingChild:
|
|
323
|
+
def memory_info(self):
|
|
324
|
+
raise psutil.NoSuchProcess(pid=99999)
|
|
325
|
+
|
|
326
|
+
real_process = psutil.Process
|
|
327
|
+
|
|
328
|
+
class _FakeParent:
|
|
329
|
+
def __init__(self, pid):
|
|
330
|
+
self._real = real_process(pid)
|
|
331
|
+
|
|
332
|
+
def memory_info(self):
|
|
333
|
+
return self._real.memory_info()
|
|
334
|
+
|
|
335
|
+
def children(self, recursive=True):
|
|
336
|
+
return [_FailingChild()]
|
|
337
|
+
|
|
338
|
+
try:
|
|
339
|
+
monkeypatch.setattr(agent_runtime.psutil, "Process", lambda pid: _FakeParent(pid))
|
|
340
|
+
|
|
341
|
+
total = children_rss_sum_bytes(p)
|
|
342
|
+
|
|
343
|
+
assert total is not None
|
|
344
|
+
assert total > 0 # parent's own RSS still counted
|
|
345
|
+
finally:
|
|
346
|
+
os.killpg(p.pid, signal.SIGKILL)
|
|
347
|
+
p.wait()
|
|
348
|
+
|
|
349
|
+
|
|
287
350
|
def test_run_should_extend_grace_when_child_still_live_after_result(tmp_path, monkeypatch):
|
|
288
351
|
"""Agent emits result then backgrounds a long child -> live worker -> NOT
|
|
289
352
|
grace-killed; round_budget_s (wall) reaps it instead; extended fired once.
|
|
@@ -392,6 +392,39 @@ def test_peek_should_populate_recent_blips_when_events_contain_blips(
|
|
|
392
392
|
assert rounds == [2, 3, 4, 5, 6]
|
|
393
393
|
|
|
394
394
|
|
|
395
|
+
def test_peek_should_populate_recent_cgroup_growth_warnings_when_events_contain_them(
|
|
396
|
+
tmp_git_repo: Path,
|
|
397
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
398
|
+
) -> None:
|
|
399
|
+
monkeypatch.setenv("HOME", str(tmp_git_repo))
|
|
400
|
+
api.init(tmp_git_repo, force=False, commit=False)
|
|
401
|
+
_seed_logs(tmp_git_repo)
|
|
402
|
+
cfg = load_config(tmp_git_repo / "agent-runner.toml")
|
|
403
|
+
log_dir = cfg.runtime.log_dir
|
|
404
|
+
warning_lines = [
|
|
405
|
+
json.dumps(
|
|
406
|
+
{
|
|
407
|
+
"ts": f"2026-05-12T11:00:0{i}.000Z",
|
|
408
|
+
"event": "cgroup_growth_rate_warning",
|
|
409
|
+
"round_num": i,
|
|
410
|
+
"rate_mb_per_min": 900.0,
|
|
411
|
+
"threshold_mb_per_min": 512.0,
|
|
412
|
+
"source": "cgroup",
|
|
413
|
+
"context": {},
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
for i in range(7)
|
|
417
|
+
]
|
|
418
|
+
with (log_dir / "events-2026-05.jsonl").open("a", encoding="utf-8") as f:
|
|
419
|
+
f.write("\n".join(warning_lines) + "\n")
|
|
420
|
+
|
|
421
|
+
state = api.peek(tmp_git_repo)
|
|
422
|
+
|
|
423
|
+
assert len(state.recent_cgroup_growth_warnings) == 5, "default limit is 5, most-recent first"
|
|
424
|
+
rounds = [w["round_num"] for w in state.recent_cgroup_growth_warnings]
|
|
425
|
+
assert rounds == [2, 3, 4, 5, 6]
|
|
426
|
+
|
|
427
|
+
|
|
395
428
|
def test_narrate_events_should_yield_formatted_lines_when_events_seeded(
|
|
396
429
|
tmp_path: Path,
|
|
397
430
|
) -> None:
|
|
@@ -2072,6 +2072,44 @@ def test_in_round_terminate_should_reject_non_bool_value(tmp_path: Path, bad: st
|
|
|
2072
2072
|
load_config(toml)
|
|
2073
2073
|
|
|
2074
2074
|
|
|
2075
|
+
@pytest.mark.parametrize("bad", ["0", "-1", '"x"', "true", "nan"])
|
|
2076
|
+
def test_cgroup_growth_rate_warning_mb_per_min_should_reject_non_positive_or_bool_values(
|
|
2077
|
+
tmp_path: Path, bad: str
|
|
2078
|
+
) -> None:
|
|
2079
|
+
|
|
2080
|
+
toml = _write_toml(
|
|
2081
|
+
tmp_path, _HOST_HEALTH_PRESSURE_BASE + f"cgroup_growth_rate_warning_mb_per_min = {bad}\n"
|
|
2082
|
+
)
|
|
2083
|
+
|
|
2084
|
+
with pytest.raises(
|
|
2085
|
+
ValueError, match="monitor.host_health.pressure.cgroup_growth_rate_warning_mb_per_min"
|
|
2086
|
+
):
|
|
2087
|
+
load_config(toml)
|
|
2088
|
+
|
|
2089
|
+
|
|
2090
|
+
@pytest.mark.parametrize(("literal", "expected"), [("256", 256.0), ("128.5", 128.5)])
|
|
2091
|
+
def test_cgroup_growth_rate_warning_mb_per_min_should_accept_int_or_float_when_positive(
|
|
2092
|
+
tmp_path: Path, literal: str, expected: float
|
|
2093
|
+
) -> None:
|
|
2094
|
+
|
|
2095
|
+
toml = _write_toml(
|
|
2096
|
+
tmp_path,
|
|
2097
|
+
_HOST_HEALTH_PRESSURE_BASE + f"cgroup_growth_rate_warning_mb_per_min = {literal}\n",
|
|
2098
|
+
)
|
|
2099
|
+
|
|
2100
|
+
cfg = load_config(toml)
|
|
2101
|
+
|
|
2102
|
+
assert cfg.monitor.host_health.pressure.cgroup_growth_rate_warning_mb_per_min == expected
|
|
2103
|
+
|
|
2104
|
+
|
|
2105
|
+
def test_cgroup_growth_rate_warning_mb_per_min_should_default_to_512() -> None:
|
|
2106
|
+
from agent_runner.config import MonitorHostHealthConfig
|
|
2107
|
+
|
|
2108
|
+
cfg = MonitorHostHealthConfig()
|
|
2109
|
+
|
|
2110
|
+
assert cfg.pressure.cgroup_growth_rate_warning_mb_per_min == 512.0
|
|
2111
|
+
|
|
2112
|
+
|
|
2075
2113
|
_INJECT_CONTEXT_BASE = """\
|
|
2076
2114
|
[agent]
|
|
2077
2115
|
command = ["true"]
|