cli-agent-runner 0.2.15__tar.gz → 0.2.16__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/.vulture-whitelist.py +4 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/CHANGELOG.md +19 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/PKG-INFO +2 -2
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/README.md +1 -1
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_emit.py +143 -3
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_serve_policy.py +62 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_throttle.py +105 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/api.py +6 -0
- cli_agent_runner-0.2.16/agent_runner/cli/_serve_round.py +442 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/serve_cmd.py +57 -263
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/config/models.py +15 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/config/parsers.py +17 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/config/validators.py +12 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/defenses.py +23 -2
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/events.py +5 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/host_health.py +14 -9
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/metrics.py +108 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/round_log.py +10 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/service_unit.py +7 -3
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/architecture.md +15 -7
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/commands.md +1 -1
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/configuration.md +5 -1
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/migrations/0.2.md +76 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/runbook.md +54 -22
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/contract/test_public_api_surface.py +8 -0
- cli_agent_runner-0.2.16/tests/integration/test_spawn_round_mem_floor.py +441 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_architecture.py +107 -23
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_event_kinds_ssot.py +7 -1
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_module_boundaries.py +1 -1
- cli_agent_runner-0.2.16/tests/unit/test_cgroup_probe.py +233 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_config.py +75 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_defenses.py +2 -2
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_host_health.py +13 -0
- cli_agent_runner-0.2.16/tests/unit/test_serve_crash_loop.py +640 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_serve_mem_gate.py +25 -1
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_serve_policy.py +42 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_service_unit.py +27 -2
- cli_agent_runner-0.2.15/tests/integration/test_spawn_round_mem_floor.py +0 -224
- cli_agent_runner-0.2.15/tests/unit/test_serve_crash_loop.py +0 -226
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/.codecov.yml +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/.gitignore +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/BACKLOG.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/LICENSE +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/README.zh.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/SECURITY.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_monitor_state.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/agent_runtime.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/config/__init__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/config/loader.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/phase_select.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/runner.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/build.sh +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/README.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/events.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/plugins.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/docs/thesis.md +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/pyproject.toml +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/__init__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/_clock.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/conftest.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_auto_stop_e2e.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_spawn_round_wedged.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_no_vacuous_invariants.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_timeout_budget_invariant.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_agent_runtime_reap.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_config_nested_strictness.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_events.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_example_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_git_timeout.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_monitor_dedup_rearm.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_phase_select.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_primary_prompt_file_home.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.2.15 → cli_agent_runner-0.2.16}/tests/unit/test_vcs_state.py +0 -0
|
@@ -150,6 +150,10 @@ disk_warning_pct
|
|
|
150
150
|
disk_critical_pct
|
|
151
151
|
swap_sout_noise_floor_mb
|
|
152
152
|
mem_free_low_mb
|
|
153
|
+
psi_full_avg10_critical
|
|
154
|
+
psi_some_avg10_warning
|
|
155
|
+
mem_critical_consecutive_samples
|
|
156
|
+
in_round_mem_terminate
|
|
153
157
|
|
|
154
158
|
# agent_runner.config.models.PhaseOverride
|
|
155
159
|
round_timeout_s
|
|
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.16] - 2026-09-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `[monitor.host_health]` gains PSI knobs — `psi_full_avg10_critical` (default 60.0) and `psi_some_avg10_warning` (default 5.0) — plus `mem_critical_consecutive_samples` (default 3) and an `in_round_mem_terminate` off switch for the mid-round memory floor.
|
|
12
|
+
- `serve` probes this process's cgroup v2 memory budget at startup (`host_cgroup_memory_limit`); when `memory.max` and `memory.swap.max` are both finite, the mid-round floor defers to kernel cgroup-OOM (`mem_pressure_deferred_to_cgroup`) instead of terminating the round itself.
|
|
13
|
+
- `mem_loop` now escalates across restarts: recurring 3 times inside a rolling 2h window stops the service for real (`mem_loop_persistent`, exit `70`) instead of respawning into the same loop forever.
|
|
14
|
+
- New exit-0 no-progress breaker: 5 consecutive fast, clean rounds with no recorded usage (a CLI that exits 0 without ever reaching the model) now stop `serve` (`stalled_no_progress`, exit `75`) instead of spinning invisibly.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- PSI-full critical was a single ≥1% momentary sample — a hiccup, not a coma — on every PSI-capable host. It now requires a sustained ≥60% avg10 held for `mem_critical_consecutive_samples` (default 3, ~30s) consecutive checks.
|
|
18
|
+
- The mid-round swap-out check is now per-tick instead of cumulative since round-start, so a host that recovers mid-round is no longer permanently flagged for the rest of the round.
|
|
19
|
+
- The pre-round admission gate now defers only on `critical` pressure, not on any pressure (a mere `warning`, e.g. ordinary swap churn, no longer delays the next round).
|
|
20
|
+
|
|
21
|
+
### Notes
|
|
22
|
+
- New events: `host_cgroup_memory_limit`, `mem_pressure_deferred_to_cgroup`, `mem_loop_persistent`, `stalled_no_progress`, `round_mem_critical_sample`; `round_mem_terminated` gained `consecutive`/`context` fields. Not breaking: no `agent-runner migrate` needed, `peek --json` schema is unchanged.
|
|
23
|
+
- Re-render the systemd unit (`agent-runner install`) after upgrading: the new unit adds exit `70` to `RestartPreventExitStatus` so `mem_loop_persistent` stops the service instead of restarting into the same loop.
|
|
24
|
+
|
|
25
|
+
See `docs/migrations/0.2.md`.
|
|
26
|
+
|
|
8
27
|
## [0.2.15] - 2026-09-04
|
|
9
28
|
|
|
10
29
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.16
|
|
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
|
|
@@ -94,7 +94,7 @@ Verb reference: [`docs/commands.md`](docs/commands.md).
|
|
|
94
94
|
|
|
95
95
|
## Defenses (built in)
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
15 named defenses, structured as data — see `agent-runner peek --select defenses`.
|
|
98
98
|
Each carries the historical incident it codifies and the invariant test that
|
|
99
99
|
guards it. Highlights:
|
|
100
100
|
|
|
@@ -58,7 +58,7 @@ Verb reference: [`docs/commands.md`](docs/commands.md).
|
|
|
58
58
|
|
|
59
59
|
## Defenses (built in)
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
15 named defenses, structured as data — see `agent-runner peek --select defenses`.
|
|
62
62
|
Each carries the historical incident it codifies and the invariant test that
|
|
63
63
|
guards it. Highlights:
|
|
64
64
|
|
|
@@ -19,13 +19,17 @@ __all__ = [
|
|
|
19
19
|
"emit_anomaly_repetitive_tool",
|
|
20
20
|
"emit_config_migrated",
|
|
21
21
|
"emit_fresh_eyes_round_triggered",
|
|
22
|
+
"emit_host_cgroup_memory_limit",
|
|
22
23
|
"emit_max_rounds_reached",
|
|
23
24
|
"emit_mem_loop",
|
|
25
|
+
"emit_mem_loop_persistent",
|
|
26
|
+
"emit_mem_pressure_deferred_to_cgroup",
|
|
24
27
|
"emit_rate_limit_stop",
|
|
25
28
|
"emit_round_deferred",
|
|
26
29
|
"emit_round_grace_extended",
|
|
27
30
|
"emit_round_grace_kill",
|
|
28
31
|
"emit_round_logs_prune_deferred",
|
|
32
|
+
"emit_round_mem_critical_sample",
|
|
29
33
|
"emit_round_mem_terminated",
|
|
30
34
|
"emit_round_progress",
|
|
31
35
|
"emit_round_resumed",
|
|
@@ -36,6 +40,7 @@ __all__ = [
|
|
|
36
40
|
"emit_schedule_phase_skipped",
|
|
37
41
|
"emit_schedule_resumed",
|
|
38
42
|
"emit_stale_index_lock_cleared",
|
|
43
|
+
"emit_stalled_no_progress",
|
|
39
44
|
"emit_stop_file_detected",
|
|
40
45
|
"emit_transient_error_backoff_capped",
|
|
41
46
|
"emit_transient_error_detected",
|
|
@@ -110,6 +115,60 @@ def emit_mem_loop(log_dir: Path, *, consecutive: int, exit_code: int, log_path:
|
|
|
110
115
|
_emit_giveup(log_dir, MEM_LOOP, consecutive=consecutive, exit_code=exit_code, log_path=log_path)
|
|
111
116
|
|
|
112
117
|
|
|
118
|
+
def emit_mem_loop_persistent(
|
|
119
|
+
log_dir: Path, *, consecutive: int, exit_code: int, log_path: Path
|
|
120
|
+
) -> None:
|
|
121
|
+
"""Emit mem_loop_persistent (serve STOPS for real — 0.2.16 Task 5 cross-restart
|
|
122
|
+
convergence: mem_loop itself kept recurring across restarts within the
|
|
123
|
+
escalation window, so systemd is told to stop rather than respawn into the
|
|
124
|
+
identical loop forever). Distinct from mem_loop: this IS a deliberate stop,
|
|
125
|
+
like crash_loop/config_broken, so it belongs in the unit's
|
|
126
|
+
RestartPreventExitStatus.
|
|
127
|
+
|
|
128
|
+
``consecutive`` counts mem_loop EPISODES within the persistence window
|
|
129
|
+
(up to ``_serve_policy._MEM_LOOP_PERSIST_THRESHOLD``) — NOT mid-round
|
|
130
|
+
terminations within one episode, which is mem_loop's own ``consecutive``
|
|
131
|
+
(up to ``MEM_LOOP_THRESHOLD``). Same redacted-log-tail reason capture as
|
|
132
|
+
crash_loop/mem_loop."""
|
|
133
|
+
from agent_runner.events import MEM_LOOP_PERSISTENT
|
|
134
|
+
|
|
135
|
+
_emit_giveup(
|
|
136
|
+
log_dir,
|
|
137
|
+
MEM_LOOP_PERSISTENT,
|
|
138
|
+
consecutive=consecutive,
|
|
139
|
+
exit_code=exit_code,
|
|
140
|
+
log_path=log_path,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def emit_stalled_no_progress(
|
|
145
|
+
log_dir: Path, *, consecutive: int, exit_code: int, log_path: Path
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Emit stalled_no_progress (serve gave up after consecutive clean-but-
|
|
148
|
+
no-progress rounds -- 0.2.16 Task 6). A round that exits 0 fast with no
|
|
149
|
+
``agent_usage_recorded`` never reached the model (pi, and CLIs like it,
|
|
150
|
+
exit 0 on a provider failure) -- ``_round_ok = exit_code == 0``
|
|
151
|
+
(api_types.py) reads that as clean, so without this breaker it is a fast,
|
|
152
|
+
invisible, unclassified-failure spin, no different in kind from an
|
|
153
|
+
unknown short crash except for the exit code it hides behind.
|
|
154
|
+
|
|
155
|
+
Deliberately reuses ``CRASH_LOOP_EXIT`` (75), not a new exit code: this is
|
|
156
|
+
the SAME give-up verdict as crash_loop ("an unknown failure kept
|
|
157
|
+
recurring, stop for real") reached via a different signal (no usage
|
|
158
|
+
instead of a non-zero exit) -- not a new failure class, so it needs no new
|
|
159
|
+
entry in the systemd unit's ``RestartPreventExitStatus``. Same redacted-
|
|
160
|
+
log-tail reason capture as crash_loop/mem_loop."""
|
|
161
|
+
from agent_runner.events import STALLED_NO_PROGRESS
|
|
162
|
+
|
|
163
|
+
_emit_giveup(
|
|
164
|
+
log_dir,
|
|
165
|
+
STALLED_NO_PROGRESS,
|
|
166
|
+
consecutive=consecutive,
|
|
167
|
+
exit_code=exit_code,
|
|
168
|
+
log_path=log_path,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
113
172
|
def emit_config_migrated(
|
|
114
173
|
log_dir: Path, *, applied: list[str], manual: list[str], path: str
|
|
115
174
|
) -> None:
|
|
@@ -202,15 +261,96 @@ def emit_round_resumed(log_dir: Path, *, deferred_for_s: int) -> None:
|
|
|
202
261
|
|
|
203
262
|
|
|
204
263
|
def emit_round_mem_terminated(
|
|
205
|
-
log_dir: Path,
|
|
264
|
+
log_dir: Path,
|
|
265
|
+
*,
|
|
266
|
+
pid: int,
|
|
267
|
+
severity: str,
|
|
268
|
+
signal: str,
|
|
269
|
+
message: str,
|
|
270
|
+
consecutive: int,
|
|
271
|
+
context: dict,
|
|
206
272
|
) -> None:
|
|
207
273
|
"""Emit when _spawn_round's mid-round hard floor terminated a ballooning
|
|
208
274
|
round on critical host_health pressure -- the actual coma-preventer (a
|
|
209
275
|
pre-round-only gate can't stop a single round mid-flight). Distinct from
|
|
210
|
-
round_supervisor_wedged (a wall-clock ceiling breach, unrelated cause).
|
|
276
|
+
round_supervisor_wedged (a wall-clock ceiling breach, unrelated cause).
|
|
277
|
+
|
|
278
|
+
0.2.16: ``consecutive`` (the critical_streak that crossed the threshold)
|
|
279
|
+
and ``context`` (Pressure.context -- the actual psi/mem numbers, e.g.
|
|
280
|
+
psi_full_avg10) make the kill legible from the event stream alone, so an
|
|
281
|
+
operator can retune host_health thresholds without SSH."""
|
|
211
282
|
from agent_runner.events import ROUND_MEM_TERMINATED, emit
|
|
212
283
|
|
|
213
|
-
emit(
|
|
284
|
+
emit(
|
|
285
|
+
log_dir,
|
|
286
|
+
ROUND_MEM_TERMINATED,
|
|
287
|
+
pid=pid,
|
|
288
|
+
severity=severity,
|
|
289
|
+
signal=signal,
|
|
290
|
+
message=message,
|
|
291
|
+
consecutive=consecutive,
|
|
292
|
+
context=context,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def emit_round_mem_critical_sample(
|
|
297
|
+
log_dir: Path, *, round_num: int, consecutive: int, context: dict
|
|
298
|
+
) -> None:
|
|
299
|
+
"""Emit on EACH critical host_health sample inside _spawn_round's mid-round
|
|
300
|
+
hard floor -- deliberately NOT deduped (unlike round_mem_terminated): the
|
|
301
|
+
point is calibration visibility into near-misses, so an operator watching
|
|
302
|
+
the event stream sees the critical_streak build (1, 2, ...) even on ticks
|
|
303
|
+
that never reach the terminate threshold (a healthy tick resets it before
|
|
304
|
+
3-in-a-row). ``context`` carries the same Pressure.context numbers as
|
|
305
|
+
round_mem_terminated. Fires only during critical pressure -- never during
|
|
306
|
+
warning/healthy -- so it adds no normal-operation noise, including while
|
|
307
|
+
``defer_to_cgroup`` steps back from terminating (the streak/context is
|
|
308
|
+
still useful calibration there; this event stays scoped to the
|
|
309
|
+
sample-level signal, distinct from mem_pressure_deferred_to_cgroup's
|
|
310
|
+
once-per-episode terminate-vs-defer notice)."""
|
|
311
|
+
from agent_runner.events import ROUND_MEM_CRITICAL_SAMPLE, emit
|
|
312
|
+
|
|
313
|
+
emit(
|
|
314
|
+
log_dir,
|
|
315
|
+
ROUND_MEM_CRITICAL_SAMPLE,
|
|
316
|
+
round_num=round_num,
|
|
317
|
+
consecutive=consecutive,
|
|
318
|
+
context=context,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def emit_host_cgroup_memory_limit(
|
|
323
|
+
log_dir: Path, *, memory_max: int | None, memory_swap_max: int | None, cgroup_path: str | None
|
|
324
|
+
) -> None:
|
|
325
|
+
"""Emit once at serve startup: this process's cgroup v2 memory budget
|
|
326
|
+
(``metrics.cgroup_memory_limits``). ``None`` fields mean unlimited (or
|
|
327
|
+
cgroup v2 unavailable). Serve uses this once to decide whether the
|
|
328
|
+
mid-round hard floor can defer to kernel cgroup-OOM -- see
|
|
329
|
+
``emit_mem_pressure_deferred_to_cgroup`` below."""
|
|
330
|
+
from agent_runner.events import HOST_CGROUP_MEMORY_LIMIT, emit
|
|
331
|
+
|
|
332
|
+
emit(
|
|
333
|
+
log_dir,
|
|
334
|
+
HOST_CGROUP_MEMORY_LIMIT,
|
|
335
|
+
memory_max=memory_max,
|
|
336
|
+
memory_swap_max=memory_swap_max,
|
|
337
|
+
cgroup_path=cgroup_path,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def emit_mem_pressure_deferred_to_cgroup(
|
|
342
|
+
log_dir: Path, *, pid: int, signal: str, message: str
|
|
343
|
+
) -> None:
|
|
344
|
+
"""Emit when _spawn_round's mid-round hard floor hits sustained critical
|
|
345
|
+
pressure but this cgroup's (mem+swap) budget is bounded end to end
|
|
346
|
+
(both memory.max and memory.swap.max finite) -- kernel cgroup-OOM will
|
|
347
|
+
contain the agent and keep the host responsive on its own, so the
|
|
348
|
+
cruder host-wide round-kill steps back instead of firing
|
|
349
|
+
round_mem_terminated. This OVERRIDES in_round_mem_terminate=True: a
|
|
350
|
+
bounded cgroup makes the host floor strictly worse, not just redundant."""
|
|
351
|
+
from agent_runner.events import MEM_PRESSURE_DEFERRED_TO_CGROUP, emit
|
|
352
|
+
|
|
353
|
+
emit(log_dir, MEM_PRESSURE_DEFERRED_TO_CGROUP, pid=pid, signal=signal, message=message)
|
|
214
354
|
|
|
215
355
|
|
|
216
356
|
def emit_round_substrate_before(
|
|
@@ -54,6 +54,46 @@ MEM_LOOP_EXIT = 71
|
|
|
54
54
|
# Consecutive mem-terminated rounds before serve gives up (break-then-restart).
|
|
55
55
|
MEM_LOOP_THRESHOLD = 5
|
|
56
56
|
|
|
57
|
+
# Exit code for mem_loop's cross-restart escalation (0.2.16 Task 5 — field
|
|
58
|
+
# confirmed: NRestarts climbs, never converges). MEM_LOOP_EXIT (71) alone
|
|
59
|
+
# resets on every process restart, so a host stuck in sustained pressure
|
|
60
|
+
# respawns into the identical break-then-restart loop forever. Once mem_loop
|
|
61
|
+
# itself keeps recurring across restarts (see _MEM_LOOP_PERSIST_THRESHOLD),
|
|
62
|
+
# this is a DELIBERATE stop instead — "gave up for real, needs a human" — so,
|
|
63
|
+
# unlike 71, it IS listed in the unit's RestartPreventExitStatus. 70 =
|
|
64
|
+
# EX_SOFTWARE (sysexits); verified free (grep -rnE "= 70\b|code=70\b").
|
|
65
|
+
MEM_LOOP_PERSISTENT_EXIT = 70
|
|
66
|
+
|
|
67
|
+
# Escalation window/threshold for the cross-restart convergence above: prior
|
|
68
|
+
# mem_loop events are counted only within the last _MEM_LOOP_PERSIST_WINDOW_S
|
|
69
|
+
# seconds (2h) of "now" — old episodes age out on their own, so a host that
|
|
70
|
+
# stays healthy for a sustained window earns a fresh restartable 71 again
|
|
71
|
+
# instead of ratcheting toward the persistent stop forever (no state file:
|
|
72
|
+
# purely events-tail derived, restart-safe by construction). Once this run's
|
|
73
|
+
# mem_loop would be the _MEM_LOOP_PERSIST_THRESHOLD-th (3rd) occurrence inside
|
|
74
|
+
# the window (~2 prior + this one, ⇒ ~15 mem-terminated kills total), serve
|
|
75
|
+
# escalates to MEM_LOOP_PERSISTENT_EXIT instead of the usual 71.
|
|
76
|
+
_MEM_LOOP_PERSIST_WINDOW_S = 7200
|
|
77
|
+
_MEM_LOOP_PERSIST_THRESHOLD = 3
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Exit-0 no-progress breaker (0.2.16 Task 6). Some CLIs (pi -- see
|
|
81
|
+
# builtin_plugins/pi.py's "pi exits 0 on provider failure") exit 0 on a
|
|
82
|
+
# provider failure that never reaches the model: `_round_ok = exit_code == 0`
|
|
83
|
+
# (api_types.py) reads that as a clean round, so without this breaker an
|
|
84
|
+
# invalid credential (or an exhausted-retries outage) spins as a fast,
|
|
85
|
+
# invisible "success" loop with no breaker, no back-off, and no alert -- on a
|
|
86
|
+
# constrained host, that tight loop is itself a memory-pressure generator.
|
|
87
|
+
# "No progress" = exit 0, a SHORT round (< _NO_PROGRESS_SHORT_S), and no
|
|
88
|
+
# agent_usage_recorded for it (see _throttle.round_had_no_progress for the
|
|
89
|
+
# events-derived scoping). Deliberately reuses CRASH_LOOP_THRESHOLD /
|
|
90
|
+
# CRASH_LOOP_EXIT rather than minting new ones: this is the SAME give-up
|
|
91
|
+
# verdict as the crash-loop breaker ("an unknown failure kept recurring, stop
|
|
92
|
+
# for real"), reached via a different signal (no usage instead of a non-zero
|
|
93
|
+
# exit) -- not a new failure class needing its own systemd
|
|
94
|
+
# RestartPreventExitStatus entry.
|
|
95
|
+
_NO_PROGRESS_SHORT_S = 30
|
|
96
|
+
|
|
57
97
|
|
|
58
98
|
class EnvironmentalError(Exception):
|
|
59
99
|
"""Marks a round-child failure as ENVIRONMENTAL (recoverable) rather than a
|
|
@@ -136,6 +176,28 @@ def _mem_loop_decision(
|
|
|
136
176
|
return ("continue", consecutive)
|
|
137
177
|
|
|
138
178
|
|
|
179
|
+
def _no_progress_decision(
|
|
180
|
+
*, no_progress: bool, consecutive: int
|
|
181
|
+
) -> tuple[Literal["stalled_no_progress", "continue"], int]:
|
|
182
|
+
"""Exit-0 no-progress give-up cap (0.2.16 Task 6) -- a NEW, SEPARATE,
|
|
183
|
+
private counter from ``post_round_decision``'s crash-loop breaker (that one
|
|
184
|
+
keys on ``returncode != 0``; here returncode IS 0, so a round that never
|
|
185
|
+
reached the model would otherwise sail through with no breaker at all).
|
|
186
|
+
Same shape as :func:`_mem_loop_decision`: returns ``("stalled_no_progress",
|
|
187
|
+
consecutive)`` once ``consecutive`` reaches ``CRASH_LOOP_THRESHOLD`` --
|
|
188
|
+
reusing that threshold (and, at the call site, ``CRASH_LOOP_EXIT``) rather
|
|
189
|
+
than minting new ones, since this is the identical give-up verdict reached
|
|
190
|
+
via a different signal. A round WITH progress (usage recorded, non-zero
|
|
191
|
+
exit, or slow) resets the counter to 0, same reset shape as
|
|
192
|
+
crash-loop/mem-loop."""
|
|
193
|
+
if not no_progress:
|
|
194
|
+
return ("continue", 0)
|
|
195
|
+
consecutive += 1
|
|
196
|
+
if consecutive >= CRASH_LOOP_THRESHOLD:
|
|
197
|
+
return ("stalled_no_progress", consecutive)
|
|
198
|
+
return ("continue", consecutive)
|
|
199
|
+
|
|
200
|
+
|
|
139
201
|
def classify_round_exit(exc: BaseException) -> int:
|
|
140
202
|
"""The round-child exit-code CLASSIFIER (Group A) — permanence, not exception
|
|
141
203
|
identity, decides serve's response. Replaces the old exception *whitelist*
|
|
@@ -22,6 +22,7 @@ from agent_runner.api_types import TransientErrorState
|
|
|
22
22
|
from agent_runner.clock import SYSTEM_CLOCK, Clock
|
|
23
23
|
from agent_runner.events import (
|
|
24
24
|
AGENT_USAGE_RECORDED,
|
|
25
|
+
MEM_LOOP,
|
|
25
26
|
ROUND_MEM_TERMINATED,
|
|
26
27
|
ROUND_SUBSTRATE_BEFORE,
|
|
27
28
|
TRANSIENT_ERROR_DETECTED,
|
|
@@ -37,7 +38,9 @@ __all__ = [
|
|
|
37
38
|
"_interruptible_sleep",
|
|
38
39
|
"compute_adjusted_reset_at",
|
|
39
40
|
"effective_throttle_view",
|
|
41
|
+
"mem_loop_events_in_window",
|
|
40
42
|
"pending_recovered",
|
|
43
|
+
"round_had_no_progress",
|
|
41
44
|
"round_was_mem_terminated",
|
|
42
45
|
]
|
|
43
46
|
|
|
@@ -314,6 +317,108 @@ def round_was_mem_terminated(log_dir: Path) -> bool:
|
|
|
314
317
|
return parse_iso_ms(newest_terminated_ts) >= parse_iso_ms(newest_before_ts)
|
|
315
318
|
|
|
316
319
|
|
|
320
|
+
def round_had_no_progress(
|
|
321
|
+
log_dir: Path,
|
|
322
|
+
*,
|
|
323
|
+
returncode: int,
|
|
324
|
+
duration_s: float,
|
|
325
|
+
threshold_s: float,
|
|
326
|
+
throttle_active: bool = False,
|
|
327
|
+
) -> bool:
|
|
328
|
+
"""True iff the round that JUST ran exited 0, finished fast, but never
|
|
329
|
+
reached the model -- pi (and CLIs like it: see builtin_plugins/pi.py's
|
|
330
|
+
"pi exits 0 on provider failure") can exit 0 on an auth failure or an
|
|
331
|
+
exhausted-retries outage that ``_round_ok = exit_code == 0`` (api_types.py)
|
|
332
|
+
reads as clean, so this needs its own events-derived signal (feeding
|
|
333
|
+
``_serve_policy._no_progress_decision``'s "clean-but-no-progress" streak)
|
|
334
|
+
exactly parallel to :func:`round_was_mem_terminated`'s mem-terminated one.
|
|
335
|
+
|
|
336
|
+
TWO gates guard the verdict against over-firing on healthy deployments
|
|
337
|
+
(0.2.16 fix-wave CRITICAL #1):
|
|
338
|
+
|
|
339
|
+
1. ``throttle_active`` (the SAME value ``cmd()`` already computed for
|
|
340
|
+
``post_round_decision`` -- caller threads it through, never
|
|
341
|
+
recomputed here): a provider outage that exhausts retries and exits 0
|
|
342
|
+
(429/503) is ``transient_error_detected`` and excused from the
|
|
343
|
+
crash-loop breaker; without this gate it would be double-counted here
|
|
344
|
+
as "no progress" and stop the loop instead of riding the back-off.
|
|
345
|
+
2. Usage-capability: armed ONLY when at least one ``agent_usage_recorded``
|
|
346
|
+
event exists anywhere in the scanned tail. Some CLIs (kimi -- see
|
|
347
|
+
builtin_plugins/kimi.py; aider; any custom ``[agent] command`` with no
|
|
348
|
+
usage-emitting plugin) never emit usage BY DESIGN -- for those, "no
|
|
349
|
+
usage this round" is indistinguishable from "normal for this CLI", so
|
|
350
|
+
arming would stop a perfectly healthy deployment after 5 fast clean
|
|
351
|
+
rounds. If the CLI's plugin stack ever emits usage (pi does on a good
|
|
352
|
+
round), a round with none is genuine no-progress and still trips --
|
|
353
|
+
this makes the breaker CLI-adaptive with no config descriptor.
|
|
354
|
+
|
|
355
|
+
Short-circuits on ``returncode != 0`` or ``duration_s >= threshold_s``
|
|
356
|
+
without touching the events tail: a non-zero exit already has its own
|
|
357
|
+
crash-loop signal, and a slow round (even with no usage) is not a TIGHT
|
|
358
|
+
loop -- a wedged/hung round already has its own signal
|
|
359
|
+
(``round_supervisor_wedged``), so this floor is specifically the fast spin.
|
|
360
|
+
|
|
361
|
+
"Never reached the model" = no ``agent_usage_recorded`` event stamped at
|
|
362
|
+
or after this round's ``round_substrate_before`` -- the CLI plugins only
|
|
363
|
+
emit usage when the round actually consumed tokens (e.g.
|
|
364
|
+
``builtin_plugins/pi.py``'s ``_aggregate_usage`` returns ``None`` on
|
|
365
|
+
all-zero usage), so an auth failure or exhausted retries leaves no such
|
|
366
|
+
event. Same round-scoping shape as :func:`round_was_mem_terminated`
|
|
367
|
+
(newest-event-timestamp comparison against the newest
|
|
368
|
+
``round_substrate_before``), with the comparison inverted: no progress
|
|
369
|
+
means the newest usage event is EITHER absent OR older than this round's
|
|
370
|
+
own start."""
|
|
371
|
+
if throttle_active or returncode != 0 or duration_s >= threshold_s:
|
|
372
|
+
return False
|
|
373
|
+
newest_before_ts: str | None = None
|
|
374
|
+
newest_usage_ts: str | None = None
|
|
375
|
+
usage_capable = False
|
|
376
|
+
for path in sorted(log_dir.glob("events-*.jsonl"))[-2:]:
|
|
377
|
+
for ev in _iter_events(path):
|
|
378
|
+
kind = ev.get("event")
|
|
379
|
+
if kind == AGENT_USAGE_RECORDED:
|
|
380
|
+
usage_capable = True
|
|
381
|
+
ts = ev.get("ts")
|
|
382
|
+
if not ts:
|
|
383
|
+
continue
|
|
384
|
+
if kind == ROUND_SUBSTRATE_BEFORE:
|
|
385
|
+
newest_before_ts = ts
|
|
386
|
+
elif kind == AGENT_USAGE_RECORDED:
|
|
387
|
+
newest_usage_ts = ts
|
|
388
|
+
if not usage_capable:
|
|
389
|
+
return False # this CLI/plugin stack has never emitted usage -- can't arm
|
|
390
|
+
if newest_before_ts is None:
|
|
391
|
+
return False
|
|
392
|
+
if newest_usage_ts is None:
|
|
393
|
+
return True
|
|
394
|
+
return parse_iso_ms(newest_usage_ts) < parse_iso_ms(newest_before_ts)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def mem_loop_events_in_window(log_dir: Path, clock: Clock, window_s: int) -> int:
|
|
398
|
+
"""Count ``mem_loop`` events in the events tail stamped within the last
|
|
399
|
+
``window_s`` seconds of ``clock.epoch()`` — events-derived, no state file,
|
|
400
|
+
same tail-reconstruction shape as :func:`round_was_mem_terminated` above.
|
|
401
|
+
|
|
402
|
+
Feeds ``_serve_round.round_outcome_exit_code``'s cross-restart escalation
|
|
403
|
+
(0.2.16 Task 5): ``MEM_LOOP_EXIT`` (71) alone resets on every serve
|
|
404
|
+
process restart, so a host stuck in sustained pressure respawns into the
|
|
405
|
+
identical loop forever (field-confirmed: NRestarts climbs, never
|
|
406
|
+
converges). Counting PRIOR occurrences in a bounded window — rather than
|
|
407
|
+
an all-time total, or a restart-local counter — gives "ages out on its
|
|
408
|
+
own after a sustained-healthy stretch" for free: an old mem_loop episode
|
|
409
|
+
outside the window simply stops counting, no explicit reset needed."""
|
|
410
|
+
cutoff = clock.epoch() - window_s
|
|
411
|
+
count = 0
|
|
412
|
+
for path in sorted(log_dir.glob("events-*.jsonl"))[-2:]:
|
|
413
|
+
for ev in _iter_events(path):
|
|
414
|
+
if ev.get("event") != MEM_LOOP:
|
|
415
|
+
continue
|
|
416
|
+
ts = ev.get("ts")
|
|
417
|
+
if ts and parse_iso_ms(ts).timestamp() >= cutoff:
|
|
418
|
+
count += 1
|
|
419
|
+
return count
|
|
420
|
+
|
|
421
|
+
|
|
317
422
|
def effective_throttle_view(
|
|
318
423
|
log_dir: Path, *, clock: Clock = SYSTEM_CLOCK
|
|
319
424
|
) -> tuple[TransientErrorState | None, dict[str, TransientErrorState]]:
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.16'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 16)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -29,6 +29,7 @@ from agent_runner._serve_policy import ( # noqa: F401 — public re-export
|
|
|
29
29
|
CRASH_LOOP_THRESHOLD,
|
|
30
30
|
ENV_BATTERY_EXIT,
|
|
31
31
|
MEM_LOOP_EXIT,
|
|
32
|
+
MEM_LOOP_PERSISTENT_EXIT,
|
|
32
33
|
PERMANENT_CONFIG_EXIT,
|
|
33
34
|
post_round_decision,
|
|
34
35
|
)
|
|
@@ -786,13 +787,17 @@ from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-ex
|
|
|
786
787
|
emit_config_migrated,
|
|
787
788
|
emit_crash_loop,
|
|
788
789
|
emit_fresh_eyes_round_triggered,
|
|
790
|
+
emit_host_cgroup_memory_limit,
|
|
789
791
|
emit_max_rounds_reached,
|
|
790
792
|
emit_mem_loop,
|
|
793
|
+
emit_mem_loop_persistent,
|
|
794
|
+
emit_mem_pressure_deferred_to_cgroup,
|
|
791
795
|
emit_rate_limit_stop,
|
|
792
796
|
emit_round_deferred,
|
|
793
797
|
emit_round_grace_extended,
|
|
794
798
|
emit_round_grace_kill,
|
|
795
799
|
emit_round_logs_prune_deferred,
|
|
800
|
+
emit_round_mem_critical_sample,
|
|
796
801
|
emit_round_mem_terminated,
|
|
797
802
|
emit_round_progress,
|
|
798
803
|
emit_round_resumed,
|
|
@@ -803,6 +808,7 @@ from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-ex
|
|
|
803
808
|
emit_schedule_phase_skipped,
|
|
804
809
|
emit_schedule_resumed,
|
|
805
810
|
emit_stale_index_lock_cleared,
|
|
811
|
+
emit_stalled_no_progress,
|
|
806
812
|
emit_stop_file_detected,
|
|
807
813
|
emit_transient_error_backoff_capped,
|
|
808
814
|
emit_transient_error_detected,
|