cli-agent-runner 0.2.16__tar.gz → 0.2.17__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.16 → cli_agent_runner-0.2.17}/.vulture-whitelist.py +7 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/CHANGELOG.md +12 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/PKG-INFO +1 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_emit.py +17 -7
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_serve_policy.py +32 -13
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_throttle.py +161 -72
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/api.py +20 -3
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/_serve_round.py +160 -68
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/serve_cmd.py +65 -72
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/metrics.py +18 -13
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/monitor.py +62 -16
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/round_log.py +0 -10
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/runbook.md +1 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_spawn_round_mem_floor.py +110 -3
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_spawn_round_wedged.py +5 -4
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_architecture.py +12 -21
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_event_kinds_ssot.py +4 -2
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_timeout_budget_invariant.py +1 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_api_service.py +3 -3
- cli_agent_runner-0.2.17/tests/unit/test_monitor_dedup_rearm.py +104 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_monitor_remote.py +37 -0
- cli_agent_runner-0.2.17/tests/unit/test_round_outcome.py +319 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_serve_policy.py +25 -0
- cli_agent_runner-0.2.17/tests/unit/test_serve_round.py +34 -0
- cli_agent_runner-0.2.16/tests/unit/test_monitor_dedup_rearm.py +0 -42
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/.codecov.yml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/.gitignore +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/BACKLOG.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/LICENSE +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/README.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/README.zh.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/SECURITY.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_monitor_state.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/agent_runtime.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/config/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/config/loader.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/config/models.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/config/parsers.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/config/validators.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/events.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/host_health.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/phase_select.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/runner.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/build.sh +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/README.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/architecture.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/commands.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/configuration.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/events.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/migrations/0.2.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/plugins.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/docs/thesis.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/pyproject.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/_clock.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/conftest.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/contract/test_public_api_surface.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_auto_stop_e2e.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_no_vacuous_invariants.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_agent_runtime_reap.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_cgroup_probe.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_config.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_config_nested_strictness.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_events.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_example_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_git_timeout.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_host_health.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_phase_select.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_primary_prompt_file_home.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.17}/tests/unit/test_vcs_state.py +0 -0
|
@@ -13,6 +13,13 @@ log_dir
|
|
|
13
13
|
offsets
|
|
14
14
|
buffer
|
|
15
15
|
|
|
16
|
+
# agent_runner._throttle.RoundOutcome
|
|
17
|
+
mem_terminated
|
|
18
|
+
usage_capable
|
|
19
|
+
newest_usage_ts
|
|
20
|
+
newest_substrate_before_ts
|
|
21
|
+
latest_transient_per_agent
|
|
22
|
+
|
|
16
23
|
# agent_runner.agent_runtime.RunResult
|
|
17
24
|
exit_code
|
|
18
25
|
duration_s
|
|
@@ -5,6 +5,18 @@ 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.17] - 2026-09-05
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Internal refactor ("round-outcome unification"): per-round classification now does one events-tail scan per round (was up to three), the give-up decision is unified behind a single `post_round_verdicts` (precedence and exit codes `78`/`70`/`71`/`75` unchanged), and the `_serve_round`↔`serve_cmd` import cycle is removed. Behavior-preserving.
|
|
12
|
+
- `round_mem_critical_sample` is now capped at `2 × mem_critical_consecutive_samples` emits per critical episode (any non-critical tick resets it), so a host stuck under sustained pressure no longer writes that calibration event unbounded for a whole round.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Auto-stop no longer records `monitor_auto_stop_failed` for a stop that actually succeeds: under load a round can outlast the stop-confirmation window while draining, which was mis-reported as a failure. The monitor now recognizes a still-draining round and records the real outcome (triggered or failed) once it resolves on the next poll, instead of a false failure.
|
|
16
|
+
|
|
17
|
+
### Notes
|
|
18
|
+
- Not breaking: no `agent-runner migrate`, no systemd unit change, no new event kinds, `peek --json` schema unchanged. The only non-fix behavior change is the bounded `round_mem_critical_sample` above.
|
|
19
|
+
|
|
8
20
|
## [0.2.16] - 2026-09-05
|
|
9
21
|
|
|
10
22
|
### 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.17
|
|
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
|
|
@@ -296,18 +296,28 @@ def emit_round_mem_terminated(
|
|
|
296
296
|
def emit_round_mem_critical_sample(
|
|
297
297
|
log_dir: Path, *, round_num: int, consecutive: int, context: dict
|
|
298
298
|
) -> None:
|
|
299
|
-
"""Emit on
|
|
300
|
-
hard floor
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
299
|
+
"""Emit on each critical host_health sample inside _spawn_round's mid-round
|
|
300
|
+
hard floor, up to the per-episode cap (0.2.17, below) -- unlike
|
|
301
|
+
round_mem_terminated (deduped to once-per-episode), this fires on every
|
|
302
|
+
critical tick within that cap: the point is calibration visibility into
|
|
303
|
+
near-misses, so an operator watching the event stream sees the
|
|
304
|
+
critical_streak build (1, 2, ...) even on ticks that never reach the
|
|
305
|
+
terminate threshold (a healthy tick resets it before 3-in-a-row).
|
|
306
|
+
``context`` carries the same Pressure.context numbers as
|
|
305
307
|
round_mem_terminated. Fires only during critical pressure -- never during
|
|
306
308
|
warning/healthy -- so it adds no normal-operation noise, including while
|
|
307
309
|
``defer_to_cgroup`` steps back from terminating (the streak/context is
|
|
308
310
|
still useful calibration there; this event stays scoped to the
|
|
309
311
|
sample-level signal, distinct from mem_pressure_deferred_to_cgroup's
|
|
310
|
-
once-per-episode terminate-vs-defer notice).
|
|
312
|
+
once-per-episode terminate-vs-defer notice).
|
|
313
|
+
|
|
314
|
+
0.2.17: the caller caps this at ``2 * mem_critical_consecutive_samples``
|
|
315
|
+
consecutive ticks (1..6 at the default 3) -- a sustained-critical
|
|
316
|
+
don't-terminate run (cgroup-defer, or the off switch) would otherwise
|
|
317
|
+
write one event per ~10s tick for up to a whole ``round_timeout_s`` on a
|
|
318
|
+
permanently-deferred/off host. The cap is per streak-episode, not a
|
|
319
|
+
lifetime limit: any non-critical tick still resets the streak to 0, and
|
|
320
|
+
sampling resumes from 1 the next time critical pressure recurs."""
|
|
311
321
|
from agent_runner.events import ROUND_MEM_CRITICAL_SAMPLE, emit
|
|
312
322
|
|
|
313
323
|
emit(
|
|
@@ -157,6 +157,25 @@ def post_round_decision(
|
|
|
157
157
|
return ("continue", delay, 0)
|
|
158
158
|
|
|
159
159
|
|
|
160
|
+
def _consecutive_streak_decision(
|
|
161
|
+
*, triggered: bool, consecutive: int, threshold: int, verdict: str
|
|
162
|
+
) -> tuple[str, int]:
|
|
163
|
+
"""The generic give-up-cap shape both :func:`_mem_loop_decision` and
|
|
164
|
+
:func:`_no_progress_decision` delegate to (0.2.17 Task 2 — de-duplicated
|
|
165
|
+
out of the two, which were byte-identical apart from their reset
|
|
166
|
+
condition, threshold constant, and verdict string). A NEW, SEPARATE,
|
|
167
|
+
private counter from ``post_round_decision``'s crash-loop breaker, not a
|
|
168
|
+
widening of its 3-tuple contract. Returns ``(verdict, consecutive)`` once
|
|
169
|
+
``consecutive`` reaches ``threshold``; a non-``triggered`` round resets the
|
|
170
|
+
counter to 0."""
|
|
171
|
+
if not triggered:
|
|
172
|
+
return ("continue", 0)
|
|
173
|
+
consecutive += 1
|
|
174
|
+
if consecutive >= threshold:
|
|
175
|
+
return (verdict, consecutive)
|
|
176
|
+
return ("continue", consecutive)
|
|
177
|
+
|
|
178
|
+
|
|
160
179
|
def _mem_loop_decision(
|
|
161
180
|
*, mem_terminated: bool, consecutive: int
|
|
162
181
|
) -> tuple[Literal["mem_loop", "continue"], int]:
|
|
@@ -168,12 +187,12 @@ def _mem_loop_decision(
|
|
|
168
187
|
eventually giving up. Returns ``("mem_loop", consecutive)`` once
|
|
169
188
|
``consecutive`` reaches ``MEM_LOOP_THRESHOLD``; a non-mem-terminated round
|
|
170
189
|
resets the counter to 0, same reset shape as the crash-loop breaker."""
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
190
|
+
return _consecutive_streak_decision(
|
|
191
|
+
triggered=mem_terminated,
|
|
192
|
+
consecutive=consecutive,
|
|
193
|
+
threshold=MEM_LOOP_THRESHOLD,
|
|
194
|
+
verdict="mem_loop",
|
|
195
|
+
)
|
|
177
196
|
|
|
178
197
|
|
|
179
198
|
def _no_progress_decision(
|
|
@@ -190,12 +209,12 @@ def _no_progress_decision(
|
|
|
190
209
|
via a different signal. A round WITH progress (usage recorded, non-zero
|
|
191
210
|
exit, or slow) resets the counter to 0, same reset shape as
|
|
192
211
|
crash-loop/mem-loop."""
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
212
|
+
return _consecutive_streak_decision(
|
|
213
|
+
triggered=no_progress,
|
|
214
|
+
consecutive=consecutive,
|
|
215
|
+
threshold=CRASH_LOOP_THRESHOLD,
|
|
216
|
+
verdict="stalled_no_progress",
|
|
217
|
+
)
|
|
199
218
|
|
|
200
219
|
|
|
201
220
|
def classify_round_exit(exc: BaseException) -> int:
|
|
@@ -256,7 +275,7 @@ def classify_round_exit(exc: BaseException) -> int:
|
|
|
256
275
|
_REAP_GRACE_S = 5
|
|
257
276
|
_GIT_COMMIT_TIMEOUT_S = 120
|
|
258
277
|
_HOOK_ALLOWANCE_S = 60 # slack for post-round hooks between the inner wall and the ceiling
|
|
259
|
-
_ROUND_TERM_GRACE_S = 15 # mirrors api._ROUND_TERM_GRACE_S /
|
|
278
|
+
_ROUND_TERM_GRACE_S = 15 # mirrors api._ROUND_TERM_GRACE_S / cli._serve_round._ROUND_TERM_GRACE_S
|
|
260
279
|
_STOP_GRACE_MARGIN_S = 10 # pad above _ROUND_TERM_GRACE_S for systemd stop-request overhead
|
|
261
280
|
|
|
262
281
|
|
|
@@ -13,7 +13,8 @@ from __future__ import annotations
|
|
|
13
13
|
import math
|
|
14
14
|
import random
|
|
15
15
|
import warnings
|
|
16
|
-
from collections.abc import Callable
|
|
16
|
+
from collections.abc import Callable, Iterator
|
|
17
|
+
from dataclasses import dataclass
|
|
17
18
|
from datetime import UTC
|
|
18
19
|
from pathlib import Path
|
|
19
20
|
from typing import Any
|
|
@@ -41,7 +42,9 @@ __all__ = [
|
|
|
41
42
|
"mem_loop_events_in_window",
|
|
42
43
|
"pending_recovered",
|
|
43
44
|
"round_had_no_progress",
|
|
45
|
+
"round_outcome",
|
|
44
46
|
"round_was_mem_terminated",
|
|
47
|
+
"RoundOutcome", # trailing: class, not function -- kept with its round_outcome() sibling above
|
|
45
48
|
]
|
|
46
49
|
|
|
47
50
|
# _scan_events_for_transient sentinel: file held no transient event at all
|
|
@@ -107,6 +110,21 @@ def _iter_events(path: Path):
|
|
|
107
110
|
return iter_event_dicts(path)
|
|
108
111
|
|
|
109
112
|
|
|
113
|
+
def _tail_events(log_dir: Path) -> Iterator[dict[str, Any]]:
|
|
114
|
+
"""The ONE events-tail scan every per-round / per-agent / global-latest
|
|
115
|
+
reader below builds on: the newest TWO monthly ``events-*.jsonl`` files
|
|
116
|
+
(oldest-first), each read forward. 0.2.17 Task 1 consolidated 5 copies of
|
|
117
|
+
this exact ``sorted(log_dir.glob(...))[-2:]`` loop into this single
|
|
118
|
+
generator so a refactor of the tail-selection logic (e.g. widening the
|
|
119
|
+
window) touches one place, not five.
|
|
120
|
+
|
|
121
|
+
Forward, OLD-then-NEW order matters (INVARIANT 2 for callers that merge a
|
|
122
|
+
per-key "latest wins" map: iterating oldest file first means a later event
|
|
123
|
+
for the same key naturally overwrites an earlier one)."""
|
|
124
|
+
for path in sorted(log_dir.glob("events-*.jsonl"))[-2:]:
|
|
125
|
+
yield from _iter_events(path)
|
|
126
|
+
|
|
127
|
+
|
|
110
128
|
def _scan_events_for_transient(path: Path):
|
|
111
129
|
"""The file's LAST transient event: the detected dict, ``None`` (latest was a
|
|
112
130
|
recovered), or ``_NO_TRANSIENT`` (no transient event in the file).
|
|
@@ -152,13 +170,15 @@ def _latest_transient_per_agent(log_dir: Path) -> dict[str, Any]:
|
|
|
152
170
|
agent A in the old file must not be masked by agent B's event in the newer one.
|
|
153
171
|
Iterates old-then-new so the newest event per agent wins."""
|
|
154
172
|
latest: dict[str, Any] = {}
|
|
155
|
-
for
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
173
|
+
for ev in _tail_events(log_dir):
|
|
174
|
+
kind = ev.get("event")
|
|
175
|
+
# INVARIANT 2: keyed by str(agent), forward old->new merge (_tail_events
|
|
176
|
+
# yields the newest-two-files tail oldest-first) — newest event per agent
|
|
177
|
+
# wins, matching every other latest_transient_per_agent copy pre-0.2.17.
|
|
178
|
+
if kind == TRANSIENT_ERROR_DETECTED:
|
|
179
|
+
latest[str(ev.get("agent", "unknown"))] = ev
|
|
180
|
+
elif kind == TRANSIENT_ERROR_RECOVERED:
|
|
181
|
+
latest[str(ev.get("agent", "unknown"))] = None
|
|
162
182
|
return latest
|
|
163
183
|
|
|
164
184
|
|
|
@@ -174,15 +194,14 @@ def _backoff_exponent(log_dir: Path, agent: str) -> int:
|
|
|
174
194
|
deliberately NOT a reset point — the throttle-aware-skip path emits one every
|
|
175
195
|
cycle, which would pin the exponent at <=1 and flatten the ladder."""
|
|
176
196
|
count = 0
|
|
177
|
-
for
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
count = 0
|
|
197
|
+
for ev in _tail_events(log_dir):
|
|
198
|
+
if str(ev.get("agent", "")) != agent:
|
|
199
|
+
continue
|
|
200
|
+
kind = ev.get("event")
|
|
201
|
+
if kind == TRANSIENT_ERROR_DETECTED:
|
|
202
|
+
count += 1
|
|
203
|
+
elif kind == AGENT_USAGE_RECORDED and ev.get("success"):
|
|
204
|
+
count = 0
|
|
186
205
|
return max(0, count - 1)
|
|
187
206
|
|
|
188
207
|
|
|
@@ -240,7 +259,11 @@ def _events_derived_reset(
|
|
|
240
259
|
|
|
241
260
|
|
|
242
261
|
def _active_throttles(
|
|
243
|
-
log_dir: Path,
|
|
262
|
+
log_dir: Path,
|
|
263
|
+
*,
|
|
264
|
+
clock: Clock = SYSTEM_CLOCK,
|
|
265
|
+
_exponent_cache: dict[str, int] | None = None,
|
|
266
|
+
_latest: dict[str, Any] | None = None,
|
|
244
267
|
) -> dict[str, TransientErrorState]:
|
|
245
268
|
"""Currently-active throttles keyed by AGENT label (binary basename), one entry
|
|
246
269
|
per agent whose latest transient event is an unrecovered ``detected`` with its
|
|
@@ -251,10 +274,16 @@ def _active_throttles(
|
|
|
251
274
|
healthy agent's phase keeps running. Restart-safe (events-derived). The effective
|
|
252
275
|
reset applies the events-derived exp-backoff ladder (:func:`_events_derived_reset`)
|
|
253
276
|
so a permanently-failing agent escalates here too, not just on the wait/back_off
|
|
254
|
-
path — ONE ladder, shared by both regimes.
|
|
277
|
+
path — ONE ladder, shared by both regimes.
|
|
278
|
+
|
|
279
|
+
``_latest``, when given, is an already-computed :func:`_latest_transient_per_agent`
|
|
280
|
+
map (e.g. :attr:`RoundOutcome.latest_transient_per_agent`) — skips a fresh
|
|
281
|
+
events-tail scan here, the same memoization idea as ``_exponent_cache``. Omitted,
|
|
282
|
+
it is computed fresh (unchanged pre-0.2.17 behavior)."""
|
|
255
283
|
now = clock.epoch()
|
|
256
284
|
active: dict[str, TransientErrorState] = {}
|
|
257
|
-
|
|
285
|
+
latest = _latest if _latest is not None else _latest_transient_per_agent(log_dir)
|
|
286
|
+
for agent, detected in latest.items():
|
|
258
287
|
if detected is None:
|
|
259
288
|
continue
|
|
260
289
|
classification = str(detected.get("classification", "rate_limit_account"))
|
|
@@ -277,7 +306,79 @@ def _active_throttles(
|
|
|
277
306
|
return active
|
|
278
307
|
|
|
279
308
|
|
|
280
|
-
|
|
309
|
+
@dataclass(frozen=True)
|
|
310
|
+
class RoundOutcome:
|
|
311
|
+
"""Every PER-ROUND-scoped verdict :func:`round_was_mem_terminated` and
|
|
312
|
+
:func:`round_had_no_progress` need, folded from ONE :func:`_tail_events`
|
|
313
|
+
scan (0.2.17 Task 1) instead of the pre-0.2.17 three separate ones (those
|
|
314
|
+
two functions' own scans, plus the throttle check's
|
|
315
|
+
:func:`_latest_transient_per_agent` scan serve_cmd ran back-to-back with
|
|
316
|
+
them every round).
|
|
317
|
+
|
|
318
|
+
Deliberately does NOT include :func:`mem_loop_events_in_window` (give-up-only,
|
|
319
|
+
needs its own clock cutoff — a different axis than "this round") or
|
|
320
|
+
:func:`_backoff_exponent` (agent-scoped, keyed by a caller-supplied agent, not
|
|
321
|
+
"the round that just ran") — both stay on :func:`_tail_events` directly."""
|
|
322
|
+
|
|
323
|
+
mem_terminated: bool
|
|
324
|
+
usage_capable: bool
|
|
325
|
+
newest_usage_ts: str | None
|
|
326
|
+
newest_substrate_before_ts: str | None
|
|
327
|
+
latest_transient_per_agent: dict[str, Any]
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def round_outcome(log_dir: Path) -> RoundOutcome:
|
|
331
|
+
"""Single-pass fold over :func:`_tail_events` computing :class:`RoundOutcome`.
|
|
332
|
+
Callers: :func:`round_was_mem_terminated`, :func:`round_had_no_progress` (each
|
|
333
|
+
accepts a precomputed ``outcome=`` to skip re-scanning), and
|
|
334
|
+
``serve_cmd.cmd()``'s post-round block, which computes this ONCE per round
|
|
335
|
+
(INVARIANT 3) and reuses it for the mem-terminated / throttle / no-progress
|
|
336
|
+
checks that used to scan separately."""
|
|
337
|
+
newest_before_ts: str | None = None
|
|
338
|
+
newest_terminated_ts: str | None = None
|
|
339
|
+
newest_usage_ts: str | None = None
|
|
340
|
+
usage_capable = False
|
|
341
|
+
transient: dict[str, Any] = {}
|
|
342
|
+
for ev in _tail_events(log_dir):
|
|
343
|
+
kind = ev.get("event")
|
|
344
|
+
# INVARIANT 2: latest_transient_per_agent keys by str(agent), no ts guard,
|
|
345
|
+
# forward old->new merge (_tail_events yields oldest-file-first) — identical
|
|
346
|
+
# to the pre-0.2.17 _latest_transient_per_agent copy this replaces.
|
|
347
|
+
if kind == TRANSIENT_ERROR_DETECTED:
|
|
348
|
+
transient[str(ev.get("agent", "unknown"))] = ev
|
|
349
|
+
elif kind == TRANSIENT_ERROR_RECOVERED:
|
|
350
|
+
transient[str(ev.get("agent", "unknown"))] = None
|
|
351
|
+
# INVARIANT 1: usage_capable set UNCONDITIONALLY here, on the raw kind check,
|
|
352
|
+
# BEFORE the `ts` guard below — matches the pre-refactor round_had_no_progress
|
|
353
|
+
# (usage_capable must arm even off a ts-less agent_usage_recorded event).
|
|
354
|
+
if kind == AGENT_USAGE_RECORDED:
|
|
355
|
+
usage_capable = True
|
|
356
|
+
ts = ev.get("ts")
|
|
357
|
+
if not ts:
|
|
358
|
+
continue
|
|
359
|
+
if kind == ROUND_SUBSTRATE_BEFORE:
|
|
360
|
+
newest_before_ts = ts
|
|
361
|
+
elif kind == ROUND_MEM_TERMINATED:
|
|
362
|
+
newest_terminated_ts = ts
|
|
363
|
+
elif kind == AGENT_USAGE_RECORDED:
|
|
364
|
+
newest_usage_ts = ts
|
|
365
|
+
# round_was_mem_terminated's own scoping logic (see its docstring): >=, not >,
|
|
366
|
+
# since a fast loop can legitimately stamp both events in the same millisecond.
|
|
367
|
+
mem_terminated = (
|
|
368
|
+
newest_before_ts is not None
|
|
369
|
+
and newest_terminated_ts is not None
|
|
370
|
+
and parse_iso_ms(newest_terminated_ts) >= parse_iso_ms(newest_before_ts)
|
|
371
|
+
)
|
|
372
|
+
return RoundOutcome(
|
|
373
|
+
mem_terminated=mem_terminated,
|
|
374
|
+
usage_capable=usage_capable,
|
|
375
|
+
newest_usage_ts=newest_usage_ts,
|
|
376
|
+
newest_substrate_before_ts=newest_before_ts,
|
|
377
|
+
latest_transient_per_agent=transient,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def round_was_mem_terminated(log_dir: Path, *, outcome: RoundOutcome | None = None) -> bool:
|
|
281
382
|
"""True iff the round that JUST ran was killed by ``_spawn_round``'s
|
|
282
383
|
mid-round memory-pressure hard floor (``round_mem_terminated``) rather
|
|
283
384
|
than a genuine crash — so ``serve_cmd.cmd`` can excuse it from the
|
|
@@ -299,22 +400,15 @@ def round_was_mem_terminated(log_dir: Path) -> bool:
|
|
|
299
400
|
``round_mem_terminated`` within the very round it is scoping. ``>=``, not
|
|
300
401
|
``>``: a fast loop (or millisecond-resolution ties) can legitimately stamp
|
|
301
402
|
both events in the same millisecond; erring toward "this round's" on a tie
|
|
302
|
-
only risks over-excusing, never mistaking a genuine crash for a rescue.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if kind == ROUND_SUBSTRATE_BEFORE:
|
|
312
|
-
newest_before_ts = ts
|
|
313
|
-
elif kind == ROUND_MEM_TERMINATED:
|
|
314
|
-
newest_terminated_ts = ts
|
|
315
|
-
if newest_before_ts is None or newest_terminated_ts is None:
|
|
316
|
-
return False
|
|
317
|
-
return parse_iso_ms(newest_terminated_ts) >= parse_iso_ms(newest_before_ts)
|
|
403
|
+
only risks over-excusing, never mistaking a genuine crash for a rescue.
|
|
404
|
+
|
|
405
|
+
``outcome``, when given (the serve post-round block's one-scan path — see
|
|
406
|
+
:class:`RoundOutcome`), is used verbatim instead of triggering a fresh
|
|
407
|
+
:func:`round_outcome` scan; every existing caller/test omits it and gets the
|
|
408
|
+
pre-0.2.17 from-scratch-scan behavior unchanged."""
|
|
409
|
+
if outcome is None:
|
|
410
|
+
outcome = round_outcome(log_dir)
|
|
411
|
+
return outcome.mem_terminated
|
|
318
412
|
|
|
319
413
|
|
|
320
414
|
def round_had_no_progress(
|
|
@@ -324,6 +418,7 @@ def round_had_no_progress(
|
|
|
324
418
|
duration_s: float,
|
|
325
419
|
threshold_s: float,
|
|
326
420
|
throttle_active: bool = False,
|
|
421
|
+
outcome: RoundOutcome | None = None,
|
|
327
422
|
) -> bool:
|
|
328
423
|
"""True iff the round that JUST ran exited 0, finished fast, but never
|
|
329
424
|
reached the model -- pi (and CLIs like it: see builtin_plugins/pi.py's
|
|
@@ -352,9 +447,11 @@ def round_had_no_progress(
|
|
|
352
447
|
round), a round with none is genuine no-progress and still trips --
|
|
353
448
|
this makes the breaker CLI-adaptive with no config descriptor.
|
|
354
449
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
450
|
+
INVARIANT 4: short-circuits on ``returncode != 0`` or ``duration_s >=
|
|
451
|
+
threshold_s`` (or ``throttle_active``) BEFORE touching ``outcome`` at all --
|
|
452
|
+
still ahead of any events-tail scan / ``newest_usage_ts`` comparison, exactly
|
|
453
|
+
as the pre-0.2.17 single-scan version did: a non-zero exit already has its
|
|
454
|
+
own crash-loop signal, and a slow round (even with no usage) is not a TIGHT
|
|
358
455
|
loop -- a wedged/hung round already has its own signal
|
|
359
456
|
(``round_supervisor_wedged``), so this floor is specifically the fast spin.
|
|
360
457
|
|
|
@@ -367,31 +464,22 @@ def round_had_no_progress(
|
|
|
367
464
|
(newest-event-timestamp comparison against the newest
|
|
368
465
|
``round_substrate_before``), with the comparison inverted: no progress
|
|
369
466
|
means the newest usage event is EITHER absent OR older than this round's
|
|
370
|
-
own start.
|
|
467
|
+
own start.
|
|
468
|
+
|
|
469
|
+
``outcome``, when given (the serve post-round block's one-scan path), is used
|
|
470
|
+
verbatim instead of triggering a fresh :func:`round_outcome` scan — see
|
|
471
|
+
:func:`round_was_mem_terminated`."""
|
|
371
472
|
if throttle_active or returncode != 0 or duration_s >= threshold_s:
|
|
372
473
|
return False
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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:
|
|
474
|
+
if outcome is None:
|
|
475
|
+
outcome = round_outcome(log_dir)
|
|
476
|
+
if not outcome.usage_capable:
|
|
389
477
|
return False # this CLI/plugin stack has never emitted usage -- can't arm
|
|
390
|
-
if
|
|
478
|
+
if outcome.newest_substrate_before_ts is None:
|
|
391
479
|
return False
|
|
392
|
-
if newest_usage_ts is None:
|
|
480
|
+
if outcome.newest_usage_ts is None:
|
|
393
481
|
return True
|
|
394
|
-
return parse_iso_ms(newest_usage_ts) < parse_iso_ms(
|
|
482
|
+
return parse_iso_ms(outcome.newest_usage_ts) < parse_iso_ms(outcome.newest_substrate_before_ts)
|
|
395
483
|
|
|
396
484
|
|
|
397
485
|
def mem_loop_events_in_window(log_dir: Path, clock: Clock, window_s: int) -> int:
|
|
@@ -399,7 +487,7 @@ def mem_loop_events_in_window(log_dir: Path, clock: Clock, window_s: int) -> int
|
|
|
399
487
|
``window_s`` seconds of ``clock.epoch()`` — events-derived, no state file,
|
|
400
488
|
same tail-reconstruction shape as :func:`round_was_mem_terminated` above.
|
|
401
489
|
|
|
402
|
-
Feeds ``_serve_round.
|
|
490
|
+
Feeds ``_serve_round.post_round_verdicts``'s cross-restart escalation
|
|
403
491
|
(0.2.16 Task 5): ``MEM_LOOP_EXIT`` (71) alone resets on every serve
|
|
404
492
|
process restart, so a host stuck in sustained pressure respawns into the
|
|
405
493
|
identical loop forever (field-confirmed: NRestarts climbs, never
|
|
@@ -407,15 +495,16 @@ def mem_loop_events_in_window(log_dir: Path, clock: Clock, window_s: int) -> int
|
|
|
407
495
|
an all-time total, or a restart-local counter — gives "ages out on its
|
|
408
496
|
own after a sustained-healthy stretch" for free: an old mem_loop episode
|
|
409
497
|
outside the window simply stops counting, no explicit reset needed."""
|
|
498
|
+
# Kept OUT of RoundOutcome/round_outcome (0.2.17 Task 1): give-up-only, and its
|
|
499
|
+
# window cutoff needs its OWN clock read, unlike anything folded into that struct.
|
|
410
500
|
cutoff = clock.epoch() - window_s
|
|
411
501
|
count = 0
|
|
412
|
-
for
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
count += 1
|
|
502
|
+
for ev in _tail_events(log_dir):
|
|
503
|
+
if ev.get("event") != MEM_LOOP:
|
|
504
|
+
continue
|
|
505
|
+
ts = ev.get("ts")
|
|
506
|
+
if ts and parse_iso_ms(ts).timestamp() >= cutoff:
|
|
507
|
+
count += 1
|
|
419
508
|
return count
|
|
420
509
|
|
|
421
510
|
|
|
@@ -657,9 +746,9 @@ def _interruptible_sleep(
|
|
|
657
746
|
:func:`_apply_back_off` so a SIGTERM or a stop_file lands within one chunk instead
|
|
658
747
|
of after the full sleep (e.g. the 8h back-off cap).
|
|
659
748
|
|
|
660
|
-
``should_stop`` matches :func:`_pause_poll`'s contract (
|
|
661
|
-
predicate the caller closes over its own stop_file check with, so this
|
|
662
|
-
needs to know what "should stop" means beyond calling it.
|
|
749
|
+
``should_stop`` matches :func:`_pause_poll`'s contract (cli/_serve_round.py): a
|
|
750
|
+
zero-arg predicate the caller closes over its own stop_file check with, so this
|
|
751
|
+
module never needs to know what "should stop" means beyond calling it.
|
|
663
752
|
|
|
664
753
|
Counts down the *intended* nap per slice rather than measuring a wall/monotonic
|
|
665
754
|
deadline: NTP-step immune (no clock read for the deadline) AND does not busy-spin
|
|
@@ -669,7 +758,7 @@ def _interruptible_sleep(
|
|
|
669
758
|
``deadline_epoch``, when given, additionally re-checks ``clock.epoch() >=
|
|
670
759
|
deadline_epoch`` at each chunk boundary and returns False (completed, NOT
|
|
671
760
|
interrupted) the moment it's reached — mirroring the skip path's self-correcting
|
|
672
|
-
``clock.epoch() >= wake_epoch`` (serve_cmd.py:
|
|
761
|
+
``clock.epoch() >= wake_epoch`` (serve_cmd.py:238). Only :func:`_apply_back_off`
|
|
673
762
|
passes this: its ``total_s`` is computed against the caller's OWN clock reading at
|
|
674
763
|
call time, which on an RTC-less host booting hours behind is stale, inflating
|
|
675
764
|
``total_s`` well past the real wall-clock target; re-checking the target itself
|
|
@@ -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.17'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 17)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -319,9 +319,9 @@ def start(project: str | Path) -> ServiceStatus:
|
|
|
319
319
|
_PID_SIGNAL_GRACE_S = 5
|
|
320
320
|
|
|
321
321
|
# Grace after TERMing the round-lock holder before escalating to SIGKILL: mirrors
|
|
322
|
-
#
|
|
322
|
+
# cli._serve_round._ROUND_TERM_GRACE_S (the round's own SIGTERM handler needs
|
|
323
323
|
# agent_runtime.REAP_GRACE_S + margin to reap its agent pgroup and exit).
|
|
324
|
-
# Cross-checked against
|
|
324
|
+
# Cross-checked against that module's value by
|
|
325
325
|
# test_round_kill_grace_matches_serve so the two never drift.
|
|
326
326
|
_ROUND_TERM_GRACE_S = 15
|
|
327
327
|
|
|
@@ -712,12 +712,29 @@ def _monitor_loop_iter(
|
|
|
712
712
|
# work_dir with a non-preset log_dir would target the wrong dir, see no
|
|
713
713
|
# pidfile, and no-op while serve keeps running. The Path resolves to the
|
|
714
714
|
# real cfg.runtime.log_dir.
|
|
715
|
-
monitor.on_alert(
|
|
715
|
+
verdict = monitor.on_alert(
|
|
716
716
|
alert,
|
|
717
717
|
project=work_dir,
|
|
718
718
|
log_dir=cfg.runtime.log_dir,
|
|
719
719
|
allowed_stop_names=cfg.monitor.auto_stop_on,
|
|
720
720
|
)
|
|
721
|
+
if verdict == "draining":
|
|
722
|
+
# Nothing was recorded for this alert this poll (see on_alert's
|
|
723
|
+
# docstring) — force-clear its `seen` entry so it is NOT treated
|
|
724
|
+
# as "still firing, stay suppressed" below: the alert condition
|
|
725
|
+
# persisting is exactly the case that must re-fire on_alert next
|
|
726
|
+
# poll, unlike every other verdict (recorded outcome, or never
|
|
727
|
+
# eligible), which keeps the normal dedup.
|
|
728
|
+
#
|
|
729
|
+
# Trade-off: `seen` also gates the `yield alert` above (the
|
|
730
|
+
# dedup consumers of the monitor stream see), so re-arming it
|
|
731
|
+
# to retry on_alert ALSO re-yields this same alert to the
|
|
732
|
+
# stream consumer next poll. Accepted: a persisting alert
|
|
733
|
+
# re-appearing each poll while its stop drains just reads as a
|
|
734
|
+
# "still draining" signal to that consumer. A fuller design
|
|
735
|
+
# would split consumer-yield dedup from on_alert-retry state,
|
|
736
|
+
# but that split isn't warranted for this narrow case.
|
|
737
|
+
del seen[key]
|
|
721
738
|
# Re-arm: an episode absent from this poll has cleared, so forget it — a
|
|
722
739
|
# later recurrence is a NEW episode and must fire again (not stay suppressed
|
|
723
740
|
# until bounded eviction). Only keys still firing this poll survive.
|