cli-agent-runner 0.2.16__tar.gz → 0.2.18__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.18}/.github/workflows/ci.yml +8 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/.vulture-whitelist.py +11 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/CHANGELOG.md +34 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/CONTRIBUTING.md +4 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/PKG-INFO +3 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/README.zh.md +1 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/__init__.py +20 -5
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_emit.py +127 -9
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_monitor_detectors.py +10 -4
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_monitor_state.py +35 -15
- cli_agent_runner-0.2.18/agent_runner/_plugin_scan.py +109 -0
- cli_agent_runner-0.2.18/agent_runner/_round_outcome.py +255 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_serve_policy.py +43 -22
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_substrate.py +2 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_throttle.py +113 -158
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/api.py +69 -72
- cli_agent_runner-0.2.18/agent_runner/cli/_serve_round.py +796 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/events_cmd.py +28 -6
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/serve_cmd.py +94 -78
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/clock.py +2 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/defenses.py +2 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/events.py +49 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/http_progress.py +7 -5
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/metrics.py +180 -17
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/monitor.py +89 -25
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/round_log.py +0 -10
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/runner.py +7 -2
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/schedule.py +2 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/service_unit.py +10 -7
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/build.sh +21 -4
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/architecture.md +7 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/configuration.md +3 -2
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/migrations/0.2.md +28 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/runbook.md +52 -2
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/pyproject.toml +12 -2
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/_test_helpers.py +64 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/contract/test_public_api_surface.py +2 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_auto_stop_e2e.py +58 -16
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_bounded_run.py +18 -3
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_grace_kill_emission.py +20 -6
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_run_one_round_with_fake_agent.py +31 -7
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_serve_loop.py +10 -2
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_spawn_round_mem_floor.py +216 -20
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_spawn_round_wedged.py +5 -4
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/_event_scan.py +5 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_architecture.py +16 -26
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_docs_generated.py +2 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_event_kinds_ssot.py +4 -2
- cli_agent_runner-0.2.18/tests/invariants/test_import_footprint.py +209 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_no_internal_refs.py +13 -2
- cli_agent_runner-0.2.18/tests/invariants/test_no_price_logic.py +51 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_no_vacuous_invariants.py +3 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_peek_schema_version.py +5 -2
- cli_agent_runner-0.2.18/tests/invariants/test_round_alloc_growth.py +127 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_timeout_budget_invariant.py +16 -5
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_agent_runtime.py +29 -10
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_agent_runtime_grace.py +143 -51
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_agent_runtime_marker_scan.py +5 -3
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_agent_runtime_progress.py +2 -2
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_agent_runtime_reap.py +9 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_api_observation.py +2 -2
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_api_service.py +54 -3
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_auto_stop_gating.py +13 -2
- cli_agent_runner-0.2.18/tests/unit/test_cgroup_probe.py +451 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_cli_service_peek_monitor.py +7 -8
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_events.py +38 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_events_cmd.py +9 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_events_jsonl_decode.py +1 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_git_timeout.py +6 -2
- cli_agent_runner-0.2.18/tests/unit/test_http_progress_epoch.py +20 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_init_entry_points.py +33 -33
- cli_agent_runner-0.2.18/tests/unit/test_metrics_cgroup_usage.py +54 -0
- cli_agent_runner-0.2.18/tests/unit/test_metrics_swap.py +21 -0
- cli_agent_runner-0.2.18/tests/unit/test_monitor_dedup_rearm.py +104 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_monitor_detect_rate_limit.py +42 -0
- cli_agent_runner-0.2.18/tests/unit/test_monitor_loop.py +110 -0
- cli_agent_runner-0.2.18/tests/unit/test_monitor_on_alert.py +104 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_monitor_remote.py +72 -1
- cli_agent_runner-0.2.18/tests/unit/test_plugin_scan_parity.py +156 -0
- cli_agent_runner-0.2.18/tests/unit/test_round_outcome.py +325 -0
- cli_agent_runner-0.2.18/tests/unit/test_round_outcome_agent_axis.py +116 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_runner.py +1 -1
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_serve_policy.py +25 -0
- cli_agent_runner-0.2.18/tests/unit/test_serve_round.py +34 -0
- cli_agent_runner-0.2.18/tests/unit/test_serve_round_cgroup_spine.py +187 -0
- cli_agent_runner-0.2.18/tests/unit/test_serve_round_oom_killed.py +198 -0
- cli_agent_runner-0.2.18/tests/unit/test_serve_round_terminate.py +32 -0
- cli_agent_runner-0.2.18/tests/unit/test_throttle_epoch_bound.py +77 -0
- cli_agent_runner-0.2.16/agent_runner/cli/_serve_round.py +0 -442
- cli_agent_runner-0.2.16/tests/unit/test_cgroup_probe.py +0 -233
- 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.18}/.codecov.yml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/.gitignore +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/BACKLOG.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/LICENSE +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/README.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/SECURITY.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/agent_runtime.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/config/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/config/loader.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/config/models.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/config/parsers.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/config/validators.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/host_health.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/phase_select.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/README.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/commands.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/events.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/plugins.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/docs/thesis.md +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/_clock.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/conftest.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/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.18}/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.18}/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.18}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_config.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_config_nested_strictness.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_example_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_host_health.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_phase_select.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_primary_prompt_file_home.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.2.16 → cli_agent_runner-0.2.18}/tests/unit/test_vcs_state.py +0 -0
|
@@ -92,7 +92,14 @@ jobs:
|
|
|
92
92
|
# `| tail` would swallow the exit code and let dead code through.
|
|
93
93
|
run: vulture
|
|
94
94
|
- name: tests with coverage
|
|
95
|
-
|
|
95
|
+
# Two passes so `serial` tests (install process-wide signal handlers /
|
|
96
|
+
# self-signals -- unsafe to share an xdist worker process) still run
|
|
97
|
+
# every time: parallel set + serial set == the full suite, mirroring
|
|
98
|
+
# ./build.sh test. pytest-cov 7.x auto-combines worker coverage data.
|
|
99
|
+
run: |
|
|
100
|
+
pytest -q --ignore=tests/e2e --ignore=tests/literate \
|
|
101
|
+
-n auto --dist worksteal -m "not serial" --cov --cov-report=xml --cov-report=term
|
|
102
|
+
pytest -q --ignore=tests/e2e --ignore=tests/literate -m serial --cov --cov-append --cov-report=xml --cov-report=term
|
|
96
103
|
- name: docs CI gate
|
|
97
104
|
run: |
|
|
98
105
|
python -m agent_runner._docgen
|
|
@@ -13,6 +13,17 @@ log_dir
|
|
|
13
13
|
offsets
|
|
14
14
|
buffer
|
|
15
15
|
|
|
16
|
+
# agent_runner._round_outcome.RoundOutcome
|
|
17
|
+
mem_terminated
|
|
18
|
+
usage_capable
|
|
19
|
+
newest_usage_ts
|
|
20
|
+
newest_substrate_before_ts
|
|
21
|
+
latest_transient_per_agent
|
|
22
|
+
ran_agent
|
|
23
|
+
usage_capable_by_agent
|
|
24
|
+
newest_usage_ts_by_agent
|
|
25
|
+
backoff_exponent_by_agent
|
|
26
|
+
|
|
16
27
|
# agent_runner.agent_runtime.RunResult
|
|
17
28
|
exit_code
|
|
18
29
|
duration_s
|
|
@@ -5,6 +5,40 @@ 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.18] - 2026-09-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Cgroup pressure spine: `round_cgroup_memory` emits once per round (when this process's cgroup has a finite `memory.max`) with peak `memory.current`/`memory.swap.current` and `memory.events` counter deltas over the round — pressure becomes visible before it trips a give-up verdict.
|
|
12
|
+
- `round_oom_killed`: a pointer-only event (never round-log content) when the kernel cgroup-OOM-kills a round; the round still exits 137 and counts toward the crash streak exactly as before — this only names the cause.
|
|
13
|
+
- `host_cgroup_memory_limit` gains four advisory fields (`swap_total_bytes`, `swap_cap_pct`, `memory_high`, `advisory`) computed once at serve startup, plus a swap-plausibility guard on the cgroup-defer decision.
|
|
14
|
+
- Docs: a real-RAM ledger, a zram worked example, and a commented `# MemoryHigh` hint (with the PSI-floor interaction warning).
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Per-agent no-progress verdict: a usageless round on a never-emits-usage CLI is no longer misread as stalled in a mixed `[phases]` config.
|
|
18
|
+
- Fail-open hardening: the monitor no longer crashes on the stop/classify path (an emit error, a poisoned epoch field, or an unreapable round now degrade instead of raising).
|
|
19
|
+
- `kill` now escalates SIGTERM to SIGKILL under systemd-user after the grace window, matching the docs.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Plugin discovery scans `entry_points.txt` directly with a metadata parity fallback, ahead of 0.3's group growth.
|
|
23
|
+
- Internal cleanup: `RoundOutcome` carved into `_round_outcome`, constants single-sourced, lazy `hashlib`/`zoneinfo` behind an import-footprint gate, a tenet-3 price-blind invariant, plus test-speed work (parallel gate + faster inner loop).
|
|
24
|
+
|
|
25
|
+
### Notes
|
|
26
|
+
- Not breaking: no `agent-runner migrate` step, no systemd unit change; `peek --json` gains the two new event kinds above, existing kinds unchanged.
|
|
27
|
+
|
|
28
|
+
See `docs/migrations/0.2.md`.
|
|
29
|
+
|
|
30
|
+
## [0.2.17] - 2026-09-05
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- 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.
|
|
34
|
+
- `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.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- 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.
|
|
38
|
+
|
|
39
|
+
### Notes
|
|
40
|
+
- 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.
|
|
41
|
+
|
|
8
42
|
## [0.2.16] - 2026-09-05
|
|
9
43
|
|
|
10
44
|
### Added
|
|
@@ -105,7 +105,10 @@ drifts.
|
|
|
105
105
|
|
|
106
106
|
## Tests
|
|
107
107
|
|
|
108
|
-
- Unit + integration: `pytest
|
|
108
|
+
- Unit + integration (parallel gate): `./build.sh test` — pytest-xdist
|
|
109
|
+
(`-n $AR_TEST_JOBS`, default `auto`) plus a serial pass for tests that
|
|
110
|
+
install process-wide signal handlers. Never set `AR_TEST_JOBS` above 2 on a
|
|
111
|
+
constrained host (e.g. a 462MB Pi).
|
|
109
112
|
- Literate quickstart: `./build.sh literate`
|
|
110
113
|
- Pi e2e (opt-in): `AGENT_RUNNER_E2E_PI=1 pytest tests/e2e/` (needs a `pi`
|
|
111
114
|
ssh alias; not run in CI).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.18
|
|
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
|
|
@@ -27,6 +27,8 @@ Requires-Python: >=3.11
|
|
|
27
27
|
Requires-Dist: psutil>=5.9
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-timeout>=2.3; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-xdist>=3.8; extra == 'dev'
|
|
30
32
|
Requires-Dist: pytest>=8; extra == 'dev'
|
|
31
33
|
Requires-Dist: ruff<0.17,>=0.16; extra == 'dev'
|
|
32
34
|
Requires-Dist: vulture>=2.13; extra == 'dev'
|
|
@@ -27,16 +27,31 @@ def _load_plugins_from_group(group: str) -> None:
|
|
|
27
27
|
|
|
28
28
|
Called at package import. A broken plugin must not crash the supervisor;
|
|
29
29
|
each failure surfaces as a ``UserWarning``.
|
|
30
|
+
|
|
31
|
+
Discovery goes through the ``entry_points.txt`` scanner (cheaper than a
|
|
32
|
+
fresh ``importlib.metadata.entry_points()`` scan per group per process;
|
|
33
|
+
see ``_plugin_scan``), with a hard fallback to ``importlib.metadata`` on
|
|
34
|
+
any parse failure. Loading a plugin means only importing its module and
|
|
35
|
+
resolving its attribute — same as ``EntryPoint.load()`` — since every
|
|
36
|
+
built-in plugin registers itself as a module-top side effect (documented
|
|
37
|
+
in docs/plugins.md); this loader never instantiates or calls the target.
|
|
30
38
|
"""
|
|
39
|
+
import importlib
|
|
40
|
+
import sys
|
|
31
41
|
import warnings
|
|
32
|
-
from importlib.metadata import entry_points
|
|
33
42
|
|
|
34
|
-
|
|
43
|
+
from agent_runner._plugin_scan import scan_entry_points
|
|
44
|
+
|
|
45
|
+
for name, value in scan_entry_points(sys.path, group):
|
|
35
46
|
try:
|
|
36
|
-
|
|
47
|
+
module_path, _, attr_path = value.partition(":")
|
|
48
|
+
mod = importlib.import_module(module_path)
|
|
49
|
+
target = mod
|
|
50
|
+
for attr in filter(None, attr_path.split(".")):
|
|
51
|
+
target = getattr(target, attr)
|
|
37
52
|
except Exception as e:
|
|
38
53
|
warnings.warn(
|
|
39
|
-
f"failed to load {group} plugin {
|
|
54
|
+
f"failed to load {group} plugin {name!r}: {e}",
|
|
40
55
|
stacklevel=3,
|
|
41
56
|
)
|
|
42
57
|
|
|
@@ -80,7 +95,7 @@ def apply_plugin_disable(names: list[str]) -> None:
|
|
|
80
95
|
registered plugin (typo catcher; tolerates cross-env config drift).
|
|
81
96
|
|
|
82
97
|
Plugin packages still load at import time — this removes from the registries
|
|
83
|
-
that the runner and peek consult. Side effects from
|
|
98
|
+
that the runner and peek consult. Side effects from loading (module-level
|
|
84
99
|
imports, etc.) have already happened by the time this runs.
|
|
85
100
|
|
|
86
101
|
Known limitation: vcs_state._PLUGIN_OWNED_PATHS lacks per-plugin name
|
|
@@ -25,12 +25,14 @@ __all__ = [
|
|
|
25
25
|
"emit_mem_loop_persistent",
|
|
26
26
|
"emit_mem_pressure_deferred_to_cgroup",
|
|
27
27
|
"emit_rate_limit_stop",
|
|
28
|
+
"emit_round_cgroup_memory",
|
|
28
29
|
"emit_round_deferred",
|
|
29
30
|
"emit_round_grace_extended",
|
|
30
31
|
"emit_round_grace_kill",
|
|
31
32
|
"emit_round_logs_prune_deferred",
|
|
32
33
|
"emit_round_mem_critical_sample",
|
|
33
34
|
"emit_round_mem_terminated",
|
|
35
|
+
"emit_round_oom_killed",
|
|
34
36
|
"emit_round_progress",
|
|
35
37
|
"emit_round_resumed",
|
|
36
38
|
"emit_round_substrate_after",
|
|
@@ -296,18 +298,28 @@ def emit_round_mem_terminated(
|
|
|
296
298
|
def emit_round_mem_critical_sample(
|
|
297
299
|
log_dir: Path, *, round_num: int, consecutive: int, context: dict
|
|
298
300
|
) -> None:
|
|
299
|
-
"""Emit on
|
|
300
|
-
hard floor
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
"""Emit on each critical host_health sample inside _spawn_round's mid-round
|
|
302
|
+
hard floor, up to the per-episode cap (0.2.17, below) -- unlike
|
|
303
|
+
round_mem_terminated (deduped to once-per-episode), this fires on every
|
|
304
|
+
critical tick within that cap: the point is calibration visibility into
|
|
305
|
+
near-misses, so an operator watching the event stream sees the
|
|
306
|
+
critical_streak build (1, 2, ...) even on ticks that never reach the
|
|
307
|
+
terminate threshold (a healthy tick resets it before 3-in-a-row).
|
|
308
|
+
``context`` carries the same Pressure.context numbers as
|
|
305
309
|
round_mem_terminated. Fires only during critical pressure -- never during
|
|
306
310
|
warning/healthy -- so it adds no normal-operation noise, including while
|
|
307
311
|
``defer_to_cgroup`` steps back from terminating (the streak/context is
|
|
308
312
|
still useful calibration there; this event stays scoped to the
|
|
309
313
|
sample-level signal, distinct from mem_pressure_deferred_to_cgroup's
|
|
310
|
-
once-per-episode terminate-vs-defer notice).
|
|
314
|
+
once-per-episode terminate-vs-defer notice).
|
|
315
|
+
|
|
316
|
+
0.2.17: the caller caps this at ``2 * mem_critical_consecutive_samples``
|
|
317
|
+
consecutive ticks (1..6 at the default 3) -- a sustained-critical
|
|
318
|
+
don't-terminate run (cgroup-defer, or the off switch) would otherwise
|
|
319
|
+
write one event per ~10s tick for up to a whole ``round_timeout_s`` on a
|
|
320
|
+
permanently-deferred/off host. The cap is per streak-episode, not a
|
|
321
|
+
lifetime limit: any non-critical tick still resets the streak to 0, and
|
|
322
|
+
sampling resumes from 1 the next time critical pressure recurs."""
|
|
311
323
|
from agent_runner.events import ROUND_MEM_CRITICAL_SAMPLE, emit
|
|
312
324
|
|
|
313
325
|
emit(
|
|
@@ -320,13 +332,34 @@ def emit_round_mem_critical_sample(
|
|
|
320
332
|
|
|
321
333
|
|
|
322
334
|
def emit_host_cgroup_memory_limit(
|
|
323
|
-
log_dir: Path,
|
|
335
|
+
log_dir: Path,
|
|
336
|
+
*,
|
|
337
|
+
memory_max: int | None,
|
|
338
|
+
memory_swap_max: int | None,
|
|
339
|
+
cgroup_path: str | None,
|
|
340
|
+
swap_total_bytes: int | None = None,
|
|
341
|
+
swap_cap_pct: float | None = None,
|
|
342
|
+
memory_high: int | None = None,
|
|
343
|
+
advisory: str | None = None,
|
|
324
344
|
) -> None:
|
|
325
345
|
"""Emit once at serve startup: this process's cgroup v2 memory budget
|
|
326
346
|
(``metrics.cgroup_memory_limits``). ``None`` fields mean unlimited (or
|
|
327
347
|
cgroup v2 unavailable). Serve uses this once to decide whether the
|
|
328
348
|
mid-round hard floor can defer to kernel cgroup-OOM -- see
|
|
329
|
-
``emit_mem_pressure_deferred_to_cgroup`` below.
|
|
349
|
+
``emit_mem_pressure_deferred_to_cgroup`` below.
|
|
350
|
+
|
|
351
|
+
0.2.18 adds an optional startup swap-cap advisory
|
|
352
|
+
(``swap_total_bytes`` / ``swap_cap_pct`` / ``memory_high`` / ``advisory``)
|
|
353
|
+
as FIELDS on this SAME event -- never a separate event kind, and never an
|
|
354
|
+
auto-change to the operator's cgroup or unit. ``swap_total_bytes`` is the
|
|
355
|
+
host's total swap (``metrics.swap_total_bytes``); ``swap_cap_pct`` is
|
|
356
|
+
``memory_swap_max`` as a percentage of it (``None`` when either side is
|
|
357
|
+
unknown); ``memory_high`` is the bounding ancestor's ``memory.high`` in
|
|
358
|
+
bytes (``metrics.cgroup_memory_high``) -- ``None`` means unset (the
|
|
359
|
+
cgroup read the literal ``"max"``, or no finite value at all), never the
|
|
360
|
+
raw ``"max"`` token, so a caller can't mistake "unset" for a real
|
|
361
|
+
ceiling; ``advisory`` is the human-readable warning text when the swap
|
|
362
|
+
cap looks implausibly tight, else ``None``."""
|
|
330
363
|
from agent_runner.events import HOST_CGROUP_MEMORY_LIMIT, emit
|
|
331
364
|
|
|
332
365
|
emit(
|
|
@@ -335,6 +368,91 @@ def emit_host_cgroup_memory_limit(
|
|
|
335
368
|
memory_max=memory_max,
|
|
336
369
|
memory_swap_max=memory_swap_max,
|
|
337
370
|
cgroup_path=cgroup_path,
|
|
371
|
+
swap_total_bytes=swap_total_bytes,
|
|
372
|
+
swap_cap_pct=swap_cap_pct,
|
|
373
|
+
memory_high=memory_high,
|
|
374
|
+
advisory=advisory,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def emit_round_cgroup_memory(
|
|
379
|
+
log_dir: Path,
|
|
380
|
+
*,
|
|
381
|
+
round_num: int,
|
|
382
|
+
memory_current_peak: int,
|
|
383
|
+
memory_swap_current_peak: int,
|
|
384
|
+
events_high_delta: int,
|
|
385
|
+
events_max_delta: int,
|
|
386
|
+
events_oom_delta: int,
|
|
387
|
+
events_oom_kill_delta: int,
|
|
388
|
+
bounding_cgroup_path: str | None,
|
|
389
|
+
) -> None:
|
|
390
|
+
"""Emit once per round: the round's peak cgroup ``memory.current`` (+swap) and
|
|
391
|
+
the ``memory.events`` DELTAS (high/max/oom/oom_kill) over the round, read at the
|
|
392
|
+
bounding ancestor. Deltas, never absolutes -- an operator sees pressure BUILDING
|
|
393
|
+
without SSH. Peak is the max over the existing ~10s mid-round tick (NOT
|
|
394
|
+
``memory.peak``, which is cumulative since cgroup creation). No-op when this
|
|
395
|
+
host has no finite cgroup bound (``_serve_round._emit_round_cgroup_memory``
|
|
396
|
+
never calls this in that case).
|
|
397
|
+
|
|
398
|
+
``bounding_cgroup_path`` is deliberately NOT named ``cgroup_path`` --
|
|
399
|
+
``host_cgroup_memory_limit`` already uses that name for the LEAF (this
|
|
400
|
+
process's own) cgroup; here it names the BOUNDING ANCESTOR (the one whose
|
|
401
|
+
``memory.max`` actually binds). Same key, two meanings across the two
|
|
402
|
+
events an operator correlates would be a trap -- events.md pins field
|
|
403
|
+
semantics permanently once shipped, so the distinct name is chosen now."""
|
|
404
|
+
from agent_runner.events import ROUND_CGROUP_MEMORY, emit
|
|
405
|
+
|
|
406
|
+
emit(
|
|
407
|
+
log_dir,
|
|
408
|
+
ROUND_CGROUP_MEMORY,
|
|
409
|
+
round_num=round_num,
|
|
410
|
+
memory_current_peak=memory_current_peak,
|
|
411
|
+
memory_swap_current_peak=memory_swap_current_peak,
|
|
412
|
+
events_high_delta=events_high_delta,
|
|
413
|
+
events_max_delta=events_max_delta,
|
|
414
|
+
events_oom_delta=events_oom_delta,
|
|
415
|
+
events_oom_kill_delta=events_oom_kill_delta,
|
|
416
|
+
bounding_cgroup_path=bounding_cgroup_path,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def emit_round_oom_killed(
|
|
421
|
+
log_dir: Path,
|
|
422
|
+
*,
|
|
423
|
+
round_num: int,
|
|
424
|
+
log_path: Path,
|
|
425
|
+
log_bytes: int,
|
|
426
|
+
oom_kill_delta: int,
|
|
427
|
+
partial_log: bool,
|
|
428
|
+
) -> None:
|
|
429
|
+
"""Emit when the kernel cgroup-OOM-killed a round (``memory.events.oom_kill``
|
|
430
|
+
rose over the round, per ``_serve_round._maybe_emit_oom_killed`` -- folded
|
|
431
|
+
from the SAME ``cgroup_memory_usage()`` read ``round_cgroup_memory`` already
|
|
432
|
+
took, no second sysfs read). Symmetric with ``round_mem_terminated``
|
|
433
|
+
(supervisor-killed) -- this is the kernel-killed sibling.
|
|
434
|
+
|
|
435
|
+
POINTER-ONLY: ``log_path`` + ``log_bytes`` + ``oom_kill_delta`` -- NEVER the
|
|
436
|
+
round-log/transcript content itself (durability, secret-redaction, size all
|
|
437
|
+
argue against embedding it in events.jsonl). ``partial_log`` reports whether
|
|
438
|
+
the supervisor's own truncation trailer (see ``_mark_partial_log``) was
|
|
439
|
+
written onto the round log, so a later reader can tell the log's tail is
|
|
440
|
+
supervisor residue, not the agent's own output.
|
|
441
|
+
|
|
442
|
+
Classification is UNCHANGED by this event: the round still exits 137 and
|
|
443
|
+
counts toward the crash streak exactly as before -- this is pure
|
|
444
|
+
observability layered on top, computed and emitted after that decision is
|
|
445
|
+
already made."""
|
|
446
|
+
from agent_runner.events import ROUND_OOM_KILLED, emit
|
|
447
|
+
|
|
448
|
+
emit(
|
|
449
|
+
log_dir,
|
|
450
|
+
ROUND_OOM_KILLED,
|
|
451
|
+
round_num=round_num,
|
|
452
|
+
log_path=str(log_path),
|
|
453
|
+
log_bytes=log_bytes,
|
|
454
|
+
oom_kill_delta=oom_kill_delta,
|
|
455
|
+
partial_log=partial_log,
|
|
338
456
|
)
|
|
339
457
|
|
|
340
458
|
|
|
@@ -14,7 +14,7 @@ the plugin registry (``_monitor_registry``), and the cycle-edge wiring
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import re
|
|
17
|
-
from datetime import
|
|
17
|
+
from datetime import datetime, timedelta
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Any
|
|
20
20
|
|
|
@@ -407,15 +407,21 @@ def detect_rate_limit_active(
|
|
|
407
407
|
if kind == TRANSIENT_ERROR_RECOVERED:
|
|
408
408
|
return None
|
|
409
409
|
if kind == TRANSIENT_ERROR_DETECTED:
|
|
410
|
-
from agent_runner._throttle import
|
|
410
|
+
from agent_runner._throttle import (
|
|
411
|
+
_coerce_epoch_int,
|
|
412
|
+
_events_derived_reset,
|
|
413
|
+
_safe_epoch_iso,
|
|
414
|
+
)
|
|
411
415
|
|
|
412
416
|
agent = str(ev.get("agent", "unknown"))
|
|
413
417
|
classification = ev.get("classification", "unknown")
|
|
414
|
-
reset =
|
|
418
|
+
reset = _coerce_epoch_int(ev.get("reset_at_epoch"), 0, now_epoch=now)
|
|
415
419
|
if log_dir is not None:
|
|
416
420
|
reset = _events_derived_reset(log_dir, agent, str(classification), reset)
|
|
417
421
|
if reset > now:
|
|
418
|
-
iso =
|
|
422
|
+
iso = _safe_epoch_iso(reset)
|
|
423
|
+
if iso is None:
|
|
424
|
+
return None # poisoned epoch — don't blind-crash the detector
|
|
419
425
|
return _alert(
|
|
420
426
|
"rate_limit_active",
|
|
421
427
|
"warning",
|
|
@@ -9,15 +9,16 @@ cycle-edge wiring (``run_all_detectors``/``on_alert``) in ``monitor.py``.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
from collections import deque
|
|
12
|
-
from collections.abc import Iterable
|
|
12
|
+
from collections.abc import Iterable, Iterator
|
|
13
13
|
from dataclasses import dataclass, field
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
from typing import Any, Protocol
|
|
16
16
|
|
|
17
17
|
from agent_runner.api_types import ProjectState, ServiceMode, ServiceStatus, SystemMetrics
|
|
18
18
|
from agent_runner.builtin_plugins._constants import _TAIL_LINES
|
|
19
|
+
from agent_runner.clock import SYSTEM_CLOCK
|
|
19
20
|
from agent_runner.context_store import read_json
|
|
20
|
-
from agent_runner.events import
|
|
21
|
+
from agent_runner.events import iter_event_dicts, read_new
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class StateSource(Protocol):
|
|
@@ -82,22 +83,41 @@ class _EventTail:
|
|
|
82
83
|
)
|
|
83
84
|
|
|
84
85
|
def read(self, files: list[Path]) -> list[dict[str, Any]]:
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
new_events, self.offsets = read_new(files, self.offsets)
|
|
87
|
+
self.buffer.extend(new_events)
|
|
88
|
+
return list(self.buffer)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _tail_events_jsonl(
|
|
92
|
+
log_dir: Path,
|
|
93
|
+
*,
|
|
94
|
+
start_at_now: bool,
|
|
95
|
+
poll_interval_s: float,
|
|
96
|
+
) -> Iterator[dict[str, Any]]:
|
|
97
|
+
"""Polling tailer: yields parsed event dicts from events-*.jsonl files.
|
|
98
|
+
|
|
99
|
+
``start_at_now``: if True, snapshot current file sizes at init so existing
|
|
100
|
+
events are skipped (machine-consumption use case). If False, yield from
|
|
101
|
+
byte 0 of every file present at start (human-narrate use case).
|
|
102
|
+
|
|
103
|
+
Follows file rotation transparently — when a new events-YYYY-MM.jsonl
|
|
104
|
+
appears, it is picked up from byte 0.
|
|
105
|
+
"""
|
|
106
|
+
offsets: dict[Path, int] = {}
|
|
107
|
+
if start_at_now:
|
|
108
|
+
for path in sorted(log_dir.glob("events-*.jsonl")):
|
|
87
109
|
try:
|
|
88
|
-
|
|
110
|
+
offsets[path] = path.stat().st_size
|
|
89
111
|
except FileNotFoundError:
|
|
90
112
|
continue
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
self.offsets[path] = f.tell()
|
|
100
|
-
return list(self.buffer)
|
|
113
|
+
|
|
114
|
+
while True:
|
|
115
|
+
files = sorted(log_dir.glob("events-*.jsonl"))
|
|
116
|
+
new_events, offsets = read_new(files, offsets)
|
|
117
|
+
if new_events:
|
|
118
|
+
yield from new_events
|
|
119
|
+
else:
|
|
120
|
+
SYSTEM_CLOCK.sleep(poll_interval_s)
|
|
101
121
|
|
|
102
122
|
|
|
103
123
|
_MAX_TAIL_FILES = 20
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Plugin discovery scanner — replaces per-group importlib.metadata.entry_points
|
|
2
|
+
scans at startup with one cheap entry_points.txt parse. Hard-falls-back to
|
|
3
|
+
importlib.metadata on any parse failure; AGENT_RUNNER_PLUGIN_DISCOVERY=metadata
|
|
4
|
+
forces the fallback. Parity with importlib.metadata is pinned by
|
|
5
|
+
tests/unit/test_plugin_scan_parity.py per group."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import configparser
|
|
10
|
+
import os
|
|
11
|
+
import warnings
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _metadata_entry_points(group: str) -> list[tuple[str, str]]:
|
|
16
|
+
"""The old path: importlib.metadata's own distribution scan.
|
|
17
|
+
|
|
18
|
+
Imported lazily — importlib.metadata pulls in email.* (METADATA parsing)
|
|
19
|
+
at import time, which is most of the footprint this scanner exists to
|
|
20
|
+
avoid. Importing it at module top would tax every process even when the
|
|
21
|
+
fast path never falls back.
|
|
22
|
+
"""
|
|
23
|
+
from importlib.metadata import entry_points
|
|
24
|
+
|
|
25
|
+
return [(ep.name, ep.value) for ep in entry_points(group=group)]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Parsed entry_points.txt contents, memoized per dist-info file for the life
|
|
29
|
+
# of the process: {ep_file_path: {group: [(name, value), ...]}}. Package
|
|
30
|
+
# import calls _parse_entry_points_files once per plugin group (7x today,
|
|
31
|
+
# _HOOK_GROUPS + event_kinds + detectors) across the SAME sys.path, so every
|
|
32
|
+
# call after the first re-walks the identical dist-info directories --
|
|
33
|
+
# without this cache each one would re-open and re-parse (configparser) every
|
|
34
|
+
# entry_points.txt found, 7x over, just to pull out a different [group]
|
|
35
|
+
# section each time. Keyed by the file's path only (not mtime) -- a dist-info
|
|
36
|
+
# is not expected to change under a running process.
|
|
37
|
+
_PARSED_DIST_INFO_CACHE: dict[Path, dict[str, list[tuple[str, str]]]] = {}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _parsed_entry_points_by_group(ep_file: Path) -> dict[str, list[tuple[str, str]]]:
|
|
41
|
+
"""One ``entry_points.txt``, parsed once and bucketed by ``[group]``
|
|
42
|
+
section: ``{group: [(name, value), ...]}``. Memoized in
|
|
43
|
+
:data:`_PARSED_DIST_INFO_CACHE` so a second call for the same file (a
|
|
44
|
+
different plugin group asking about the same dist-info) reuses the
|
|
45
|
+
parse instead of re-opening + re-running configparser over it."""
|
|
46
|
+
cached = _PARSED_DIST_INFO_CACHE.get(ep_file)
|
|
47
|
+
if cached is not None:
|
|
48
|
+
return cached
|
|
49
|
+
parser = configparser.ConfigParser(interpolation=None)
|
|
50
|
+
parser.optionxform = str # preserve case -- entry-point names are case-sensitive
|
|
51
|
+
parser.read(ep_file, encoding="utf-8")
|
|
52
|
+
parsed = {section: list(parser[section].items()) for section in parser.sections()}
|
|
53
|
+
_PARSED_DIST_INFO_CACHE[ep_file] = parsed
|
|
54
|
+
return parsed
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _parse_entry_points_files(sys_path: list[str], group: str) -> list[tuple[str, str]]:
|
|
58
|
+
"""(name, value) pairs for ``group`` by parsing each ``*.dist-info/entry_points.txt``
|
|
59
|
+
found on ``sys_path``. A name seen in an earlier ``sys.path`` entry wins over a
|
|
60
|
+
later one — the same first-found precedence ``sys.path`` gives real imports.
|
|
61
|
+
|
|
62
|
+
A dropped duplicate emits a ``UserWarning`` naming the group, the duplicated
|
|
63
|
+
name, and which value won — the same diagnostic value the old
|
|
64
|
+
``importlib.metadata`` + ``ensure_unique`` path gave operators (that path
|
|
65
|
+
let both entries load and had ``ensure_unique`` reject the second with a
|
|
66
|
+
warning); silently dropping it here would regress that visibility.
|
|
67
|
+
"""
|
|
68
|
+
out: list[tuple[str, str]] = []
|
|
69
|
+
seen: dict[str, str] = {}
|
|
70
|
+
for entry in sys_path:
|
|
71
|
+
base = Path(entry) if entry else Path.cwd()
|
|
72
|
+
if not base.is_dir():
|
|
73
|
+
continue
|
|
74
|
+
for dist_info in base.glob("*.dist-info"):
|
|
75
|
+
ep_file = dist_info / "entry_points.txt"
|
|
76
|
+
if not ep_file.is_file():
|
|
77
|
+
continue
|
|
78
|
+
groups = _parsed_entry_points_by_group(ep_file)
|
|
79
|
+
if group not in groups:
|
|
80
|
+
continue
|
|
81
|
+
for name, value in groups[group]:
|
|
82
|
+
winner = seen.get(name)
|
|
83
|
+
if winner is None: # dedup: first sys.path entry wins
|
|
84
|
+
seen[name] = value
|
|
85
|
+
out.append((name, value))
|
|
86
|
+
elif winner != value:
|
|
87
|
+
warnings.warn(
|
|
88
|
+
f"duplicate entry_point {name!r} in group {group!r}: "
|
|
89
|
+
f"{value!r} dropped in favor of the earlier {winner!r} "
|
|
90
|
+
f"(first sys.path entry wins)",
|
|
91
|
+
stacklevel=2,
|
|
92
|
+
)
|
|
93
|
+
return out
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def scan_entry_points(sys_path: list[str], group: str) -> list[tuple[str, str]]:
|
|
97
|
+
"""(name, 'module:attr') pairs for ``group`` across ``sys_path``.
|
|
98
|
+
|
|
99
|
+
Hard-falls-back to ``importlib.metadata.entry_points`` on any parse
|
|
100
|
+
failure, or unconditionally when ``AGENT_RUNNER_PLUGIN_DISCOVERY=metadata``
|
|
101
|
+
is set (escape hatch for an environment where the scan and metadata
|
|
102
|
+
disagree).
|
|
103
|
+
"""
|
|
104
|
+
if os.environ.get("AGENT_RUNNER_PLUGIN_DISCOVERY") == "metadata":
|
|
105
|
+
return _metadata_entry_points(group)
|
|
106
|
+
try:
|
|
107
|
+
return _parse_entry_points_files(sys_path, group)
|
|
108
|
+
except Exception: # noqa: BLE001 — never let discovery crash import; fall back
|
|
109
|
+
return _metadata_entry_points(group)
|