cli-agent-runner 0.3.10__tar.gz → 0.3.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/.vulture-whitelist.py +25 -0
- cli_agent_runner-0.3.12/BACKLOG.md +195 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/CHANGELOG.md +23 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/PKG-INFO +5 -5
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/README.md +4 -4
- cli_agent_runner-0.3.12/agent_runner/_bounded.py +135 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_docgen.py +14 -1
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_lifecycle.py +1 -1
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_monitor_detectors.py +88 -1
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_monitor_registry.py +1 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_serve_policy.py +13 -5
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/config/__init__.py +7 -1
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/config/loader.py +107 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/config/models.py +78 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/config/parsers.py +71 -1
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/events.py +67 -13
- cli_agent_runner-0.3.12/agent_runner/goal.py +428 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/metrics.py +5 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/monitor.py +12 -2
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/runner.py +33 -1
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/service_unit.py +3 -1
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/vcs_state.py +12 -28
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/architecture.md +6 -3
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/commands.md +2 -2
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/configuration.md +118 -3
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/memory-efficiency.md +163 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/migrations/0.3.md +41 -15
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/pyproject.toml +2 -1
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/contract/test_public_api_surface.py +1 -0
- cli_agent_runner-0.3.12/tests/e2e/conftest.py +376 -0
- cli_agent_runner-0.3.12/tests/e2e/test_e2e_pre_oom.py +282 -0
- cli_agent_runner-0.3.12/tests/integration/test_goal_steering_e2e.py +479 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_spawn_round_mem_floor.py +149 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_architecture.py +2 -2
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_config_error_consistency.py +1 -0
- cli_agent_runner-0.3.12/tests/invariants/test_goal_firewall.py +166 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_import_footprint.py +5 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_module_boundaries.py +32 -5
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_no_vacuous_invariants.py +1 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_timeout_budget_invariant.py +27 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_config.py +438 -1
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_docgen.py +1 -1
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_events.py +57 -1
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_git_timeout.py +9 -4
- cli_agent_runner-0.3.12/tests/unit/test_goal_assessor.py +264 -0
- cli_agent_runner-0.3.12/tests/unit/test_goal_executor.py +222 -0
- cli_agent_runner-0.3.12/tests/unit/test_goal_firewall_behavioral.py +173 -0
- cli_agent_runner-0.3.12/tests/unit/test_goal_ledger.py +136 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_host_health.py +93 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_metrics.py +32 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_detectors.py +61 -1
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_outer_round_ceiling.py +37 -0
- cli_agent_runner-0.3.12/tests/unit/test_run_bounded.py +133 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_runner.py +86 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_service_unit.py +35 -0
- cli_agent_runner-0.3.10/BACKLOG.md +0 -43
- cli_agent_runner-0.3.10/tests/e2e/conftest.py +0 -162
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/.codecov.yml +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/.github/dependabot.yml +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/.gitignore +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/LICENSE +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/README.zh.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/SECURITY.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_emit/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_emit/agent.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_emit/memory.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_emit/rounds.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_emit/serve.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_install.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_monitor_state.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_notify.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_observe.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_plugin_scan.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_procwait.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_round_outcome.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/agent_runtime.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/api.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/_serve_cgroup.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/_serve_round.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/doctor_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/serve_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/config/validators.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/event_log.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/host_health.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/phase_select.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/build.sh +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/deploy/Dockerfile +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/deploy/docker-compose.yml +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/README.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/events.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/migrations/0.2.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/plugins.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/recipes/container-pi.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/recipes/deploy-all-in-one.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/runbook.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/docs/thesis.md +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/_clock.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/conftest.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/e2e/test_e2e_pi_resume.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_auto_stop_e2e.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_cooperative_grace_property.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_cooperative_stop_signal.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_reap_property.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_spawn_round_wedged.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_bdd_test_conventions.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_brake_defaults_off.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_container_stop_bound.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_hard_wall_signal.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_no_asyncio_select.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_no_direct_inspect_import.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_no_price_logic.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_no_version_refs.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_procwait_shield.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_round_alloc_growth.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_throttle_stateless_imports.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/linux/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/linux/test_memory_high_nudge_e2e.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime_container.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime_reap.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_apply_reap_grace_env.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_apply_resume_env.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_cgroup_probe.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_config_nested_strictness.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_config_resume_guard.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_cooperative_signal_env.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_doctor_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_event_log.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_exec_prefix.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_http_progress_epoch.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_kill_pgroup_shielding.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_loader_split.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_metrics_cgroup_usage.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_metrics_swap.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_dedup_rearm.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_loop.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_on_alert.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_notify.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_phase_select.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_plugin_scan_parity.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_plugins_config_fields.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_procwait.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_resolve_dirty_tree.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_round_outcome.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_round_outcome_agent_axis.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_policy.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_round.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_round_oom_killed.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_round_terminate.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_round_wait_exit.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_serve_wakeup.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_terminate_round_wait_exit.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_throttle_epoch_bound.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.3.10 → cli_agent_runner-0.3.12}/tests/unit/test_vcs_state.py +0 -0
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
# bare name below marks one field "used". SSOT = the @dataclass definitions in
|
|
7
7
|
# agent_runner/; a new field is covered by regenerating, never by editing here.
|
|
8
8
|
|
|
9
|
+
# agent_runner._bounded.BoundedResult
|
|
10
|
+
rc
|
|
11
|
+
stdout
|
|
12
|
+
stderr
|
|
13
|
+
timed_out
|
|
14
|
+
|
|
9
15
|
# agent_runner._monitor_state.LocalSource
|
|
10
16
|
log_dir
|
|
11
17
|
|
|
@@ -161,6 +167,11 @@ monitor
|
|
|
161
167
|
phases
|
|
162
168
|
plugins
|
|
163
169
|
schedule
|
|
170
|
+
goal
|
|
171
|
+
|
|
172
|
+
# agent_runner.config.models.GoalConfig
|
|
173
|
+
ledger
|
|
174
|
+
checks
|
|
164
175
|
|
|
165
176
|
# agent_runner.config.models.MonitorConfig
|
|
166
177
|
auth_fail_patterns
|
|
@@ -232,6 +243,12 @@ pause_windows
|
|
|
232
243
|
stash_idempotency_s
|
|
233
244
|
dirty_action
|
|
234
245
|
|
|
246
|
+
# agent_runner.config.models._GoalCheckConfig
|
|
247
|
+
name
|
|
248
|
+
cmd
|
|
249
|
+
cwd
|
|
250
|
+
timeout_s
|
|
251
|
+
|
|
235
252
|
# agent_runner.config.models._HostHealthBrakeConfig
|
|
236
253
|
memory_high
|
|
237
254
|
memory_high_step_pct
|
|
@@ -240,6 +257,9 @@ warning_consecutive_samples
|
|
|
240
257
|
# agent_runner.config.models._HostHealthDiskConfig
|
|
241
258
|
warning_pct
|
|
242
259
|
critical_pct
|
|
260
|
+
growth_window_s
|
|
261
|
+
disk_growth_pct_per_hr_warning
|
|
262
|
+
inode_growth_pct_per_hr_warning
|
|
243
263
|
|
|
244
264
|
# agent_runner.config.models._HostHealthMemoryConfig
|
|
245
265
|
avail_min_mb
|
|
@@ -278,6 +298,11 @@ codifies
|
|
|
278
298
|
guarded_by
|
|
279
299
|
current_state
|
|
280
300
|
|
|
301
|
+
# agent_runner.goal.Advisory
|
|
302
|
+
observation
|
|
303
|
+
question
|
|
304
|
+
confidence
|
|
305
|
+
|
|
281
306
|
# agent_runner.hooks.HookContext
|
|
282
307
|
work_dir
|
|
283
308
|
log_dir
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Backlog
|
|
2
|
+
|
|
3
|
+
Known follow-ups and candidate work — real design or behavior decisions, not
|
|
4
|
+
defects. Each entry carries enough context to act on without re-deriving it.
|
|
5
|
+
|
|
6
|
+
Items marked **NEEDS_DESIGN** change user-visible behavior and are held for an
|
|
7
|
+
explicit decision — they are not shipped silently in a maintenance release.
|
|
8
|
+
|
|
9
|
+
**Ordered by priority** (highest first). The number is a rank, not a stable id.
|
|
10
|
+
Priority follows the sharpest differentiator: prove and deepen host-health /
|
|
11
|
+
cooperative pre-OOM supervision on constrained hosts first (items 1–2); the
|
|
12
|
+
cognitive / goal-adherence layer (item 3) builds on that same control loop;
|
|
13
|
+
cross-CLI resume breadth and reach are lower.
|
|
14
|
+
|
|
15
|
+
## 1. Prove cooperative pre-OOM drain end-to-end on a constrained cgroup — NEEDS_DESIGN
|
|
16
|
+
|
|
17
|
+
The host-health mechanisms built across the 0.3.x line — the cgroup
|
|
18
|
+
memory-growth-rate detector (0.3.1), the `memory.high` soft-brake (0.3.7), and
|
|
19
|
+
the typed `cooperative_stop` signal (0.3.9) — have never been demonstrated
|
|
20
|
+
*together*, end-to-end, under real memory pressure on a small (~462 MB) cgroup.
|
|
21
|
+
The load-bearing property is: the agent is cooperatively drained **before** the
|
|
22
|
+
kernel OOM killer fires. It is provable with a synthetic memory-growing child
|
|
23
|
+
under a constrained cgroup — no live model needed. Build that harness, verify
|
|
24
|
+
the full detect → brake → drain loop, and pin the small-host calibration
|
|
25
|
+
(swap-floor + PSI thresholds) and reap-safety (PID-reuse, orphan reap) as a
|
|
26
|
+
durable, tested execution guarantee. Making this claim demonstrable is the
|
|
27
|
+
highest-value work on the list; everything below is secondary to it.
|
|
28
|
+
|
|
29
|
+
## 2. Runaway session-footprint host-health signal — NEEDS_DESIGN
|
|
30
|
+
|
|
31
|
+
A resumed CLI session file (e.g. pi's session `.jsonl`) grows unbounded across
|
|
32
|
+
rounds — a disk / inode pressure surface only the supervisor can see, and one
|
|
33
|
+
that cross-round resume (0.3.10) amplifies. Add a host disk/inode pressure
|
|
34
|
+
detector as a leading indicator so the pre-OOM brake can fire earlier and more
|
|
35
|
+
specifically. Frame it as a *host* signal (any growing per-agent footprint),
|
|
36
|
+
not a single-CLI feature.
|
|
37
|
+
|
|
38
|
+
## 3. Cognitive / goal-adherence supervision loop (observe → assess → steer → persist) — NEEDS_DESIGN
|
|
39
|
+
|
|
40
|
+
Today the supervisor watches *resource* health (memory / host) and *mechanical*
|
|
41
|
+
progress (no-usage, substrate fingerprint, repetitive-tool). It cannot tell when
|
|
42
|
+
an agent is "on a treadmill" — busy (produces output, edits files, exits 0) but
|
|
43
|
+
not actually advancing toward its goal. Add a second, **advisory** layer to the
|
|
44
|
+
same round-loop: a plugin observes each round, assesses progress toward a
|
|
45
|
+
declared goal, and — only when warranted — injects a short, grounded advisory
|
|
46
|
+
into the *next* round's context (through the existing `inject_context` path) that
|
|
47
|
+
the agent may heed or dismiss.
|
|
48
|
+
|
|
49
|
+
Design spine (composes as a fold over the durable event bus + `inject_context`,
|
|
50
|
+
**not** a new live callback — the round is a fresh subprocess, so cross-round
|
|
51
|
+
state is file/event-based; this re-adds a feedback seam an earlier release
|
|
52
|
+
removed for lack of a producer, but strictly narrower: a data-return / fold, not
|
|
53
|
+
arbitrary context mutation):
|
|
54
|
+
|
|
55
|
+
- A first-class `[goal]` config (the "goal command"). PRIMARY verification is
|
|
56
|
+
**objective**: operator-declared `goal.checks` (a command the supervisor runs
|
|
57
|
+
each round — exit 0 = satisfied, an optional numeric target) and milestones,
|
|
58
|
+
run through the reap-safe / timeout / killpg machinery so a check can never
|
|
59
|
+
orphan or blow the cgroup. An optional, objective-gated LLM assessor is a last
|
|
60
|
+
resort, never in the base install.
|
|
61
|
+
- A zero-cost default assessor: an objective **"treadmill signature"**
|
|
62
|
+
(activity-high + net-convergence-low, from signals already emitted; requires
|
|
63
|
+
persistence over K rounds + affirmative evidence, so false positives stay near
|
|
64
|
+
zero).
|
|
65
|
+
- **Hard invariant — the advisory / kill firewall:** the advisory path is
|
|
66
|
+
strictly separate from the give-up / kill decision. A possibly-wrong assessor
|
|
67
|
+
can steer but can NEVER influence a kill (a distinct event kind the kill path
|
|
68
|
+
never reads, plus an import firewall; the assessment type structurally has no
|
|
69
|
+
kill field). Killing stays on the conservative mechanical path.
|
|
70
|
+
- Steering is advisory only: a fixed schema that cannot express a command
|
|
71
|
+
(grounded observation + a question + confidence + a respected one-line
|
|
72
|
+
dismissal), edge-triggered, one concern at a time, dismiss-and-respect so it
|
|
73
|
+
never nags.
|
|
74
|
+
- CLI-agnostic: reads the agent's merged log + the goal; works around any CLI.
|
|
75
|
+
- Persistence rides a supervisor-owned, bounded, append-only lessons ledger
|
|
76
|
+
re-injected each round (subordinate to the disk host-health signal in item 2).
|
|
77
|
+
The supervisor never edits the agent's own memory files — it may *suggest* the
|
|
78
|
+
agent record a lesson.
|
|
79
|
+
|
|
80
|
+
Sequence this after items 1–2: it is a second consumer of the same
|
|
81
|
+
observe → assess → act control loop (mechanical actuator kills, advisory
|
|
82
|
+
actuator steers), and it earns its slot only once the resource edge is proven.
|
|
83
|
+
|
|
84
|
+
## 4. SECURITY containment guidance
|
|
85
|
+
|
|
86
|
+
agent-runner is lifecycle-safety + observability, not containment — a
|
|
87
|
+
deliberate boundary that is currently undocumented, so it reads as a gap. Add a
|
|
88
|
+
SECURITY section that states the division plainly and gives the operator
|
|
89
|
+
concrete containment guidance: a dedicated unprivileged user, a container/VM via
|
|
90
|
+
`exec_prefix`, egress limits, no passwordless sudo, read-only mounts. Docs-only.
|
|
91
|
+
|
|
92
|
+
## 5. claude cross-round resume — NEEDS_DESIGN
|
|
93
|
+
|
|
94
|
+
0.3.10 shipped resume for pi only (its `--session-id` is idempotent —
|
|
95
|
+
create-or-resume). claude's `--session-id` is create-only and `--resume` is a
|
|
96
|
+
separate flag, so it needs a create-vs-resume state machine gated on the prior
|
|
97
|
+
round's outcome (a crashed round re-creates rather than `--resume`-errors), plus
|
|
98
|
+
an empirical check that `claude -p --resume <id>` with the prompt on stdin
|
|
99
|
+
actually resumes **and** runs the new turn (rather than silently no-opping).
|
|
100
|
+
Keep it a thin per-CLI declaration; if it forces heavy session-state ownership,
|
|
101
|
+
that is the signal it belongs at the CLI layer, not here.
|
|
102
|
+
|
|
103
|
+
## 6. Surface resume in the preflight (doctor / peek) — NEEDS_DESIGN
|
|
104
|
+
|
|
105
|
+
resume is the one manifest-declared capability not shown by `doctor` / `peek`
|
|
106
|
+
(unlike `cooperative_stop` and the SIGTERM grace). An operator on a
|
|
107
|
+
resume-capable preset has no preflight way to confirm resume will engage for
|
|
108
|
+
their phase config — in particular the deliberate cold-start on an ambiguous
|
|
109
|
+
phase is silent until they notice no `session_resumed` events. Add a preflight
|
|
110
|
+
line (with a "resume will not engage — ambiguous phase" note). Requires a `peek`
|
|
111
|
+
schema bump.
|
|
112
|
+
|
|
113
|
+
## 7. Resume for gemini / codex / kimi — NEEDS_DESIGN
|
|
114
|
+
|
|
115
|
+
Each has a different resume shape (gemini resumes by index/latest, not a chosen
|
|
116
|
+
id; codex uses a subcommand plus a captured id). Extending resume means widening
|
|
117
|
+
the manifest `resume_flag` into a small sum type with a per-variant injection at
|
|
118
|
+
the argv-build site — never optional fields bolted onto one struct. Ship only
|
|
119
|
+
the variants whose resume is verified to work end-to-end.
|
|
120
|
+
|
|
121
|
+
## 8. Fleet fan-in view — NEEDS_DESIGN
|
|
122
|
+
|
|
123
|
+
Aggregate several supervised hosts into one place to watch, built on the
|
|
124
|
+
existing remote-relay primitives — the density story (many cheap / constrained
|
|
125
|
+
boxes under one supervisor). A larger effort; sequence it after the pre-OOM edge
|
|
126
|
+
(item 1) is proven, so it fans in verified hosts rather than claimed ones.
|
|
127
|
+
|
|
128
|
+
## 9. Runtime & distribution: single-binary edge install (language) — longer-horizon, gated
|
|
129
|
+
|
|
130
|
+
Python is the right foundation for the *plugin* surface — dynamic per-CLI
|
|
131
|
+
plugins and the declared-capability manifest are a Python strength, and "cover
|
|
132
|
+
every CLI via a plugin" is a core stance; a wholesale Rust/Go rewrite would
|
|
133
|
+
regress exactly that. The one place another language would genuinely help is
|
|
134
|
+
**distribution**: a static single binary ("copy one file, run it", no
|
|
135
|
+
interpreter or install step) suits constrained / edge hosts better than a Python
|
|
136
|
+
runtime plus a dependency. Footprint and speed are *not* reasons — the
|
|
137
|
+
supervisor's ~24 MB RSS is negligible against a GB-scale agent, and all internal
|
|
138
|
+
scheduling is already event-based (run time is model-latency-bound, not
|
|
139
|
+
CPU-bound in our code).
|
|
140
|
+
|
|
141
|
+
Held for an explicit decision, gated on two triggers: (a) the host-health /
|
|
142
|
+
pre-OOM edge (item 1) is proven and stable — you do not rewrite a moving,
|
|
143
|
+
unproven core; and (b) a distribution benchmark shows single-binary install is
|
|
144
|
+
the binding constraint. Even then, try Python-native single-binary packaging
|
|
145
|
+
first (a self-contained bundle) before any rewrite. If a rewrite is ever
|
|
146
|
+
warranted, the realistic shape is a small, stable core of the host-health / reap
|
|
147
|
+
logic in Rust or Go with the plugin layer staying Python or a declarative
|
|
148
|
+
format — a hybrid whose language boundary is the hard part, not a full port.
|
|
149
|
+
|
|
150
|
+
## 10. Per-CLI session-directory resolution
|
|
151
|
+
|
|
152
|
+
Resolve each CLI's session directory via its own environment precedence rather
|
|
153
|
+
than a hardcoded default (never assume `~/.pi` / `~/.claude` / etc.). Ties to
|
|
154
|
+
the existing work_dir-resolution work.
|
|
155
|
+
|
|
156
|
+
## 11. Test the resume boot-guard's per-phase branch
|
|
157
|
+
|
|
158
|
+
The boot guard that rejects a static resume flag already present in a command is
|
|
159
|
+
tested for the base agent but not the per-phase `[phases.<name>.agent]` override
|
|
160
|
+
branch. The override loop is correct by construction but untested — one small
|
|
161
|
+
test.
|
|
162
|
+
|
|
163
|
+
## 12. `_tail_events` / `_tail_events_jsonl` duplication
|
|
164
|
+
|
|
165
|
+
Three event-stream tailers now exist at deliberately different scopes, and they
|
|
166
|
+
are not accidental copies:
|
|
167
|
+
|
|
168
|
+
- `cli/events_cmd.py:_tail_events` — current-month-scoped
|
|
169
|
+
(`_current_month_events_file`), resets its offset to `0` on truncation.
|
|
170
|
+
- `_monitor_state.py:_tail_events_jsonl` — globs every `events-*.jsonl`;
|
|
171
|
+
re-exported through the `monitor.py` facade. It moved out of `api.py` in the
|
|
172
|
+
0.3.0 api split (`_install`/`_lifecycle`/`_observe`).
|
|
173
|
+
- `_throttle.py:_tail_events` — the newest two monthly files
|
|
174
|
+
(`glob("events-*.jsonl")[-2:]`).
|
|
175
|
+
|
|
176
|
+
Collapsing them means first deciding which scope is correct for each caller — a
|
|
177
|
+
structural decision, not a literal swap. (The older "character-identical
|
|
178
|
+
kind-read loop in `_throttle.py` and `monitor.py`" sub-concern has evaporated:
|
|
179
|
+
`monitor.py` no longer carries that loop — it moved to `_monitor_detectors.py`
|
|
180
|
+
and grew its own reset-ladder logic — and `_throttle.py` iterates forward over
|
|
181
|
+
the tailer generator, so the only shared text left is the single line
|
|
182
|
+
`kind = ev.get("event")`.)
|
|
183
|
+
|
|
184
|
+
## 13. `stash_orphan` loses the ref when the post-push listing fails
|
|
185
|
+
|
|
186
|
+
`vcs_state.stash_orphan` returns `None` in three cases; two are true no-ops. The
|
|
187
|
+
third — `git stash push` succeeded but the follow-up `git stash list` failed —
|
|
188
|
+
means the WIP *is* stashed yet callers read "nothing stashed" and report the
|
|
189
|
+
tree as ignored. It is already documented in-code as a KNOWN GAP and judged
|
|
190
|
+
effectively unreachable (a listing that fails microseconds after a push that
|
|
191
|
+
just succeeded in the same repo).
|
|
192
|
+
|
|
193
|
+
Deferred because closing it is a naming decision, not cleanup: no event kind
|
|
194
|
+
carries the meaning "stashed but ref lost" (`orphan_stash_failed` would be wrong
|
|
195
|
+
— the stash exists). Left as-is until that kind is designed.
|
|
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.12] - 2026-09-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `[goal]` config table: a markdown lessons `ledger` (listed in `[prompt] files` at index >= 1, and — since it must survive the round's own `git stash` — required to sit under `log_dir` or outside `work_dir`) plus `[[goal.checks]]` (`name`, `cmd` argv, optional `timeout_s`) — objective, CLI-agnostic checks the round child runs after each round's agent exits, each emitting a `goal_check` event (`satisfied` bool, optional `value`).
|
|
12
|
+
- Treadmill assessor (advisory-only): across the last 3 completed rounds, if every round showed activity but at least one goal check stayed unsatisfied with an unchanged signature, serve writes one edge-triggered advisory to the ledger and emits a `goal_assessment` event (never on convergence). The ledger's `[prompt] files` membership carries it into the next round's prompt.
|
|
13
|
+
- Advisory/kill firewall: the goal loop never CAUSES a kill or give-up — the kill/give-up path reads events by kind and stays blind to `goal_check`/`goal_assessment`. A goal check's own wall time does count toward the round's duration, so a long check can DELAY (never trigger) the fast-spin `stalled_no_progress`/`crash_loop` breakers; config load warns if the total check budget reaches that window.
|
|
14
|
+
|
|
15
|
+
Check timeouts fold into the round's own timeout budget. No config migration required (v0.3.11 silently ignores an unrecognized `[goal]` table). `peek --json` schema stays `2.6`.
|
|
16
|
+
|
|
17
|
+
## [0.3.11] - 2026-09-17
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Host disk/inode-growth WARNING detector: `[monitor.host_health.disk]` gains `disk_growth_pct_per_hr_warning`/`inode_growth_pct_per_hr_warning` (default 5.0%/hr each); a new `disk_growth` monitor alert kind (observability-only, no action taken).
|
|
21
|
+
- `agent_runner.events.register_plugin_kind(name)` restores a namespaced custom-event-kind affordance for out-of-tree plugins, validated against the builtin-kind namespace.
|
|
22
|
+
|
|
23
|
+
### Verified
|
|
24
|
+
- Pre-OOM host-pressure terminate: small-host PSI/swap calibration pinned as always-on tests-of-record, plus a gated real-cgroup property test (`AGENT_RUNNER_E2E_PI`) run live on a ~0.5 GB host — sustained host PSI-full ≥ 60 (measured 69–77 over 3 samples) terminated and reaped the agent with the host still responsive and no cgroup OOM kill; reproduced across two clean runs.
|
|
25
|
+
|
|
26
|
+
### Docs
|
|
27
|
+
- `docs/migrations/0.3.md` clarifies the 0.3.9 seam-subtraction adaptation contract (fold-in via `post_round_hooks`, `event_kinds` replacement) and documents `register_plugin_kind`.
|
|
28
|
+
|
|
29
|
+
No config migration required. `peek --json` schema stays `2.6`.
|
|
30
|
+
|
|
8
31
|
## [0.3.10] - 2026-09-17
|
|
9
32
|
|
|
10
33
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.12
|
|
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
|
|
@@ -51,7 +51,7 @@ full disks, runaway memory.
|
|
|
51
51
|
|
|
52
52
|
```
|
|
53
53
|
┌──────────────────────────────────────────┐
|
|
54
|
-
│ Layer 3: The Witness (monitor) │
|
|
54
|
+
│ Layer 3: The Witness (monitor) │ 14 detectors + auto-stop
|
|
55
55
|
├──────────────────────────────────────────┤
|
|
56
56
|
│ Layer 2: The Loop (serve) │ signal-trapping restart loop
|
|
57
57
|
├──────────────────────────────────────────┤
|
|
@@ -88,7 +88,7 @@ Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
|
|
|
88
88
|
|---|---|
|
|
89
89
|
| `init` / `install` / `uninstall` | `peek` — state snapshot |
|
|
90
90
|
| `start` / `stop` / `kill` | `watch` — peek in a refresh loop |
|
|
91
|
-
| `restart` / `status` | `monitor` —
|
|
91
|
+
| `restart` / `status` | `monitor` — 14 detectors, alerts, auto-stop |
|
|
92
92
|
| `round` / `serve` | `events` — query / stream events.jsonl |
|
|
93
93
|
| `upgrade` / `migrate` | `doctor` — read-only pre-flight |
|
|
94
94
|
|
|
@@ -113,10 +113,10 @@ Optionally pause the loop during off-hours: `[schedule]` run/pause windows gate
|
|
|
113
113
|
`serve` (override with `serve --ignore-schedule`) — see the `[schedule]` section
|
|
114
114
|
of [`docs/configuration.md`](docs/configuration.md).
|
|
115
115
|
|
|
116
|
-
## Monitor:
|
|
116
|
+
## Monitor: 14 detectors
|
|
117
117
|
|
|
118
118
|
Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
|
|
119
|
-
`mem_pressure`, `mem_pressure_gate_inert`, `mem_signal_unavailable`,
|
|
119
|
+
`disk_growth`, `mem_pressure`, `mem_pressure_gate_inert`, `mem_signal_unavailable`,
|
|
120
120
|
`network_fail`, `rate_limit_active`, `anomaly_repetitive_active`,
|
|
121
121
|
`supervisor_stale`. `mem_pressure` also drives a separate serve-loop
|
|
122
122
|
admission gate that defers or terminates rounds under real memory pressure —
|
|
@@ -13,7 +13,7 @@ full disks, runaway memory.
|
|
|
13
13
|
|
|
14
14
|
```
|
|
15
15
|
┌──────────────────────────────────────────┐
|
|
16
|
-
│ Layer 3: The Witness (monitor) │
|
|
16
|
+
│ Layer 3: The Witness (monitor) │ 14 detectors + auto-stop
|
|
17
17
|
├──────────────────────────────────────────┤
|
|
18
18
|
│ Layer 2: The Loop (serve) │ signal-trapping restart loop
|
|
19
19
|
├──────────────────────────────────────────┤
|
|
@@ -50,7 +50,7 @@ Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
|
|
|
50
50
|
|---|---|
|
|
51
51
|
| `init` / `install` / `uninstall` | `peek` — state snapshot |
|
|
52
52
|
| `start` / `stop` / `kill` | `watch` — peek in a refresh loop |
|
|
53
|
-
| `restart` / `status` | `monitor` —
|
|
53
|
+
| `restart` / `status` | `monitor` — 14 detectors, alerts, auto-stop |
|
|
54
54
|
| `round` / `serve` | `events` — query / stream events.jsonl |
|
|
55
55
|
| `upgrade` / `migrate` | `doctor` — read-only pre-flight |
|
|
56
56
|
|
|
@@ -75,10 +75,10 @@ Optionally pause the loop during off-hours: `[schedule]` run/pause windows gate
|
|
|
75
75
|
`serve` (override with `serve --ignore-schedule`) — see the `[schedule]` section
|
|
76
76
|
of [`docs/configuration.md`](docs/configuration.md).
|
|
77
77
|
|
|
78
|
-
## Monitor:
|
|
78
|
+
## Monitor: 14 detectors
|
|
79
79
|
|
|
80
80
|
Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
|
|
81
|
-
`mem_pressure`, `mem_pressure_gate_inert`, `mem_signal_unavailable`,
|
|
81
|
+
`disk_growth`, `mem_pressure`, `mem_pressure_gate_inert`, `mem_signal_unavailable`,
|
|
82
82
|
`network_fail`, `rate_limit_active`, `anomaly_repetitive_active`,
|
|
83
83
|
`supervisor_stale`. `mem_pressure` also drives a separate serve-loop
|
|
84
84
|
admission gate that defers or terminates rounds under real memory pressure —
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Sanctioned timeout-bounded subprocess primitive.
|
|
2
|
+
|
|
3
|
+
Runs a command in its OWN session under a wall-clock timeout, escalating
|
|
4
|
+
TERM -> grace -> killpg(SIGKILL) on breach so a hung process leaves no
|
|
5
|
+
descendants (a backgrounded grandchild included, since killpg targets the
|
|
6
|
+
whole process group). Returns a ``BoundedResult`` with ``timed_out=True`` on
|
|
7
|
+
breach instead of raising -- the caller decides how to react. No ``clock``
|
|
8
|
+
parameter: ``subprocess.communicate(timeout=)`` self-times, there is no raw
|
|
9
|
+
wall-clock read here to abstract.
|
|
10
|
+
|
|
11
|
+
Two hardening properties beyond the plain TERM/KILL escalation:
|
|
12
|
+
|
|
13
|
+
- **Bounded even against a session-detached pipe holder.** A descendant that
|
|
14
|
+
``setsid``-detaches out of the killed process group can still hold the
|
|
15
|
+
inherited stdout/stderr pipe open, so the final drain after ``killpg`` would
|
|
16
|
+
block forever waiting on EOF. Every drain is capped at ``_KILL_GRACE_S``; on
|
|
17
|
+
breach the read ends are closed and whatever was captured so far is returned.
|
|
18
|
+
The whole primitive therefore returns within ``timeout_s + 2*_KILL_GRACE_S``
|
|
19
|
+
(one grace for TERM->KILL, one for the post-kill drain) no matter what the
|
|
20
|
+
command spawns. A truly detached grandchild in its OWN session is NOT reaped
|
|
21
|
+
-- killpg cannot reach it; keep checks in a single process group.
|
|
22
|
+
- **Reap-on-interrupt.** A SIGTERM to the supervisor (see
|
|
23
|
+
``cli.common.install_term_handler``, which raises ``KeyboardInterrupt``)
|
|
24
|
+
propagates out of ``communicate()`` while the command runs in its own
|
|
25
|
+
session and would otherwise never be signalled. Any ``BaseException`` out of
|
|
26
|
+
a wait therefore SIGKILLs the whole group, closes the pipes, and reaps under
|
|
27
|
+
one bounded ``_KILL_GRACE_S`` wait before re-raising, mirroring
|
|
28
|
+
``agent_runtime._kill_pgroup``'s bounded-wait-after-SIGKILL.
|
|
29
|
+
|
|
30
|
+
Kept out of the startup import graph on purpose (see
|
|
31
|
+
tests/invariants/test_import_footprint.py): callers import it function-scope.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import os
|
|
37
|
+
import signal
|
|
38
|
+
import subprocess # noqa: TID251 — agent_runner._bounded is the sanctioned timeout-bounded subprocess primitive
|
|
39
|
+
from dataclasses import dataclass
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
_KILL_GRACE_S = 3 # grace between TERM and killpg(SIGKILL), and for the post-kill drain
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class BoundedResult:
|
|
47
|
+
rc: int
|
|
48
|
+
stdout: str
|
|
49
|
+
stderr: str
|
|
50
|
+
timed_out: bool
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _kill_group(proc: subprocess.Popen) -> None:
|
|
54
|
+
try:
|
|
55
|
+
os.killpg(proc.pid, signal.SIGKILL)
|
|
56
|
+
except OSError:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _close_pipes(proc: subprocess.Popen) -> None:
|
|
61
|
+
for pipe in (proc.stdout, proc.stderr):
|
|
62
|
+
if pipe is not None:
|
|
63
|
+
try:
|
|
64
|
+
pipe.close()
|
|
65
|
+
except OSError:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _drain_after_kill(proc: subprocess.Popen, exc: subprocess.TimeoutExpired) -> tuple[str, str]:
|
|
70
|
+
"""Best-effort drain after ``killpg(SIGKILL)`` that never blocks unbounded.
|
|
71
|
+
|
|
72
|
+
A ``setsid``-detached descendant can still hold the stdout/stderr pipe open,
|
|
73
|
+
so stop waiting on it: close the read ends and reap the (already-SIGKILLed)
|
|
74
|
+
immediate child under one more ``_KILL_GRACE_S`` cap. ``TimeoutExpired.output``
|
|
75
|
+
is ``bytes`` even on this text-mode Popen (the partial read captured at
|
|
76
|
+
timeout bypasses the text-decoding wrapper), so decode it here with the
|
|
77
|
+
same ``errors="replace"`` policy; fall back to empty strings if the
|
|
78
|
+
attribute isn't bytes at all."""
|
|
79
|
+
_close_pipes(proc)
|
|
80
|
+
try:
|
|
81
|
+
proc.wait(timeout=_KILL_GRACE_S)
|
|
82
|
+
except subprocess.TimeoutExpired:
|
|
83
|
+
pass
|
|
84
|
+
out = (
|
|
85
|
+
exc.output.decode("utf-8", errors="replace")
|
|
86
|
+
if isinstance(exc.output, (bytes, bytearray))
|
|
87
|
+
else ""
|
|
88
|
+
)
|
|
89
|
+
err = (
|
|
90
|
+
exc.stderr.decode("utf-8", errors="replace")
|
|
91
|
+
if isinstance(exc.stderr, (bytes, bytearray))
|
|
92
|
+
else ""
|
|
93
|
+
)
|
|
94
|
+
return out, err
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def run_bounded(argv: list[str], *, cwd: Path, timeout_s: int) -> BoundedResult:
|
|
98
|
+
"""Run argv in its own session under a wall-clock timeout, escalating
|
|
99
|
+
TERM -> grace -> killpg on breach so a hung command leaves no reachable
|
|
100
|
+
descendants. Returns timed_out=True on breach -- never raises on breach.
|
|
101
|
+
A ``BaseException`` out of a wait (e.g. a SIGTERM-driven KeyboardInterrupt)
|
|
102
|
+
SIGKILLs the group, closes the pipes, and reaps under one bounded
|
|
103
|
+
``_KILL_GRACE_S`` wait, then re-raises, so an interrupted check is torn
|
|
104
|
+
down (not left a zombie) rather than orphaned. ``errors="replace"`` keeps a
|
|
105
|
+
non-UTF-8 byte in the command's output from raising a UnicodeDecodeError
|
|
106
|
+
past the caller's fail-open guard."""
|
|
107
|
+
proc = subprocess.Popen(
|
|
108
|
+
argv,
|
|
109
|
+
cwd=cwd,
|
|
110
|
+
stdout=subprocess.PIPE,
|
|
111
|
+
stderr=subprocess.PIPE,
|
|
112
|
+
text=True,
|
|
113
|
+
errors="replace",
|
|
114
|
+
start_new_session=True,
|
|
115
|
+
)
|
|
116
|
+
try:
|
|
117
|
+
try:
|
|
118
|
+
out, err = proc.communicate(timeout=timeout_s)
|
|
119
|
+
return BoundedResult(rc=proc.returncode, stdout=out, stderr=err, timed_out=False)
|
|
120
|
+
except subprocess.TimeoutExpired:
|
|
121
|
+
proc.terminate()
|
|
122
|
+
try:
|
|
123
|
+
out, err = proc.communicate(timeout=_KILL_GRACE_S)
|
|
124
|
+
except subprocess.TimeoutExpired as exc:
|
|
125
|
+
_kill_group(proc)
|
|
126
|
+
out, err = _drain_after_kill(proc, exc)
|
|
127
|
+
return BoundedResult(rc=proc.returncode, stdout=out, stderr=err, timed_out=True)
|
|
128
|
+
except BaseException:
|
|
129
|
+
_kill_group(proc)
|
|
130
|
+
_close_pipes(proc)
|
|
131
|
+
try:
|
|
132
|
+
proc.wait(timeout=_KILL_GRACE_S)
|
|
133
|
+
except subprocess.TimeoutExpired:
|
|
134
|
+
pass
|
|
135
|
+
raise
|
|
@@ -21,6 +21,7 @@ from agent_runner.cli.init_cmd import _preset_names
|
|
|
21
21
|
from agent_runner.config import (
|
|
22
22
|
AgentConfig,
|
|
23
23
|
Config,
|
|
24
|
+
GoalConfig,
|
|
24
25
|
MonitorConfig,
|
|
25
26
|
MonitorHostHealthConfig,
|
|
26
27
|
PhasesConfig,
|
|
@@ -29,6 +30,7 @@ from agent_runner.config import (
|
|
|
29
30
|
RuntimeConfig,
|
|
30
31
|
ScheduleConfig,
|
|
31
32
|
VcsConfig,
|
|
33
|
+
_GoalCheckConfig,
|
|
32
34
|
_HostHealthBrakeConfig,
|
|
33
35
|
_HostHealthDiskConfig,
|
|
34
36
|
_HostHealthMemoryConfig,
|
|
@@ -72,6 +74,7 @@ _SECTIONS: list[tuple[str, type, list[tuple[str, type]]]] = [
|
|
|
72
74
|
("phases", PhasesConfig, []),
|
|
73
75
|
("plugins", PluginsConfig, []),
|
|
74
76
|
("schedule", ScheduleConfig, []),
|
|
77
|
+
("goal", GoalConfig, [("goal.checks", _GoalCheckConfig)]),
|
|
75
78
|
]
|
|
76
79
|
|
|
77
80
|
|
|
@@ -102,13 +105,23 @@ def _field_table(dc: type) -> list[str]:
|
|
|
102
105
|
return rows
|
|
103
106
|
|
|
104
107
|
|
|
108
|
+
# Nested sub-sections that are TOML arrays-of-tables (`[[name]]`), not single
|
|
109
|
+
# sub-tables (`[name]`): the parent field is a list/tuple of the sub-dataclass,
|
|
110
|
+
# so the header must double-bracket or a copy-pasted single `[goal.checks]` is
|
|
111
|
+
# rejected by the parser (_parse_goal expects a list).
|
|
112
|
+
_ARRAY_OF_TABLES_SECTIONS = frozenset({"goal.checks"})
|
|
113
|
+
|
|
114
|
+
|
|
105
115
|
def render_config_schema_table() -> str:
|
|
106
116
|
"""Markdown sub-sections per Config dataclass with field/type/default."""
|
|
107
117
|
parts: list[str] = []
|
|
108
118
|
for name, dc, nested in _SECTIONS:
|
|
109
119
|
parts += [f"### `[{name}]`", "", *_field_table(dc), ""]
|
|
110
120
|
for sub_name, sub_dc in nested:
|
|
111
|
-
|
|
121
|
+
brackets = (
|
|
122
|
+
f"[[{sub_name}]]" if sub_name in _ARRAY_OF_TABLES_SECTIONS else f"[{sub_name}]"
|
|
123
|
+
)
|
|
124
|
+
parts += [f"#### `{brackets}`", "", *_field_table(sub_dc), ""]
|
|
112
125
|
return "\n".join(parts).rstrip()
|
|
113
126
|
|
|
114
127
|
|
|
@@ -56,7 +56,7 @@ def outer_round_ceiling_s(cfg: Config, phase_arg: str | None) -> int:
|
|
|
56
56
|
(cfg.profile_for(p).runtime.round_budget_s for p in (cfg.phases.list or [])),
|
|
57
57
|
default=cfg.runtime.round_budget_s,
|
|
58
58
|
)
|
|
59
|
-
_, outer_ceiling = timeout_budget(inner)
|
|
59
|
+
_, outer_ceiling = timeout_budget(inner, goal_checks_allowance_s=cfg.goal_checks_allowance_s)
|
|
60
60
|
return outer_ceiling
|
|
61
61
|
|
|
62
62
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Pure anomaly detectors over events + metrics + log tails.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
14 built-in detectors. Two trigger ``auto_action="stop_service"``:
|
|
4
4
|
* oauth_fail — agent-reported auth failures, or an auth pattern in
|
|
5
5
|
short-exit logs (retrying burns API quota)
|
|
6
6
|
* disk_critical — disk_used_pct > 95% (writing more risks corruption)
|
|
@@ -218,6 +218,93 @@ def detect_disk_critical(
|
|
|
218
218
|
)
|
|
219
219
|
|
|
220
220
|
|
|
221
|
+
def detect_disk_growth(
|
|
222
|
+
metrics: list[dict[str, Any]],
|
|
223
|
+
*,
|
|
224
|
+
growth_window_s: int = 1800,
|
|
225
|
+
disk_growth_pct_per_hr_warning: float = 5.0,
|
|
226
|
+
inode_growth_pct_per_hr_warning: float = 5.0,
|
|
227
|
+
) -> Alert | None:
|
|
228
|
+
"""WARNING when disk_used_pct and/or inode_used_pct is climbing faster than
|
|
229
|
+
its configured %/hr floor -- a generalized host-disk/inode-pressure signal
|
|
230
|
+
(pi's cross-round resume amplifies session-log growth; not pi-specific).
|
|
231
|
+
|
|
232
|
+
Rate is Δpct over the OLDEST metrics sample landing within
|
|
233
|
+
``growth_window_s`` of the LATEST sample's ``ts`` (``agent_runner``'s
|
|
234
|
+
``parse_iso_ms``), divided by the elapsed hours between them -- real
|
|
235
|
+
metrics land at variable wall-clock gaps, so a fixed-interval
|
|
236
|
+
``(cur - prev)`` delta would misjudge the rate whenever polls drift.
|
|
237
|
+
Fewer than two samples inside the window (a cold start, or every prior
|
|
238
|
+
sample already aged out) returns ``None`` -- no baseline, no rate.
|
|
239
|
+
|
|
240
|
+
Observability only BY DESIGN, mirroring
|
|
241
|
+
:func:`host_health.cgroup_growth_rate_pressure`: unlike
|
|
242
|
+
:func:`detect_disk_critical`, this NEVER sets ``auto_action`` -- a
|
|
243
|
+
growth-rate signal is for an operator/plugin to act on, not a kill
|
|
244
|
+
trigger. Carries BOTH the disk and inode rate dims on one alert (not two
|
|
245
|
+
separate alert kinds) even when only one crosses its floor, so a reader
|
|
246
|
+
always sees the other dimension's current rate for context.
|
|
247
|
+
"""
|
|
248
|
+
latest_ts_raw = _latest(metrics, "ts")
|
|
249
|
+
latest_disk = _latest(metrics, "disk_used_pct")
|
|
250
|
+
if latest_ts_raw is None or latest_disk is None:
|
|
251
|
+
return None
|
|
252
|
+
latest_ts = parse_iso_ms(latest_ts_raw)
|
|
253
|
+
latest_inode = _latest(metrics, "inode_used_pct")
|
|
254
|
+
|
|
255
|
+
baseline: dict[str, Any] | None = None
|
|
256
|
+
baseline_ts: datetime | None = None
|
|
257
|
+
for m in metrics:
|
|
258
|
+
ts_raw = m.get("ts")
|
|
259
|
+
if ts_raw is None or "disk_used_pct" not in m:
|
|
260
|
+
continue
|
|
261
|
+
ts = parse_iso_ms(ts_raw)
|
|
262
|
+
age_s = (latest_ts - ts).total_seconds()
|
|
263
|
+
if age_s < 0 or age_s > growth_window_s:
|
|
264
|
+
continue # future-dated, or aged out of the growth window
|
|
265
|
+
# metrics is chronologically ascending, so the first in-window sample
|
|
266
|
+
# is the oldest -- the widest baseline; no later sample can be older.
|
|
267
|
+
baseline, baseline_ts = m, ts
|
|
268
|
+
break
|
|
269
|
+
|
|
270
|
+
if baseline is None or baseline_ts is None or baseline_ts >= latest_ts:
|
|
271
|
+
return None # no earlier in-window sample to diff against
|
|
272
|
+
elapsed_hr = (latest_ts - baseline_ts).total_seconds() / 3600.0
|
|
273
|
+
if elapsed_hr <= 0:
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
disk_rate = (latest_disk - baseline["disk_used_pct"]) / elapsed_hr
|
|
277
|
+
baseline_inode = baseline.get("inode_used_pct")
|
|
278
|
+
inode_rate = (
|
|
279
|
+
(latest_inode - baseline_inode) / elapsed_hr
|
|
280
|
+
if latest_inode is not None and baseline_inode is not None
|
|
281
|
+
else None
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
disk_trip = disk_rate >= disk_growth_pct_per_hr_warning
|
|
285
|
+
inode_trip = inode_rate is not None and inode_rate >= inode_growth_pct_per_hr_warning
|
|
286
|
+
if not disk_trip and not inode_trip:
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
bits = []
|
|
290
|
+
if disk_trip:
|
|
291
|
+
bits.append(f"disk +{disk_rate:.1f}%/hr (>= {disk_growth_pct_per_hr_warning})")
|
|
292
|
+
if inode_trip:
|
|
293
|
+
bits.append(f"inode +{inode_rate:.1f}%/hr (>= {inode_growth_pct_per_hr_warning})")
|
|
294
|
+
return _alert(
|
|
295
|
+
"disk_growth",
|
|
296
|
+
"warning",
|
|
297
|
+
f"{' and '.join(bits)} over the last {growth_window_s}s",
|
|
298
|
+
{
|
|
299
|
+
"disk_pct_per_hr": round(disk_rate, 2),
|
|
300
|
+
"disk_threshold_pct_per_hr": disk_growth_pct_per_hr_warning,
|
|
301
|
+
"inode_pct_per_hr": round(inode_rate, 2) if inode_rate is not None else None,
|
|
302
|
+
"inode_threshold_pct_per_hr": inode_growth_pct_per_hr_warning,
|
|
303
|
+
"window_s": growth_window_s,
|
|
304
|
+
},
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
221
308
|
def _latest_two(metrics: list[dict[str, Any]]) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
222
309
|
"""Return (current, previous) full metric dicts -- the last two history
|
|
223
310
|
entries (or ``{}`` when shorter). The mem-pressure ladder needs a delta
|