cli-agent-runner 0.3.6__tar.gz → 0.3.7__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.6 → cli_agent_runner-0.3.7}/.vulture-whitelist.py +7 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/CHANGELOG.md +7 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/PKG-INFO +1 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_emit/__init__.py +6 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_emit/memory.py +64 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/api.py +3 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/_serve_cgroup.py +103 -11
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/_serve_round.py +110 -9
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/common.py +5 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/doctor_cmd.py +21 -4
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/serve_cmd.py +2 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/config/__init__.py +1 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/config/models.py +28 -2
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/config/parsers.py +41 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/events.py +3 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/metrics.py +125 -6
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/architecture.md +3 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/commands.md +1 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/configuration.md +4 -2
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/memory-efficiency.md +33 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/plugins.md +2 -2
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/contract/test_public_api_surface.py +3 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_spawn_round_mem_floor.py +521 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_architecture.py +16 -2
- cli_agent_runner-0.3.7/tests/invariants/test_brake_defaults_off.py +46 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_event_kinds_ssot.py +5 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_layer_2_loop_size.py +1 -1
- cli_agent_runner-0.3.7/tests/invariants/test_no_direct_inspect_import.py +47 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_peek_schema_version.py +3 -1
- cli_agent_runner-0.3.7/tests/linux/test_memory_high_nudge_e2e.py +180 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cgroup_probe.py +279 -3
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config.py +20 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_nested_strictness.py +61 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_doctor_cmd.py +84 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_exec_prefix.py +3 -3
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_round_outcome.py +45 -0
- cli_agent_runner-0.3.7/tests/unit/test_serve_round.py +81 -0
- cli_agent_runner-0.3.6/tests/unit/test_serve_round.py +0 -34
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.codecov.yml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/dependabot.yml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/.gitignore +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/BACKLOG.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/LICENSE +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/README.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/README.zh.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/SECURITY.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_emit/agent.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_emit/plugins.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_emit/rounds.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_emit/serve.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_install.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_lifecycle.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_monitor_state.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_notify.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_observe.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_plugin_checksum.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_plugin_sandbox.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_plugin_scan.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_procwait.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_round_outcome.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_sandbox_probe.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_serve_policy.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/agent_runtime.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/config/loader.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/config/validators.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/host_health.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/phase_select.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/runner.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/build.sh +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/deploy/Dockerfile +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/deploy/docker-compose.yml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/README.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/events.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/migrations/0.2.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/migrations/0.3.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/recipes/container-pi.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/recipes/deploy-all-in-one.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/runbook.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/docs/thesis.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/pyproject.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/_clock.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/conftest.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_auto_stop_e2e.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_cooperative_grace_property.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_reap_property.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_spawn_round_wedged.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_bdd_test_conventions.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_builtin_plugin_names_sync.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_container_stop_bound.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_import_footprint.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_kill_tests_not_skipped.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_asyncio_select.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_price_logic.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_vacuous_invariants.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_no_version_refs.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_procwait_shield.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_round_alloc_growth.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_timeout_budget_invariant.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/linux/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/linux/test_plugin_sandbox_kill.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_container.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_reap.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_apply_reap_grace_env.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_builtin_provenance.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_collapse_spawn_decisions.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_dispatch_dirty_owner_branch.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_doctor_sandbox.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_events.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_example_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_gate_serve_boot.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_git_timeout.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_host_health.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_http_progress_epoch.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_kill_pgroup_shielding.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_load_fail_closed.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_loader_split.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_manifest_spawn_hooks.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_metrics_cgroup_usage.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_metrics_swap.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_dedup_rearm.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_loop.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_on_alert.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_notify.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_peek_sandbox_block.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_phase_select.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_checksum.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_name_mismatch.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_sandbox_wire.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_scan_parity.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_plugins_config_fields.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_primary_prompt_file_home.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_procwait.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_round_outcome_agent_axis.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_sandbox_probe.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_policy.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_oom_killed.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_terminate.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_wait_exit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_serve_wakeup.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_spawn_seam.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_terminate_round_wait_exit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_throttle_epoch_bound.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.7}/tests/unit/test_vcs_state.py +0 -0
|
@@ -195,6 +195,7 @@ supervisor_stale_threshold_s
|
|
|
195
195
|
disk
|
|
196
196
|
memory
|
|
197
197
|
pressure
|
|
198
|
+
brake
|
|
198
199
|
|
|
199
200
|
# agent_runner.config.models.PhaseOverride
|
|
200
201
|
round_budget_s
|
|
@@ -255,6 +256,11 @@ pause_windows
|
|
|
255
256
|
stash_idempotency_s
|
|
256
257
|
dirty_action
|
|
257
258
|
|
|
259
|
+
# agent_runner.config.models._HostHealthBrakeConfig
|
|
260
|
+
memory_high
|
|
261
|
+
memory_high_step_pct
|
|
262
|
+
warning_consecutive_samples
|
|
263
|
+
|
|
258
264
|
# agent_runner.config.models._HostHealthDiskConfig
|
|
259
265
|
warning_pct
|
|
260
266
|
critical_pct
|
|
@@ -270,6 +276,7 @@ some_avg10_warning
|
|
|
270
276
|
critical_consecutive_samples
|
|
271
277
|
in_round_terminate
|
|
272
278
|
cgroup_growth_rate_warning_mb_per_min
|
|
279
|
+
in_round_nudge
|
|
273
280
|
|
|
274
281
|
# agent_runner.context_store.OrphanState
|
|
275
282
|
round_num
|
|
@@ -5,6 +5,13 @@ 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.7] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Opt-in cgroup v2 `memory.high` soft-brake (`[monitor.host_health.brake] memory_high`, default off): under sustained warning-level memory pressure the supervisor reversibly writes `memory.high` on its OWN cgroup leaf to throttle a growing round before the host swaps, then restores it on recovery or at round end. `memory_high_step_pct` defaults to `0` = cap-at-current (throttle further growth with no synchronous reclaim burst — safe for SD-backed hosts); `step_pct >= 1` is opt-in aggressive reclaim that dumps ~step% of the leaf to swap at engage. Fail-open; arms only when the leaf is delegated.
|
|
12
|
+
- Opt-in early cooperative SIGTERM (`[monitor.host_health.pressure] in_round_nudge`, default off): fires the hard floor's SIGTERM two samples earlier so a cooperative agent gets its wrap-up grace; recorded as `round_mem_terminated` with a new `tier` field so the mem-loop give-up still converges.
|
|
13
|
+
- New events `memory_high_engaged` / `memory_high_released` / `memory_high_write_failed`; `doctor` and `peek --json` (schema `2.5`) report the brake as `off | armed | inert(<reason>)`.
|
|
14
|
+
|
|
8
15
|
## [0.3.6] - 2026-09-15
|
|
9
16
|
|
|
10
17
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
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
|
|
@@ -32,6 +32,9 @@ from agent_runner._emit.memory import ( # noqa: F401 — public re-export
|
|
|
32
32
|
emit_cgroup_growth_rate_warning,
|
|
33
33
|
emit_host_cgroup_memory_limit,
|
|
34
34
|
emit_mem_pressure_deferred_to_cgroup,
|
|
35
|
+
emit_memory_high_engaged,
|
|
36
|
+
emit_memory_high_released,
|
|
37
|
+
emit_memory_high_write_failed,
|
|
35
38
|
emit_round_cgroup_memory,
|
|
36
39
|
emit_round_deferred,
|
|
37
40
|
emit_round_mem_critical_sample,
|
|
@@ -88,6 +91,9 @@ __all__ = [
|
|
|
88
91
|
"emit_mem_loop",
|
|
89
92
|
"emit_mem_loop_persistent",
|
|
90
93
|
"emit_mem_pressure_deferred_to_cgroup",
|
|
94
|
+
"emit_memory_high_engaged",
|
|
95
|
+
"emit_memory_high_released",
|
|
96
|
+
"emit_memory_high_write_failed",
|
|
91
97
|
"emit_plugin_builtin_name_squat",
|
|
92
98
|
"emit_plugin_checksum_mismatch",
|
|
93
99
|
"emit_plugin_sandbox_degraded",
|
|
@@ -36,6 +36,7 @@ def emit_round_mem_terminated(
|
|
|
36
36
|
message: str,
|
|
37
37
|
consecutive: int,
|
|
38
38
|
context: dict,
|
|
39
|
+
tier: str = "terminate",
|
|
39
40
|
) -> None:
|
|
40
41
|
"""Emit when _spawn_round's mid-round hard floor terminated a ballooning
|
|
41
42
|
round on critical host_health pressure -- the actual coma-preventer (a
|
|
@@ -45,7 +46,15 @@ def emit_round_mem_terminated(
|
|
|
45
46
|
``consecutive`` (the critical_streak that crossed the threshold)
|
|
46
47
|
and ``context`` (Pressure.context -- the actual psi/mem numbers, e.g.
|
|
47
48
|
psi_full_avg10) make the kill legible from the event stream alone, so an
|
|
48
|
-
operator can retune host_health thresholds without SSH.
|
|
49
|
+
operator can retune host_health thresholds without SSH.
|
|
50
|
+
|
|
51
|
+
``tier`` (``"terminate"`` | ``"nudge"``) distinguishes the hard floor's
|
|
52
|
+
``_terminate_round`` (TERM -> grace -> killpg) from the early cooperative
|
|
53
|
+
nudge (a bare ``proc.terminate()`` two samples earlier, no wait, no
|
|
54
|
+
killpg) -- both fire this SAME event kind so ``round_was_mem_terminated``
|
|
55
|
+
and the ``mem_loop`` give-up breaker count a nudged round exactly like a
|
|
56
|
+
hard-terminated one; the default keeps every existing caller byte-
|
|
57
|
+
compatible."""
|
|
49
58
|
from agent_runner.events import ROUND_MEM_TERMINATED, emit
|
|
50
59
|
|
|
51
60
|
emit(
|
|
@@ -57,6 +66,7 @@ def emit_round_mem_terminated(
|
|
|
57
66
|
message=message,
|
|
58
67
|
consecutive=consecutive,
|
|
59
68
|
context=context,
|
|
69
|
+
tier=tier,
|
|
60
70
|
)
|
|
61
71
|
|
|
62
72
|
|
|
@@ -246,6 +256,59 @@ def emit_mem_pressure_deferred_to_cgroup(
|
|
|
246
256
|
emit(log_dir, MEM_PRESSURE_DEFERRED_TO_CGROUP, pid=pid, signal=signal, message=message)
|
|
247
257
|
|
|
248
258
|
|
|
259
|
+
def emit_memory_high_engaged(
|
|
260
|
+
log_dir: Path,
|
|
261
|
+
*,
|
|
262
|
+
round_num: int,
|
|
263
|
+
previous: str,
|
|
264
|
+
written: int,
|
|
265
|
+
memory_current: int,
|
|
266
|
+
write_ms: int,
|
|
267
|
+
signal: str,
|
|
268
|
+
context: dict,
|
|
269
|
+
rate_mb_per_min: float | None,
|
|
270
|
+
) -> None:
|
|
271
|
+
"""Emit when the reversible cgroup memory.high soft-brake engaged on serve's
|
|
272
|
+
own leaf under sustained warning pressure. ``previous``/``written`` bracket
|
|
273
|
+
the reversible write; ``signal``/``context`` are the triggering Pressure;
|
|
274
|
+
``rate_mb_per_min`` rides along as growth-rate context for Pi calibration."""
|
|
275
|
+
from agent_runner.events import MEMORY_HIGH_ENGAGED, emit
|
|
276
|
+
|
|
277
|
+
emit(
|
|
278
|
+
log_dir,
|
|
279
|
+
MEMORY_HIGH_ENGAGED,
|
|
280
|
+
round_num=round_num,
|
|
281
|
+
previous=previous,
|
|
282
|
+
written=written,
|
|
283
|
+
memory_current=memory_current,
|
|
284
|
+
write_ms=write_ms,
|
|
285
|
+
signal=signal,
|
|
286
|
+
context=context,
|
|
287
|
+
rate_mb_per_min=rate_mb_per_min,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def emit_memory_high_released(log_dir: Path, *, round_num: int, reason: str) -> None:
|
|
292
|
+
"""Emit when the soft-brake was restored to its stashed value. ``reason`` is
|
|
293
|
+
``recovered`` (N healthy ticks) or ``round_end`` (the finally restore, which
|
|
294
|
+
covers every exit path out of the round incl. wedged/exception)."""
|
|
295
|
+
from agent_runner.events import MEMORY_HIGH_RELEASED, emit
|
|
296
|
+
|
|
297
|
+
emit(log_dir, MEMORY_HIGH_RELEASED, round_num=round_num, reason=reason)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def emit_memory_high_write_failed(log_dir: Path, *, round_num: int, errno: int | None) -> None:
|
|
301
|
+
"""Emit once when a memory.high write (engage or restore) hit an OSError --
|
|
302
|
+
fail-open: the brake disarms for the round and serve continues unthrottled,
|
|
303
|
+
exactly as when the brake is configured off. ``errno`` is the real OSError
|
|
304
|
+
code from the engage path; the restore path (which only sees a bool from
|
|
305
|
+
:func:`agent_runner.metrics.restore_leaf_memory_high`) passes ``None`` --
|
|
306
|
+
never ``0``, which conventionally reads as POSIX success."""
|
|
307
|
+
from agent_runner.events import MEMORY_HIGH_WRITE_FAILED, emit
|
|
308
|
+
|
|
309
|
+
emit(log_dir, MEMORY_HIGH_WRITE_FAILED, round_num=round_num, errno=errno)
|
|
310
|
+
|
|
311
|
+
|
|
249
312
|
def emit_cgroup_growth_rate_warning(
|
|
250
313
|
log_dir: Path,
|
|
251
314
|
*,
|
|
@@ -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.7'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 7)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -41,6 +41,9 @@ from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-ex
|
|
|
41
41
|
emit_mem_loop,
|
|
42
42
|
emit_mem_loop_persistent,
|
|
43
43
|
emit_mem_pressure_deferred_to_cgroup,
|
|
44
|
+
emit_memory_high_engaged,
|
|
45
|
+
emit_memory_high_released,
|
|
46
|
+
emit_memory_high_write_failed,
|
|
44
47
|
emit_plugin_builtin_name_squat,
|
|
45
48
|
emit_plugin_checksum_mismatch,
|
|
46
49
|
emit_plugin_sandbox_degraded,
|
|
@@ -22,6 +22,8 @@ from agent_runner import metrics
|
|
|
22
22
|
from agent_runner._serve_policy import _ROUND_UNREAPED_RC
|
|
23
23
|
from agent_runner.api import (
|
|
24
24
|
emit_host_cgroup_memory_limit,
|
|
25
|
+
emit_memory_high_released,
|
|
26
|
+
emit_memory_high_write_failed,
|
|
25
27
|
emit_round_cgroup_memory,
|
|
26
28
|
emit_round_oom_killed,
|
|
27
29
|
)
|
|
@@ -37,6 +39,27 @@ from agent_runner.api import (
|
|
|
37
39
|
# round.
|
|
38
40
|
_ROUND_CGROUP_STATE_BY_LOG_DIR: dict[Path, dict] = {}
|
|
39
41
|
|
|
42
|
+
# Boot-armed soft-brake gate: True iff the brake is configured on AND this
|
|
43
|
+
# process's own cgroup leaf is delegated (memory.high writable) -- probed ONCE
|
|
44
|
+
# at serve boot (_probe_and_emit_cgroup_defer already reads cgroup_delegated),
|
|
45
|
+
# read every mid-round tick by _serve_round via _brake_step_for. Same
|
|
46
|
+
# per-log_dir stable-default pattern as _ROUND_CGROUP_STATE_BY_LOG_DIR (one
|
|
47
|
+
# entry per serve process in production); cmd() cannot grow to thread it (145
|
|
48
|
+
# budget), so it rides in a module dict, not an argument.
|
|
49
|
+
_BRAKE_ARMED_BY_LOG_DIR: dict[Path, bool] = {}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _brake_step_for(log_dir: Path, host_health_cfg) -> int | None:
|
|
53
|
+
"""The armed soft-brake step_pct for this log_dir, or None when the brake is
|
|
54
|
+
disarmed: config off, or the boot probe found the leaf undelegated / no
|
|
55
|
+
cgroup v2. The step itself is single-sourced from config; the dict only
|
|
56
|
+
carries the boot-time delegated-and-on decision."""
|
|
57
|
+
if not host_health_cfg.brake.memory_high:
|
|
58
|
+
return None
|
|
59
|
+
if not _BRAKE_ARMED_BY_LOG_DIR.get(log_dir, False):
|
|
60
|
+
return None
|
|
61
|
+
return host_health_cfg.brake.memory_high_step_pct
|
|
62
|
+
|
|
40
63
|
|
|
41
64
|
def _stash_round_cgroup_state(
|
|
42
65
|
log_dir: Path, cg_base: dict, peak_current: int, peak_swap: int
|
|
@@ -190,18 +213,69 @@ def _maybe_emit_oom_killed(
|
|
|
190
213
|
# report ask #3). Advisory only -- never changes the operator's cgroup/unit.
|
|
191
214
|
_SWAP_CAP_ADVISORY_PCT = 25.0
|
|
192
215
|
|
|
193
|
-
# The delegation-readiness advisory
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
#
|
|
216
|
+
# The delegation-readiness / brake-inert advisory. Fires in two DISTINCT
|
|
217
|
+
# cases, both meaning "memory.high can't be written from here":
|
|
218
|
+
# (1) delegated is False (CONFIRMED not delegated -- systemd Delegate=yes
|
|
219
|
+
# is absent) AND memory.max/memory.high are already bound (memory_high
|
|
220
|
+
# set, or the own_scope "add memory.high" hint above just fired) --
|
|
221
|
+
# the existing config is informational-only without delegation.
|
|
222
|
+
# (2) the operator explicitly turned the soft-brake on
|
|
223
|
+
# ([monitor.host_health.brake] memory_high=true, threaded in here as
|
|
224
|
+
# brake_memory_high) but delegated is not True (False OR None) -- the
|
|
225
|
+
# brake will silently do nothing, worth announcing at boot even with
|
|
226
|
+
# no memory.max/memory.high configured yet.
|
|
227
|
+
# Deliberately NOT ``delegated is not True`` across the board: cgroup_delegated
|
|
228
|
+
# returns None for two different reasons -- cgroup v2 unavailable (harmless)
|
|
229
|
+
# AND the leaf's own os.stat() raising OSError (delegation status genuinely
|
|
230
|
+
# UNKNOWN, even though memory_high/own_scope's ancestor-walk resolved fine).
|
|
231
|
+
# Broadening case (1) to None would assert "not writable" where we actually
|
|
232
|
+
# don't know -- an overclaim this release can't afford. Case (2) is the one
|
|
233
|
+
# exception: the operator asked for the brake, so ANY non-confirmed-delegated
|
|
234
|
+
# state (False or unknown) is worth flagging as "may be inert" up front.
|
|
235
|
+
# Advisory only; this probe and its hint never write to the cgroup or the unit.
|
|
198
236
|
_UNDELEGATED_HINT = (
|
|
199
|
-
"this cgroup is not
|
|
200
|
-
"
|
|
237
|
+
"memory.high on this cgroup is not writable by this process; the soft-brake "
|
|
238
|
+
"is inert. Run serve as a user-mode unit (`systemctl --user` + `loginctl "
|
|
239
|
+
"enable-linger`) or as a root system unit."
|
|
201
240
|
)
|
|
202
241
|
|
|
203
242
|
|
|
204
|
-
def
|
|
243
|
+
def brake_report_state(enabled: bool, delegated: bool | None) -> str:
|
|
244
|
+
"""The operator-facing soft-brake state for doctor/peek: ``off`` (not
|
|
245
|
+
configured), ``armed`` (on AND the leaf is delegated so memory.high is
|
|
246
|
+
writable), or ``inert(<reason>)`` (on but the write path can't arm)."""
|
|
247
|
+
if not enabled:
|
|
248
|
+
return "off"
|
|
249
|
+
if delegated is True:
|
|
250
|
+
return "armed"
|
|
251
|
+
return "inert(undelegated)" if delegated is False else "inert(no cgroup v2)"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def current_brake_state(cfg) -> str:
|
|
255
|
+
"""Resolve the soft-brake state string for `cfg` from live cgroup facts --
|
|
256
|
+
the one-shot ``cgroup_memory_limits() -> cgroup_delegated(self_cgroup=...) ->
|
|
257
|
+
brake_report_state`` sequence, shared by peek (cli.common.emit) and doctor
|
|
258
|
+
so neither re-derives it inline."""
|
|
259
|
+
limits = metrics.cgroup_memory_limits()
|
|
260
|
+
delegated = metrics.cgroup_delegated(self_cgroup=limits["cgroup_path"])
|
|
261
|
+
return brake_report_state(cfg.monitor.host_health.brake.memory_high, delegated)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _release_brake(log_dir: Path, round_num: int, previous: str, *, reason: str) -> bool:
|
|
265
|
+
"""Restore memory.high on serve's own leaf to `previous` and emit the
|
|
266
|
+
matching event: ``memory_high_released`` on success, ``memory_high_write_failed``
|
|
267
|
+
(errno None -- restore only yields a bool) on OSError. Returns whether the
|
|
268
|
+
restore succeeded; callers own their own brake_engaged/brake_previous/
|
|
269
|
+
brake_restore_failed transitions off that result. Shared by _spawn_round's
|
|
270
|
+
mid-round recovery-release site and its unconditional round-end finally."""
|
|
271
|
+
if metrics.restore_leaf_memory_high(previous):
|
|
272
|
+
emit_memory_high_released(log_dir, round_num=round_num, reason=reason)
|
|
273
|
+
return True
|
|
274
|
+
emit_memory_high_write_failed(log_dir, round_num=round_num, errno=None)
|
|
275
|
+
return False
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _probe_and_emit_cgroup_defer(log_dir: Path, *, brake_memory_high: bool = False) -> bool:
|
|
205
279
|
"""Probe this process's cgroup v2 memory budget once at serve startup,
|
|
206
280
|
emit host_cgroup_memory_limit for observability, and return whether the
|
|
207
281
|
mid-round hard floor should defer to kernel cgroup-OOM: True only when
|
|
@@ -249,8 +323,22 @@ def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
|
|
|
249
323
|
the bounding ancestor's ``memory.high`` soft-throttle threshold
|
|
250
324
|
(``metrics.cgroup_memory_high``), ``None`` when unset -- for the same
|
|
251
325
|
reason: an operator asking "is MemoryHigh even set" is exactly this
|
|
252
|
-
release's field ask.
|
|
253
|
-
|
|
326
|
+
release's field ask.
|
|
327
|
+
|
|
328
|
+
A THIRD advisory, ``_UNDELEGATED_HINT``, reads ``brake_memory_high`` --
|
|
329
|
+
the caller-supplied ``[monitor.host_health.brake] memory_high`` config
|
|
330
|
+
switch -- alongside ``delegated``: when the operator turned that brake
|
|
331
|
+
on but this process's own cgroup leaf isn't confirmed delegated
|
|
332
|
+
(``delegated is not True``), the brake would silently write nothing, so
|
|
333
|
+
boot announces it up front even with no memory.max/memory.high
|
|
334
|
+
configured. Independently of the brake, the SAME hint also covers the
|
|
335
|
+
pre-existing "config is informational without delegation" case
|
|
336
|
+
(``delegated is False`` -- CONFIRMED not delegated, never the merely
|
|
337
|
+
unresolved ``None``) when memory_high is already set or the own_scope
|
|
338
|
+
hint above just fired. See the comment block above ``_UNDELEGATED_HINT``
|
|
339
|
+
for why those two triggers use different ``delegated`` strictness.
|
|
340
|
+
Advisory only -- this never changes the operator's cgroup or systemd
|
|
341
|
+
unit."""
|
|
254
342
|
limits = metrics.cgroup_memory_limits()
|
|
255
343
|
swap_total = metrics.swap_total_bytes()
|
|
256
344
|
# Reuse the leaf cgroup_memory_limits already resolved above -- skips
|
|
@@ -263,6 +351,8 @@ def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
|
|
|
263
351
|
# way as calling with no override would.
|
|
264
352
|
memory_high = metrics.cgroup_memory_high(self_cgroup=limits["cgroup_path"])
|
|
265
353
|
delegated = metrics.cgroup_delegated(self_cgroup=limits["cgroup_path"])
|
|
354
|
+
if brake_memory_high:
|
|
355
|
+
_BRAKE_ARMED_BY_LOG_DIR[log_dir] = delegated is True
|
|
266
356
|
swap_max = limits["memory_swap_max"]
|
|
267
357
|
swap_cap_pct = (
|
|
268
358
|
round(100.0 * swap_max / swap_total, 1) if swap_max is not None and swap_total > 0 else None
|
|
@@ -299,7 +389,9 @@ def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
|
|
|
299
389
|
"throttle's PSI-full rise doesn't trip the mid-round floor"
|
|
300
390
|
)
|
|
301
391
|
advisories.append(hint)
|
|
302
|
-
if
|
|
392
|
+
if (brake_memory_high and delegated is not True) or (
|
|
393
|
+
delegated is False and (memory_high is not None or own_scope)
|
|
394
|
+
):
|
|
303
395
|
advisories.append(_UNDELEGATED_HINT)
|
|
304
396
|
advisory = "; ".join(advisories) or None
|
|
305
397
|
if advisory is not None:
|
|
@@ -62,6 +62,8 @@ from agent_runner.api import (
|
|
|
62
62
|
emit_mem_loop,
|
|
63
63
|
emit_mem_loop_persistent,
|
|
64
64
|
emit_mem_pressure_deferred_to_cgroup,
|
|
65
|
+
emit_memory_high_engaged,
|
|
66
|
+
emit_memory_high_write_failed,
|
|
65
67
|
emit_plugin_spawn_decision,
|
|
66
68
|
emit_plugin_spawn_override_ignored,
|
|
67
69
|
emit_round_deferred,
|
|
@@ -74,8 +76,10 @@ from agent_runner.api import (
|
|
|
74
76
|
)
|
|
75
77
|
from agent_runner.api_types import SpawnDecision
|
|
76
78
|
from agent_runner.cli._serve_cgroup import (
|
|
79
|
+
_brake_step_for,
|
|
77
80
|
_emit_round_cgroup_memory,
|
|
78
81
|
_maybe_emit_oom_killed,
|
|
82
|
+
_release_brake,
|
|
79
83
|
_stash_round_cgroup_state,
|
|
80
84
|
)
|
|
81
85
|
from agent_runner.clock import SYSTEM_CLOCK, Clock
|
|
@@ -461,8 +465,8 @@ _MEM_CHECK_INTERVAL_S = 10
|
|
|
461
465
|
|
|
462
466
|
|
|
463
467
|
def _mid_round_action(
|
|
464
|
-
cfg, defer_to_cgroup: bool, critical_streak: int
|
|
465
|
-
) -> Literal["terminate", "defer", "count_only"]:
|
|
468
|
+
cfg, defer_to_cgroup: bool, critical_streak: int, nudged: bool
|
|
469
|
+
) -> Literal["terminate", "defer", "count_only", "nudge"]:
|
|
466
470
|
"""Pure, subprocess-free verdict for a critical mid-round
|
|
467
471
|
tick, once the caller has already incremented ``critical_streak`` and
|
|
468
472
|
(when the streak is within the emit cap) emitted
|
|
@@ -484,13 +488,29 @@ def _mid_round_action(
|
|
|
484
488
|
- ``"terminate"`` — sustained critical pressure, no cgroup containment to
|
|
485
489
|
defer to: the caller ``_terminate_round``s and emits
|
|
486
490
|
``round_mem_terminated``.
|
|
491
|
+
- ``"nudge"`` -- ``in_round_nudge`` is on and this is the FIRST critical
|
|
492
|
+
sample (``critical_streak == 1``, ``nudged`` still False) of a streak whose
|
|
493
|
+
hard verdict WOULD be ``terminate`` (``in_round_terminate`` and not
|
|
494
|
+
``defer_to_cgroup``): fire the hard floor's SIGTERM at the first critical
|
|
495
|
+
sample (not the sustained threshold) so a cooperative agent gets its
|
|
496
|
+
wrap-up grace. ``defer_to_cgroup`` disables the nudge (no terminate to
|
|
497
|
+
move earlier) but NOT the brake -- so does ``critical_consecutive_samples
|
|
498
|
+
== 1``, since the hard verdict above already matches at streak 1.
|
|
487
499
|
"""
|
|
488
|
-
if
|
|
500
|
+
if (
|
|
489
501
|
cfg.pressure.in_round_terminate
|
|
490
502
|
and critical_streak >= cfg.pressure.critical_consecutive_samples
|
|
491
503
|
):
|
|
492
|
-
return "
|
|
493
|
-
|
|
504
|
+
return "defer" if defer_to_cgroup else "terminate"
|
|
505
|
+
if (
|
|
506
|
+
cfg.pressure.in_round_nudge
|
|
507
|
+
and not nudged
|
|
508
|
+
and critical_streak == 1
|
|
509
|
+
and cfg.pressure.in_round_terminate
|
|
510
|
+
and not defer_to_cgroup
|
|
511
|
+
):
|
|
512
|
+
return "nudge"
|
|
513
|
+
return "count_only"
|
|
494
514
|
|
|
495
515
|
|
|
496
516
|
def _spawn_round(
|
|
@@ -586,6 +606,12 @@ def _spawn_round(
|
|
|
586
606
|
stderr=subprocess.STDOUT,
|
|
587
607
|
start_new_session=True,
|
|
588
608
|
)
|
|
609
|
+
brake_engaged = False
|
|
610
|
+
brake_previous: str | None = None
|
|
611
|
+
brake_disarmed = False
|
|
612
|
+
brake_restore_failed = False
|
|
613
|
+
warning_streak = 0
|
|
614
|
+
none_streak = 0
|
|
589
615
|
try:
|
|
590
616
|
deadline = clock.monotonic() + timeout_s
|
|
591
617
|
next_mem_check = (
|
|
@@ -600,6 +626,7 @@ def _spawn_round(
|
|
|
600
626
|
# terminates the round.
|
|
601
627
|
prev_tick_sample: dict | None = None
|
|
602
628
|
critical_streak = 0
|
|
629
|
+
nudged = False
|
|
603
630
|
cgroup_defer_notified = False
|
|
604
631
|
prev_mem_bytes: int | None = None
|
|
605
632
|
prev_mem_mono: float | None = None
|
|
@@ -644,6 +671,7 @@ def _spawn_round(
|
|
|
644
671
|
cur_sample, prev_tick_sample, host_health_cfg
|
|
645
672
|
)
|
|
646
673
|
prev_tick_sample = cur_sample
|
|
674
|
+
rate_mb_per_min: float | None = None
|
|
647
675
|
if cg_base:
|
|
648
676
|
cg_now = metrics.cgroup_memory_usage(bounding_cgroup=cg_bounding)
|
|
649
677
|
cg_peak_current = max(cg_peak_current, cg_now.get("memory_current", 0))
|
|
@@ -655,7 +683,6 @@ def _spawn_round(
|
|
|
655
683
|
mem_source = "rss_sum"
|
|
656
684
|
now_mono = clock.monotonic()
|
|
657
685
|
if cur_mem_bytes is not None:
|
|
658
|
-
rate_mb_per_min = None
|
|
659
686
|
if prev_mem_bytes is not None and prev_mem_mono is not None:
|
|
660
687
|
elapsed_s = max(now_mono - prev_mem_mono, 0.0)
|
|
661
688
|
rate_mb_per_min = (
|
|
@@ -692,6 +719,55 @@ def _spawn_round(
|
|
|
692
719
|
# (a {}/None source read). Keep prev_mem_bytes/prev_mem_mono
|
|
693
720
|
# untouched so the NEXT successful tick still diffs against
|
|
694
721
|
# the last real reading, not a spurious reset.
|
|
722
|
+
if pressure is not None:
|
|
723
|
+
warning_streak += 1
|
|
724
|
+
none_streak = 0
|
|
725
|
+
else:
|
|
726
|
+
warning_streak = 0
|
|
727
|
+
none_streak += 1
|
|
728
|
+
if (
|
|
729
|
+
brake_engaged
|
|
730
|
+
and brake_previous is not None
|
|
731
|
+
and not brake_restore_failed
|
|
732
|
+
and none_streak >= host_health_cfg.brake.warning_consecutive_samples
|
|
733
|
+
):
|
|
734
|
+
if _release_brake(
|
|
735
|
+
log_dir, round_num, brake_previous, reason="recovered"
|
|
736
|
+
):
|
|
737
|
+
brake_engaged = False
|
|
738
|
+
brake_previous = None
|
|
739
|
+
else:
|
|
740
|
+
brake_restore_failed = True
|
|
741
|
+
brake_step = _brake_step_for(log_dir, host_health_cfg)
|
|
742
|
+
if (
|
|
743
|
+
pressure is not None
|
|
744
|
+
and brake_step is not None
|
|
745
|
+
and not brake_disarmed
|
|
746
|
+
and not brake_engaged
|
|
747
|
+
and warning_streak >= host_health_cfg.brake.warning_consecutive_samples
|
|
748
|
+
):
|
|
749
|
+
t0 = clock.monotonic()
|
|
750
|
+
result = metrics.engage_leaf_memory_high(step_pct=brake_step)
|
|
751
|
+
write_ms = int((clock.monotonic() - t0) * 1000)
|
|
752
|
+
if result.get("engaged"):
|
|
753
|
+
brake_engaged = True
|
|
754
|
+
brake_previous = result["previous"]
|
|
755
|
+
emit_memory_high_engaged(
|
|
756
|
+
log_dir,
|
|
757
|
+
round_num=round_num,
|
|
758
|
+
previous=result["previous"],
|
|
759
|
+
written=result["written"],
|
|
760
|
+
memory_current=result["memory_current"],
|
|
761
|
+
write_ms=write_ms,
|
|
762
|
+
signal=pressure.signal,
|
|
763
|
+
context=pressure.context,
|
|
764
|
+
rate_mb_per_min=rate_mb_per_min,
|
|
765
|
+
)
|
|
766
|
+
elif "errno" in result:
|
|
767
|
+
emit_memory_high_write_failed(
|
|
768
|
+
log_dir, round_num=round_num, errno=result["errno"]
|
|
769
|
+
)
|
|
770
|
+
brake_disarmed = True
|
|
695
771
|
if pressure is not None and pressure.severity == "critical":
|
|
696
772
|
critical_streak += 1
|
|
697
773
|
if (
|
|
@@ -705,9 +781,22 @@ def _spawn_round(
|
|
|
705
781
|
context=pressure.context,
|
|
706
782
|
)
|
|
707
783
|
action = _mid_round_action(
|
|
708
|
-
host_health_cfg, defer_to_cgroup, critical_streak
|
|
784
|
+
host_health_cfg, defer_to_cgroup, critical_streak, nudged
|
|
709
785
|
)
|
|
710
|
-
if action == "
|
|
786
|
+
if action == "nudge":
|
|
787
|
+
nudged = True
|
|
788
|
+
proc.terminate() # bare SIGTERM to the leader -- NO wait, NO killpg
|
|
789
|
+
emit_round_mem_terminated(
|
|
790
|
+
log_dir,
|
|
791
|
+
pid=proc.pid,
|
|
792
|
+
severity=pressure.severity,
|
|
793
|
+
signal=pressure.signal,
|
|
794
|
+
message=pressure.message,
|
|
795
|
+
consecutive=critical_streak,
|
|
796
|
+
context=pressure.context,
|
|
797
|
+
tier="nudge",
|
|
798
|
+
)
|
|
799
|
+
elif action == "terminate":
|
|
711
800
|
returncode = _terminate_round(proc, clock=clock)
|
|
712
801
|
emit_round_mem_terminated(
|
|
713
802
|
log_dir,
|
|
@@ -717,10 +806,11 @@ def _spawn_round(
|
|
|
717
806
|
message=pressure.message,
|
|
718
807
|
consecutive=critical_streak,
|
|
719
808
|
context=pressure.context,
|
|
809
|
+
tier="terminate",
|
|
720
810
|
)
|
|
721
811
|
_stash_cgroup()
|
|
722
812
|
return returncode
|
|
723
|
-
|
|
813
|
+
elif action == "defer" and not cgroup_defer_notified:
|
|
724
814
|
emit_mem_pressure_deferred_to_cgroup(
|
|
725
815
|
log_dir,
|
|
726
816
|
pid=proc.pid,
|
|
@@ -736,6 +826,17 @@ def _spawn_round(
|
|
|
736
826
|
# exception-path cleanup: never orphan the round pgroup
|
|
737
827
|
_terminate_round(proc, clock=clock)
|
|
738
828
|
raise
|
|
829
|
+
finally:
|
|
830
|
+
# Unconditional restore (mirrors _stash_cgroup): runs on every exit path out of the try
|
|
831
|
+
# above -- both mid-loop returns, the exception re-raise, and the wedged fall-through
|
|
832
|
+
# below. A skipped write here leaves the cgroup throttled indefinitely. _release_brake's
|
|
833
|
+
# restore is fail-open (never raises); its best-effort emit is shielded here, so a
|
|
834
|
+
# disk-full OSError can never replace the ORIGINAL propagating exception.
|
|
835
|
+
if brake_engaged and brake_previous is not None:
|
|
836
|
+
try:
|
|
837
|
+
_release_brake(log_dir, round_num, brake_previous, reason="round_end")
|
|
838
|
+
except Exception:
|
|
839
|
+
pass
|
|
739
840
|
# Safety kill BEFORE the observability write: the wedged round must not
|
|
740
841
|
# keep burning wall-clock (and, if its own reap hangs, its agent's
|
|
741
842
|
# budget) waiting on the least-reliable step (a disk write) to finish
|
|
@@ -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.5"
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def cfg_from_args(args) -> Config:
|
|
@@ -123,6 +123,10 @@ def emit(value: Any, *, json_mode: bool, cfg: Config | None = None) -> None:
|
|
|
123
123
|
"plugins": plugins_block,
|
|
124
124
|
**_to_jsonable(value),
|
|
125
125
|
}
|
|
126
|
+
if cfg is not None:
|
|
127
|
+
from agent_runner.cli._serve_cgroup import current_brake_state
|
|
128
|
+
|
|
129
|
+
wrapped["brake"] = current_brake_state(cfg)
|
|
126
130
|
print(json.dumps(wrapped, indent=2, default=str))
|
|
127
131
|
else:
|
|
128
132
|
print(json.dumps(_to_jsonable(value), indent=2, default=str))
|
|
@@ -45,20 +45,23 @@ def _plan(cfg, rounds: int) -> list[dict]:
|
|
|
45
45
|
return out
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def _cgroup_report() -> dict:
|
|
48
|
+
def _cgroup_report(cfg) -> dict:
|
|
49
49
|
"""Read-only cgroup delegation-readiness snapshot for `doctor` -- never
|
|
50
50
|
emits an event (unlike serve's `_probe_and_emit_cgroup_defer`, which
|
|
51
51
|
shares the same underlying `metrics` probes but writes
|
|
52
52
|
`host_cgroup_memory_limit`). `doctor` never launches the agent or writes
|
|
53
53
|
anything; this section is the same discipline applied to cgroup state."""
|
|
54
54
|
from agent_runner import metrics
|
|
55
|
+
from agent_runner.cli._serve_cgroup import brake_report_state
|
|
55
56
|
|
|
56
57
|
limits = metrics.cgroup_memory_limits()
|
|
58
|
+
delegated = metrics.cgroup_delegated(self_cgroup=limits["cgroup_path"])
|
|
57
59
|
return {
|
|
58
60
|
"cgroup_path": limits["cgroup_path"],
|
|
59
61
|
"memory_max": limits["memory_max"],
|
|
60
62
|
"memory_high": metrics.cgroup_memory_high(self_cgroup=limits["cgroup_path"]),
|
|
61
|
-
"delegated":
|
|
63
|
+
"delegated": delegated,
|
|
64
|
+
"brake": brake_report_state(cfg.monitor.host_health.brake.memory_high, delegated),
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
|
|
@@ -79,7 +82,7 @@ def cmd_doctor(args) -> int:
|
|
|
79
82
|
overlaps=phase_select.find_phase_window_overlaps(cfg),
|
|
80
83
|
plan=_plan(cfg, args.rounds),
|
|
81
84
|
sandbox=doctor_snapshot(cfg),
|
|
82
|
-
cgroup=_cgroup_report(),
|
|
85
|
+
cgroup=_cgroup_report(cfg),
|
|
83
86
|
sigterm_grace=sigterm_grace,
|
|
84
87
|
)
|
|
85
88
|
json_mode = getattr(args, "json", False)
|
|
@@ -143,7 +146,21 @@ def _format(report: DoctorReport) -> str:
|
|
|
143
146
|
lines.append(f" memory_max: {cgroup['memory_max']}")
|
|
144
147
|
lines.append(f" memory_high: {cgroup['memory_high']}")
|
|
145
148
|
lines.append(f" delegated: {cgroup['delegated']}")
|
|
146
|
-
|
|
149
|
+
lines.append(f" brake: {cgroup['brake']}")
|
|
150
|
+
# Approximates serve's own advisory (_serve_cgroup._probe_and_emit_cgroup_defer):
|
|
151
|
+
# only surface the "soft-brake is inert" hint when it could actually apply --
|
|
152
|
+
# the brake is enabled (report.cgroup["brake"] != "off") or a memory_high/
|
|
153
|
+
# memory_max limit is already bound. Otherwise the brake is at its default
|
|
154
|
+
# (off) with nothing bound, and the hint would contradict "brake: off" above.
|
|
155
|
+
# Coarser than serve's own_scope (which additionally requires the bound to
|
|
156
|
+
# originate from the caller's OWN leaf, not an inherited ancestor) -- doctor's
|
|
157
|
+
# _cgroup_report never tracks the bounding cgroup path, so this errs toward
|
|
158
|
+
# showing the readiness hint in the ancestor-bound case.
|
|
159
|
+
if cgroup["delegated"] is False and (
|
|
160
|
+
cgroup["brake"] != "off"
|
|
161
|
+
or cgroup["memory_high"] is not None
|
|
162
|
+
or cgroup["memory_max"] is not None
|
|
163
|
+
):
|
|
147
164
|
from agent_runner.cli._serve_cgroup import _UNDELEGATED_HINT
|
|
148
165
|
|
|
149
166
|
lines.append(f" hint: {_UNDELEGATED_HINT}")
|
|
@@ -744,7 +744,8 @@ def cmd(args) -> int:
|
|
|
744
744
|
work_dir = cfg.runtime.work_dir
|
|
745
745
|
# Emitted exactly once per serve lifetime -- the per-round defer decision below
|
|
746
746
|
# reuses this bool rather than re-probing/re-emitting every round.
|
|
747
|
-
|
|
747
|
+
brake_memory_high = cfg.monitor.host_health.brake.memory_high
|
|
748
|
+
cgroup_probe_defer = _probe_and_emit_cgroup_defer(log_dir, brake_memory_high=brake_memory_high)
|
|
748
749
|
# Static config check -- phase-window collisions can't change mid-lifetime, so
|
|
749
750
|
# (like the cgroup probe above) this runs once at boot, not once per round.
|
|
750
751
|
overlaps = phase_select.find_phase_window_overlaps(cfg)
|