cli-agent-runner 0.3.6__tar.gz → 0.3.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.vulture-whitelist.py +7 -0
- cli_agent_runner-0.3.8/BACKLOG.md +43 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/CHANGELOG.md +16 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/CONTRIBUTING.md +5 -4
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/PKG-INFO +5 -3
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/README.md +4 -2
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_docgen.py +2 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_emit/__init__.py +6 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_emit/memory.py +73 -2
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_monitor_state.py +19 -19
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_throttle.py +6 -5
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/api.py +3 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/_serve_cgroup.py +103 -11
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/_serve_round.py +110 -9
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/common.py +5 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/doctor_cmd.py +21 -4
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/events_cmd.py +50 -121
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/serve_cmd.py +2 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/config/__init__.py +1 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/config/models.py +31 -5
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/config/parsers.py +41 -0
- cli_agent_runner-0.3.8/agent_runner/event_log.py +198 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/events.py +4 -47
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/host_health.py +6 -3
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/http_progress.py +8 -10
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/metrics.py +125 -6
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/claude.toml +3 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/vcs_state.py +39 -14
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/architecture.md +30 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/commands.md +9 -6
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/configuration.md +55 -2
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/long-running-agents.md +1 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/memory-efficiency.md +51 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/migrations/0.3.md +1 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/plugins.md +5 -2
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/recipes/pi.md +8 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/runbook.md +27 -2
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/_clock.py +6 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/_test_helpers.py +3 -3
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/contract/test_public_api_surface.py +3 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_plugin_owned_paths.py +36 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_spawn_round_mem_floor.py +521 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_architecture.py +16 -2
- cli_agent_runner-0.3.8/tests/invariants/test_brake_defaults_off.py +46 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_event_kinds_ssot.py +5 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_import_footprint.py +6 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_layer_2_loop_size.py +1 -1
- cli_agent_runner-0.3.8/tests/invariants/test_no_direct_inspect_import.py +47 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_peek_schema_version.py +3 -1
- cli_agent_runner-0.3.8/tests/invariants/test_throttle_stateless_imports.py +40 -0
- cli_agent_runner-0.3.8/tests/linux/test_memory_high_nudge_e2e.py +180 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cgroup_probe.py +279 -3
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config.py +20 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_nested_strictness.py +61 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_doctor_cmd.py +84 -0
- cli_agent_runner-0.3.8/tests/unit/test_event_log.py +174 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_events.py +1 -57
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_events_cmd.py +2 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_events_cmd_rollover.py +16 -11
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_events_cmd_tail.py +81 -1
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_exec_prefix.py +3 -3
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_presets.py +15 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_round_outcome.py +45 -0
- cli_agent_runner-0.3.8/tests/unit/test_serve_round.py +81 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_round_oom_killed.py +21 -0
- cli_agent_runner-0.3.6/BACKLOG.md +0 -98
- cli_agent_runner-0.3.6/tests/unit/test_serve_round.py +0 -34
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.codecov.yml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/dependabot.yml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/.gitignore +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/LICENSE +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/README.zh.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/SECURITY.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_emit/agent.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_emit/plugins.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_emit/rounds.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_emit/serve.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_install.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_lifecycle.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_notify.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_observe.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_plugin_checksum.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_plugin_sandbox.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_plugin_scan.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_procwait.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_round_outcome.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_sandbox_probe.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_serve_policy.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/agent_runtime.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/config/loader.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/config/validators.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/phase_select.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/runner.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/build.sh +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/deploy/Dockerfile +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/deploy/docker-compose.yml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/README.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/events.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/migrations/0.2.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/recipes/container-pi.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/recipes/deploy-all-in-one.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/docs/thesis.md +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/pyproject.toml +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/conftest.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_auto_stop_e2e.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_cooperative_grace_property.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_reap_property.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_spawn_round_wedged.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_bdd_test_conventions.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_builtin_plugin_names_sync.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_container_stop_bound.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_kill_tests_not_skipped.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_asyncio_select.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_price_logic.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_vacuous_invariants.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_no_version_refs.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_procwait_shield.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_round_alloc_growth.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_timeout_budget_invariant.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/linux/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/linux/test_plugin_sandbox_kill.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime_container.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime_reap.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_apply_reap_grace_env.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_builtin_provenance.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_collapse_spawn_decisions.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_dispatch_dirty_owner_branch.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_doctor_sandbox.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_example_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_gate_serve_boot.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_git_timeout.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_host_health.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_http_progress_epoch.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_kill_pgroup_shielding.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_load_fail_closed.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_loader_split.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_manifest_spawn_hooks.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_metrics_cgroup_usage.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_metrics_swap.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_dedup_rearm.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_loop.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_on_alert.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_notify.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_peek_sandbox_block.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_phase_select.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugin_checksum.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugin_name_mismatch.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugin_sandbox_wire.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugin_scan_parity.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_plugins_config_fields.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_primary_prompt_file_home.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_procwait.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_round_outcome_agent_axis.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_sandbox_probe.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_policy.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_round_terminate.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_round_wait_exit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_serve_wakeup.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_spawn_seam.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_terminate_round_wait_exit.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_throttle_epoch_bound.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.3.6 → cli_agent_runner-0.3.8}/tests/unit/test_vcs_state.py +0 -0
|
@@ -195,6 +195,7 @@ supervisor_stale_threshold_s
|
|
|
195
195
|
disk
|
|
196
196
|
memory
|
|
197
197
|
pressure
|
|
198
|
+
brake
|
|
198
199
|
|
|
199
200
|
# agent_runner.config.models.PhaseOverride
|
|
200
201
|
round_budget_s
|
|
@@ -255,6 +256,11 @@ pause_windows
|
|
|
255
256
|
stash_idempotency_s
|
|
256
257
|
dirty_action
|
|
257
258
|
|
|
259
|
+
# agent_runner.config.models._HostHealthBrakeConfig
|
|
260
|
+
memory_high
|
|
261
|
+
memory_high_step_pct
|
|
262
|
+
warning_consecutive_samples
|
|
263
|
+
|
|
258
264
|
# agent_runner.config.models._HostHealthDiskConfig
|
|
259
265
|
warning_pct
|
|
260
266
|
critical_pct
|
|
@@ -270,6 +276,7 @@ some_avg10_warning
|
|
|
270
276
|
critical_consecutive_samples
|
|
271
277
|
in_round_terminate
|
|
272
278
|
cgroup_growth_rate_warning_mb_per_min
|
|
279
|
+
in_round_nudge
|
|
273
280
|
|
|
274
281
|
# agent_runner.context_store.OrphanState
|
|
275
282
|
round_num
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Backlog
|
|
2
|
+
|
|
3
|
+
Known follow-ups that are real design or behavior decisions, not defects. Each
|
|
4
|
+
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
|
+
|
|
11
|
+
## 1. `_tail_events` / `_tail_events_jsonl` duplication
|
|
12
|
+
|
|
13
|
+
Three event-stream tailers now exist at deliberately different scopes, and they
|
|
14
|
+
are not accidental copies:
|
|
15
|
+
|
|
16
|
+
- `cli/events_cmd.py:_tail_events` — current-month-scoped
|
|
17
|
+
(`_current_month_events_file`), resets its offset to `0` on truncation.
|
|
18
|
+
- `_monitor_state.py:_tail_events_jsonl` — globs every `events-*.jsonl`;
|
|
19
|
+
re-exported through the `monitor.py` facade. It moved out of `api.py` in the
|
|
20
|
+
0.3.0 api split (`_install`/`_lifecycle`/`_observe`).
|
|
21
|
+
- `_throttle.py:_tail_events` — the newest two monthly files
|
|
22
|
+
(`glob("events-*.jsonl")[-2:]`).
|
|
23
|
+
|
|
24
|
+
Collapsing them means first deciding which scope is correct for each caller — a
|
|
25
|
+
structural decision, not a literal swap. (The older "character-identical
|
|
26
|
+
kind-read loop in `_throttle.py` and `monitor.py`" sub-concern has evaporated:
|
|
27
|
+
`monitor.py` no longer carries that loop — it moved to `_monitor_detectors.py`
|
|
28
|
+
and grew its own reset-ladder logic — and `_throttle.py` iterates forward over
|
|
29
|
+
the tailer generator, so the only shared text left is the single line
|
|
30
|
+
`kind = ev.get("event")`.)
|
|
31
|
+
|
|
32
|
+
## 2. `stash_orphan` loses the ref when the post-push listing fails
|
|
33
|
+
|
|
34
|
+
`vcs_state.stash_orphan` returns `None` in three cases; two are true no-ops. The
|
|
35
|
+
third — `git stash push` succeeded but the follow-up `git stash list` failed —
|
|
36
|
+
means the WIP *is* stashed yet callers read "nothing stashed" and report the
|
|
37
|
+
tree as ignored. It is already documented in-code as a KNOWN GAP and judged
|
|
38
|
+
effectively unreachable (a listing that fails microseconds after a push that
|
|
39
|
+
just succeeded in the same repo).
|
|
40
|
+
|
|
41
|
+
Deferred because closing it is a naming decision, not cleanup: no event kind
|
|
42
|
+
carries the meaning "stashed but ref lost" (`orphan_stash_failed` would be wrong
|
|
43
|
+
— the stash exists). Left as-is until that kind is designed.
|
|
@@ -5,6 +5,22 @@ 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.8] - 2026-09-16
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- `try_auto_commit` (`vcs.dirty_action = "auto_commit"`) now honors the plugin-owned-paths registry, matching `stash_orphan`: paths registered via `register_plugin_owned_paths` are excluded from the auto-commit, so a plugin's deliverables no longer land in the agent's git history.
|
|
12
|
+
- `round_oom_killed` now also carries `events_oom_kill_delta` — the canonical name, sibling-consistent with `round_cgroup_memory`'s counters; the original bare `oom_kill_delta` is retained as an alias (a future breaking release drops it).
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- The `claude` preset arms the repetitive-tool-call anomaly detector (`[monitor] anomaly_repetitive_window = 30`, `anomaly_repetitive_threshold = 15`) — trips only on an egregious stuck loop (the same tool+target ≥15× within 30 calls). The global default stays opt-in (disabled).
|
|
16
|
+
|
|
17
|
+
## [0.3.7] - 2026-09-15
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Opt-in cgroup v2 `memory.high` soft-brake (`[monitor.host_health.brake] memory_high`, default off): under sustained warning-level memory pressure the supervisor reversibly writes `memory.high` on its OWN cgroup leaf to throttle a growing round before the host swaps, then restores it on recovery or at round end. `memory_high_step_pct` defaults to `0` = cap-at-current (throttle further growth with no synchronous reclaim burst — safe for SD-backed hosts); `step_pct >= 1` is opt-in aggressive reclaim that dumps ~step% of the leaf to swap at engage. Fail-open; arms only when the leaf is delegated.
|
|
21
|
+
- Opt-in early cooperative SIGTERM (`[monitor.host_health.pressure] in_round_nudge`, default off): fires the hard floor's SIGTERM two samples earlier so a cooperative agent gets its wrap-up grace; recorded as `round_mem_terminated` with a new `tier` field so the mem-loop give-up still converges.
|
|
22
|
+
- New events `memory_high_engaged` / `memory_high_released` / `memory_high_write_failed`; `doctor` and `peek --json` (schema `2.5`) report the brake as `off | armed | inert(<reason>)`.
|
|
23
|
+
|
|
8
24
|
## [0.3.6] - 2026-09-15
|
|
9
25
|
|
|
10
26
|
### Changed
|
|
@@ -75,10 +75,11 @@ into `docs/commands.md` from the registry) plus `CHANGELOG.md`. Between them
|
|
|
75
75
|
they carry a config rewrite and per-version notes; a doc that only restates the
|
|
76
76
|
changelog is upkeep with no reader.
|
|
77
77
|
|
|
78
|
-
`docs/migrations/0.2.md`
|
|
79
|
-
steps a config rewrite can't do (recovery
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
`docs/migrations/0.2.md` and `docs/migrations/0.3.md` are the two surviving
|
|
79
|
+
guides: they collect the few manual steps a config rewrite can't do (recovery
|
|
80
|
+
procedures, a systemd regen, a mode switch, the 0.3.0 breaking-release port).
|
|
81
|
+
Keep the relevant CHANGELOG entries terse and link the matching one
|
|
82
|
+
(`See docs/migrations/0.3.md.`).
|
|
82
83
|
|
|
83
84
|
Migration guides are **not** append-only. A per-version guide is pruned once its
|
|
84
85
|
content becomes executable (`migrate`), generated (the transform list),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
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
|
|
@@ -93,7 +93,7 @@ Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
|
|
|
93
93
|
| `start` / `stop` / `kill` | `watch` — peek in a refresh loop |
|
|
94
94
|
| `restart` / `status` | `monitor` — 13 detectors, alerts, auto-stop |
|
|
95
95
|
| `round` / `serve` | `events` — query / stream events.jsonl |
|
|
96
|
-
| `upgrade` / `migrate` | |
|
|
96
|
+
| `upgrade` / `migrate` | `doctor` — read-only pre-flight |
|
|
97
97
|
|
|
98
98
|
Verb reference: [`docs/commands.md`](docs/commands.md).
|
|
99
99
|
|
|
@@ -123,7 +123,9 @@ Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
|
|
|
123
123
|
`network_fail`, `rate_limit_active`, `anomaly_repetitive_active`,
|
|
124
124
|
`supervisor_stale`. `mem_pressure` also drives a separate serve-loop
|
|
125
125
|
admission gate that defers or terminates rounds under real memory pressure —
|
|
126
|
-
see `docs/architecture.md`.
|
|
126
|
+
see `docs/architecture.md`. An opt-in soft-brake (`[monitor.host_health.brake]`,
|
|
127
|
+
default off) can additionally cap the serve leaf's `memory.high` under sustained
|
|
128
|
+
pressure on a delegated cgroup.
|
|
127
129
|
|
|
128
130
|
**Auto-stop the service** (continuing is harmful):
|
|
129
131
|
- `oauth_fail` — burning API quota on auth-rejected rounds
|
|
@@ -52,7 +52,7 @@ Full walkthrough: [`docs/quickstart.md`](docs/quickstart.md).
|
|
|
52
52
|
| `start` / `stop` / `kill` | `watch` — peek in a refresh loop |
|
|
53
53
|
| `restart` / `status` | `monitor` — 13 detectors, alerts, auto-stop |
|
|
54
54
|
| `round` / `serve` | `events` — query / stream events.jsonl |
|
|
55
|
-
| `upgrade` / `migrate` | |
|
|
55
|
+
| `upgrade` / `migrate` | `doctor` — read-only pre-flight |
|
|
56
56
|
|
|
57
57
|
Verb reference: [`docs/commands.md`](docs/commands.md).
|
|
58
58
|
|
|
@@ -82,7 +82,9 @@ Notify only: `timeout_rate`, `hung`, `orphan_chain`, `disk_warning`,
|
|
|
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 —
|
|
85
|
-
see `docs/architecture.md`.
|
|
85
|
+
see `docs/architecture.md`. An opt-in soft-brake (`[monitor.host_health.brake]`,
|
|
86
|
+
default off) can additionally cap the serve leaf's `memory.high` under sustained
|
|
87
|
+
pressure on a delegated cgroup.
|
|
86
88
|
|
|
87
89
|
**Auto-stop the service** (continuing is harmful):
|
|
88
90
|
- `oauth_fail` — burning API quota on auth-rejected rounds
|
|
@@ -29,6 +29,7 @@ from agent_runner.config import (
|
|
|
29
29
|
RuntimeConfig,
|
|
30
30
|
ScheduleConfig,
|
|
31
31
|
VcsConfig,
|
|
32
|
+
_HostHealthBrakeConfig,
|
|
32
33
|
_HostHealthDiskConfig,
|
|
33
34
|
_HostHealthMemoryConfig,
|
|
34
35
|
_HostHealthPressureConfig,
|
|
@@ -66,6 +67,7 @@ _SECTIONS: list[tuple[str, type, list[tuple[str, type]]]] = [
|
|
|
66
67
|
("monitor.host_health.disk", _HostHealthDiskConfig),
|
|
67
68
|
("monitor.host_health.memory", _HostHealthMemoryConfig),
|
|
68
69
|
("monitor.host_health.pressure", _HostHealthPressureConfig),
|
|
70
|
+
("monitor.host_health.brake", _HostHealthBrakeConfig),
|
|
69
71
|
],
|
|
70
72
|
),
|
|
71
73
|
("phases", PhasesConfig, []),
|
|
@@ -32,6 +32,9 @@ from agent_runner._emit.memory import ( # noqa: F401 — public re-export
|
|
|
32
32
|
emit_cgroup_growth_rate_warning,
|
|
33
33
|
emit_host_cgroup_memory_limit,
|
|
34
34
|
emit_mem_pressure_deferred_to_cgroup,
|
|
35
|
+
emit_memory_high_engaged,
|
|
36
|
+
emit_memory_high_released,
|
|
37
|
+
emit_memory_high_write_failed,
|
|
35
38
|
emit_round_cgroup_memory,
|
|
36
39
|
emit_round_deferred,
|
|
37
40
|
emit_round_mem_critical_sample,
|
|
@@ -88,6 +91,9 @@ __all__ = [
|
|
|
88
91
|
"emit_mem_loop",
|
|
89
92
|
"emit_mem_loop_persistent",
|
|
90
93
|
"emit_mem_pressure_deferred_to_cgroup",
|
|
94
|
+
"emit_memory_high_engaged",
|
|
95
|
+
"emit_memory_high_released",
|
|
96
|
+
"emit_memory_high_write_failed",
|
|
91
97
|
"emit_plugin_builtin_name_squat",
|
|
92
98
|
"emit_plugin_checksum_mismatch",
|
|
93
99
|
"emit_plugin_sandbox_degraded",
|
|
@@ -36,6 +36,7 @@ def emit_round_mem_terminated(
|
|
|
36
36
|
message: str,
|
|
37
37
|
consecutive: int,
|
|
38
38
|
context: dict,
|
|
39
|
+
tier: str = "terminate",
|
|
39
40
|
) -> None:
|
|
40
41
|
"""Emit when _spawn_round's mid-round hard floor terminated a ballooning
|
|
41
42
|
round on critical host_health pressure -- the actual coma-preventer (a
|
|
@@ -45,7 +46,15 @@ def emit_round_mem_terminated(
|
|
|
45
46
|
``consecutive`` (the critical_streak that crossed the threshold)
|
|
46
47
|
and ``context`` (Pressure.context -- the actual psi/mem numbers, e.g.
|
|
47
48
|
psi_full_avg10) make the kill legible from the event stream alone, so an
|
|
48
|
-
operator can retune host_health thresholds without SSH.
|
|
49
|
+
operator can retune host_health thresholds without SSH.
|
|
50
|
+
|
|
51
|
+
``tier`` (``"terminate"`` | ``"nudge"``) distinguishes the hard floor's
|
|
52
|
+
``_terminate_round`` (TERM -> grace -> killpg) from the early cooperative
|
|
53
|
+
nudge (a bare ``proc.terminate()`` two samples earlier, no wait, no
|
|
54
|
+
killpg) -- both fire this SAME event kind so ``round_was_mem_terminated``
|
|
55
|
+
and the ``mem_loop`` give-up breaker count a nudged round exactly like a
|
|
56
|
+
hard-terminated one; the default keeps every existing caller byte-
|
|
57
|
+
compatible."""
|
|
49
58
|
from agent_runner.events import ROUND_MEM_TERMINATED, emit
|
|
50
59
|
|
|
51
60
|
emit(
|
|
@@ -57,6 +66,7 @@ def emit_round_mem_terminated(
|
|
|
57
66
|
message=message,
|
|
58
67
|
consecutive=consecutive,
|
|
59
68
|
context=context,
|
|
69
|
+
tier=tier,
|
|
60
70
|
)
|
|
61
71
|
|
|
62
72
|
|
|
@@ -207,13 +217,20 @@ def emit_round_oom_killed(
|
|
|
207
217
|
took, no second sysfs read). Symmetric with ``round_mem_terminated``
|
|
208
218
|
(supervisor-killed) -- this is the kernel-killed sibling.
|
|
209
219
|
|
|
210
|
-
POINTER-ONLY: ``log_path`` + ``log_bytes`` +
|
|
220
|
+
POINTER-ONLY: ``log_path`` + ``log_bytes`` + the oom-kill delta -- NEVER the
|
|
211
221
|
round-log/transcript content itself (durability, secret-redaction, size all
|
|
212
222
|
argue against embedding it in events.jsonl). ``partial_log`` reports whether
|
|
213
223
|
the supervisor's own truncation trailer (see ``_mark_partial_log``) was
|
|
214
224
|
written onto the round log, so a later reader can tell the log's tail is
|
|
215
225
|
supervisor residue, not the agent's own output.
|
|
216
226
|
|
|
227
|
+
The oom-kill delta rides under TWO field names with the identical value:
|
|
228
|
+
``events_oom_kill_delta`` is the canonical name (sibling-consistent with
|
|
229
|
+
``round_cgroup_memory``'s ``events_oom_kill_delta``); ``oom_kill_delta`` is
|
|
230
|
+
the retained legacy alias. The append-only event contract keeps both until a
|
|
231
|
+
future breaking release drops the bare alias -- so a single ``oom_kill_delta``
|
|
232
|
+
argument single-sources both fields, which can never diverge.
|
|
233
|
+
|
|
217
234
|
Classification is UNCHANGED by this event: the round still exits 137 and
|
|
218
235
|
counts toward the crash streak exactly as before -- this is pure
|
|
219
236
|
observability layered on top, computed and emitted after that decision is
|
|
@@ -226,6 +243,7 @@ def emit_round_oom_killed(
|
|
|
226
243
|
round_num=round_num,
|
|
227
244
|
log_path=str(log_path),
|
|
228
245
|
log_bytes=log_bytes,
|
|
246
|
+
events_oom_kill_delta=oom_kill_delta,
|
|
229
247
|
oom_kill_delta=oom_kill_delta,
|
|
230
248
|
partial_log=partial_log,
|
|
231
249
|
)
|
|
@@ -246,6 +264,59 @@ def emit_mem_pressure_deferred_to_cgroup(
|
|
|
246
264
|
emit(log_dir, MEM_PRESSURE_DEFERRED_TO_CGROUP, pid=pid, signal=signal, message=message)
|
|
247
265
|
|
|
248
266
|
|
|
267
|
+
def emit_memory_high_engaged(
|
|
268
|
+
log_dir: Path,
|
|
269
|
+
*,
|
|
270
|
+
round_num: int,
|
|
271
|
+
previous: str,
|
|
272
|
+
written: int,
|
|
273
|
+
memory_current: int,
|
|
274
|
+
write_ms: int,
|
|
275
|
+
signal: str,
|
|
276
|
+
context: dict,
|
|
277
|
+
rate_mb_per_min: float | None,
|
|
278
|
+
) -> None:
|
|
279
|
+
"""Emit when the reversible cgroup memory.high soft-brake engaged on serve's
|
|
280
|
+
own leaf under sustained warning pressure. ``previous``/``written`` bracket
|
|
281
|
+
the reversible write; ``signal``/``context`` are the triggering Pressure;
|
|
282
|
+
``rate_mb_per_min`` rides along as growth-rate context for Pi calibration."""
|
|
283
|
+
from agent_runner.events import MEMORY_HIGH_ENGAGED, emit
|
|
284
|
+
|
|
285
|
+
emit(
|
|
286
|
+
log_dir,
|
|
287
|
+
MEMORY_HIGH_ENGAGED,
|
|
288
|
+
round_num=round_num,
|
|
289
|
+
previous=previous,
|
|
290
|
+
written=written,
|
|
291
|
+
memory_current=memory_current,
|
|
292
|
+
write_ms=write_ms,
|
|
293
|
+
signal=signal,
|
|
294
|
+
context=context,
|
|
295
|
+
rate_mb_per_min=rate_mb_per_min,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def emit_memory_high_released(log_dir: Path, *, round_num: int, reason: str) -> None:
|
|
300
|
+
"""Emit when the soft-brake was restored to its stashed value. ``reason`` is
|
|
301
|
+
``recovered`` (N healthy ticks) or ``round_end`` (the finally restore, which
|
|
302
|
+
covers every exit path out of the round incl. wedged/exception)."""
|
|
303
|
+
from agent_runner.events import MEMORY_HIGH_RELEASED, emit
|
|
304
|
+
|
|
305
|
+
emit(log_dir, MEMORY_HIGH_RELEASED, round_num=round_num, reason=reason)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def emit_memory_high_write_failed(log_dir: Path, *, round_num: int, errno: int | None) -> None:
|
|
309
|
+
"""Emit once when a memory.high write (engage or restore) hit an OSError --
|
|
310
|
+
fail-open: the brake disarms for the round and serve continues unthrottled,
|
|
311
|
+
exactly as when the brake is configured off. ``errno`` is the real OSError
|
|
312
|
+
code from the engage path; the restore path (which only sees a bool from
|
|
313
|
+
:func:`agent_runner.metrics.restore_leaf_memory_high`) passes ``None`` --
|
|
314
|
+
never ``0``, which conventionally reads as POSIX success."""
|
|
315
|
+
from agent_runner.events import MEMORY_HIGH_WRITE_FAILED, emit
|
|
316
|
+
|
|
317
|
+
emit(log_dir, MEMORY_HIGH_WRITE_FAILED, round_num=round_num, errno=errno)
|
|
318
|
+
|
|
319
|
+
|
|
249
320
|
def emit_cgroup_growth_rate_warning(
|
|
250
321
|
log_dir: Path,
|
|
251
322
|
*,
|
|
@@ -14,11 +14,12 @@ from dataclasses import dataclass, field
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
|
+
from agent_runner import event_log
|
|
18
|
+
from agent_runner._notify import NULL_LISTENER
|
|
17
19
|
from agent_runner.api_types import ProjectState, ServiceMode, ServiceStatus, SystemMetrics
|
|
18
20
|
from agent_runner.builtin_plugins._constants import _TAIL_LINES
|
|
19
|
-
from agent_runner.clock import SYSTEM_CLOCK
|
|
20
21
|
from agent_runner.context_store import read_json
|
|
21
|
-
from agent_runner.events import iter_event_dicts
|
|
22
|
+
from agent_runner.events import iter_event_dicts
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
@dataclass(frozen=True)
|
|
@@ -29,7 +30,7 @@ class LocalSource:
|
|
|
29
30
|
log_dir: Path
|
|
30
31
|
|
|
31
32
|
def events_files(self) -> list[Path]:
|
|
32
|
-
return
|
|
33
|
+
return event_log.all_month_files(self.log_dir)
|
|
33
34
|
|
|
34
35
|
def metrics_files(self) -> list[Path]:
|
|
35
36
|
return sorted(self.log_dir.glob("metrics-*.jsonl"))
|
|
@@ -75,7 +76,7 @@ class _EventTail:
|
|
|
75
76
|
)
|
|
76
77
|
|
|
77
78
|
def read(self, files: list[Path]) -> list[dict[str, Any]]:
|
|
78
|
-
new_events, self.offsets = read_new(files, self.offsets)
|
|
79
|
+
new_events, self.offsets = event_log.read_new(files, self.offsets)
|
|
79
80
|
self.buffer.extend(new_events)
|
|
80
81
|
return list(self.buffer)
|
|
81
82
|
|
|
@@ -93,23 +94,22 @@ def _tail_events_jsonl(
|
|
|
93
94
|
byte 0 of every file present at start (human-narrate use case).
|
|
94
95
|
|
|
95
96
|
Follows file rotation transparently — when a new events-YYYY-MM.jsonl
|
|
96
|
-
appears, it is picked up from byte 0.
|
|
97
|
+
appears, it is picked up from byte 0. A thin client over the shared
|
|
98
|
+
:func:`event_log.follow` loop (also used by ``events --tail``); polls
|
|
99
|
+
(``NULL_LISTENER`` -- no doorbell) rather than a live FIFO listener.
|
|
97
100
|
"""
|
|
98
|
-
|
|
101
|
+
seed: dict[Path, int] = {}
|
|
99
102
|
if start_at_now:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
yield from new_events
|
|
111
|
-
else:
|
|
112
|
-
SYSTEM_CLOCK.sleep(poll_interval_s)
|
|
103
|
+
seed = event_log.seed_at_eof(event_log.all_month_files(log_dir))
|
|
104
|
+
|
|
105
|
+
for _line, obj in event_log.follow(
|
|
106
|
+
log_dir,
|
|
107
|
+
event_log.all_month_files,
|
|
108
|
+
wake=NULL_LISTENER,
|
|
109
|
+
timeout_s=poll_interval_s,
|
|
110
|
+
offsets=seed,
|
|
111
|
+
):
|
|
112
|
+
yield obj
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
_MAX_TAIL_FILES = 20
|
|
@@ -18,6 +18,7 @@ from datetime import UTC, datetime
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Any
|
|
20
20
|
|
|
21
|
+
from agent_runner import event_log
|
|
21
22
|
from agent_runner._notify import NULL_LISTENER, Listener, NullListener
|
|
22
23
|
from agent_runner.api_types import TransientErrorState
|
|
23
24
|
from agent_runner.clock import SYSTEM_CLOCK, Clock
|
|
@@ -152,9 +153,10 @@ def _tail_events(log_dir: Path) -> Iterator[dict[str, Any]]:
|
|
|
152
153
|
|
|
153
154
|
Forward, OLD-then-NEW order matters (INVARIANT 2 for callers that merge a
|
|
154
155
|
per-key "latest wins" map: iterating oldest file first means a later event
|
|
155
|
-
for the same key naturally overwrites an earlier one).
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
for the same key naturally overwrites an earlier one). STATELESS full
|
|
157
|
+
re-scan by design (see :func:`event_log.scan`'s docstring) -- never the
|
|
158
|
+
offset-carrying ``follow``/``read_new`` path."""
|
|
159
|
+
yield from event_log.scan(log_dir, event_log.newest_scope(2))
|
|
158
160
|
|
|
159
161
|
|
|
160
162
|
def _scan_events_for_transient(path: Path):
|
|
@@ -185,8 +187,7 @@ def _latest_unrecovered_detected(log_dir: Path) -> dict[str, Any] | None:
|
|
|
185
187
|
Scans the newest monthly ``events-*.jsonl`` and, only if it holds no transient
|
|
186
188
|
event yet, the previous month's — so a throttle that spans a month boundary
|
|
187
189
|
(detected in the old file, cleared in the new) is not orphaned."""
|
|
188
|
-
|
|
189
|
-
for path in reversed(candidates[-2:]):
|
|
190
|
+
for path in reversed(event_log.newest_month_files(log_dir, 2)):
|
|
190
191
|
result = _scan_events_for_transient(path)
|
|
191
192
|
if result is not _NO_TRANSIENT:
|
|
192
193
|
return result # a detected dict, or None (latest transient was recovered)
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
21
|
+
__version__ = version = '0.3.8'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 8)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -41,6 +41,9 @@ from agent_runner._emit import ( # noqa: E402,F401 — intentional bottom re-ex
|
|
|
41
41
|
emit_mem_loop,
|
|
42
42
|
emit_mem_loop_persistent,
|
|
43
43
|
emit_mem_pressure_deferred_to_cgroup,
|
|
44
|
+
emit_memory_high_engaged,
|
|
45
|
+
emit_memory_high_released,
|
|
46
|
+
emit_memory_high_write_failed,
|
|
44
47
|
emit_plugin_builtin_name_squat,
|
|
45
48
|
emit_plugin_checksum_mismatch,
|
|
46
49
|
emit_plugin_sandbox_degraded,
|
|
@@ -22,6 +22,8 @@ from agent_runner import metrics
|
|
|
22
22
|
from agent_runner._serve_policy import _ROUND_UNREAPED_RC
|
|
23
23
|
from agent_runner.api import (
|
|
24
24
|
emit_host_cgroup_memory_limit,
|
|
25
|
+
emit_memory_high_released,
|
|
26
|
+
emit_memory_high_write_failed,
|
|
25
27
|
emit_round_cgroup_memory,
|
|
26
28
|
emit_round_oom_killed,
|
|
27
29
|
)
|
|
@@ -37,6 +39,27 @@ from agent_runner.api import (
|
|
|
37
39
|
# round.
|
|
38
40
|
_ROUND_CGROUP_STATE_BY_LOG_DIR: dict[Path, dict] = {}
|
|
39
41
|
|
|
42
|
+
# Boot-armed soft-brake gate: True iff the brake is configured on AND this
|
|
43
|
+
# process's own cgroup leaf is delegated (memory.high writable) -- probed ONCE
|
|
44
|
+
# at serve boot (_probe_and_emit_cgroup_defer already reads cgroup_delegated),
|
|
45
|
+
# read every mid-round tick by _serve_round via _brake_step_for. Same
|
|
46
|
+
# per-log_dir stable-default pattern as _ROUND_CGROUP_STATE_BY_LOG_DIR (one
|
|
47
|
+
# entry per serve process in production); cmd() cannot grow to thread it (145
|
|
48
|
+
# budget), so it rides in a module dict, not an argument.
|
|
49
|
+
_BRAKE_ARMED_BY_LOG_DIR: dict[Path, bool] = {}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _brake_step_for(log_dir: Path, host_health_cfg) -> int | None:
|
|
53
|
+
"""The armed soft-brake step_pct for this log_dir, or None when the brake is
|
|
54
|
+
disarmed: config off, or the boot probe found the leaf undelegated / no
|
|
55
|
+
cgroup v2. The step itself is single-sourced from config; the dict only
|
|
56
|
+
carries the boot-time delegated-and-on decision."""
|
|
57
|
+
if not host_health_cfg.brake.memory_high:
|
|
58
|
+
return None
|
|
59
|
+
if not _BRAKE_ARMED_BY_LOG_DIR.get(log_dir, False):
|
|
60
|
+
return None
|
|
61
|
+
return host_health_cfg.brake.memory_high_step_pct
|
|
62
|
+
|
|
40
63
|
|
|
41
64
|
def _stash_round_cgroup_state(
|
|
42
65
|
log_dir: Path, cg_base: dict, peak_current: int, peak_swap: int
|
|
@@ -190,18 +213,69 @@ def _maybe_emit_oom_killed(
|
|
|
190
213
|
# report ask #3). Advisory only -- never changes the operator's cgroup/unit.
|
|
191
214
|
_SWAP_CAP_ADVISORY_PCT = 25.0
|
|
192
215
|
|
|
193
|
-
# The delegation-readiness advisory
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
#
|
|
216
|
+
# The delegation-readiness / brake-inert advisory. Fires in two DISTINCT
|
|
217
|
+
# cases, both meaning "memory.high can't be written from here":
|
|
218
|
+
# (1) delegated is False (CONFIRMED not delegated -- systemd Delegate=yes
|
|
219
|
+
# is absent) AND memory.max/memory.high are already bound (memory_high
|
|
220
|
+
# set, or the own_scope "add memory.high" hint above just fired) --
|
|
221
|
+
# the existing config is informational-only without delegation.
|
|
222
|
+
# (2) the operator explicitly turned the soft-brake on
|
|
223
|
+
# ([monitor.host_health.brake] memory_high=true, threaded in here as
|
|
224
|
+
# brake_memory_high) but delegated is not True (False OR None) -- the
|
|
225
|
+
# brake will silently do nothing, worth announcing at boot even with
|
|
226
|
+
# no memory.max/memory.high configured yet.
|
|
227
|
+
# Deliberately NOT ``delegated is not True`` across the board: cgroup_delegated
|
|
228
|
+
# returns None for two different reasons -- cgroup v2 unavailable (harmless)
|
|
229
|
+
# AND the leaf's own os.stat() raising OSError (delegation status genuinely
|
|
230
|
+
# UNKNOWN, even though memory_high/own_scope's ancestor-walk resolved fine).
|
|
231
|
+
# Broadening case (1) to None would assert "not writable" where we actually
|
|
232
|
+
# don't know -- an overclaim this release can't afford. Case (2) is the one
|
|
233
|
+
# exception: the operator asked for the brake, so ANY non-confirmed-delegated
|
|
234
|
+
# state (False or unknown) is worth flagging as "may be inert" up front.
|
|
235
|
+
# Advisory only; this probe and its hint never write to the cgroup or the unit.
|
|
198
236
|
_UNDELEGATED_HINT = (
|
|
199
|
-
"this cgroup is not
|
|
200
|
-
"
|
|
237
|
+
"memory.high on this cgroup is not writable by this process; the soft-brake "
|
|
238
|
+
"is inert. Run serve as a user-mode unit (`systemctl --user` + `loginctl "
|
|
239
|
+
"enable-linger`) or as a root system unit."
|
|
201
240
|
)
|
|
202
241
|
|
|
203
242
|
|
|
204
|
-
def
|
|
243
|
+
def brake_report_state(enabled: bool, delegated: bool | None) -> str:
|
|
244
|
+
"""The operator-facing soft-brake state for doctor/peek: ``off`` (not
|
|
245
|
+
configured), ``armed`` (on AND the leaf is delegated so memory.high is
|
|
246
|
+
writable), or ``inert(<reason>)`` (on but the write path can't arm)."""
|
|
247
|
+
if not enabled:
|
|
248
|
+
return "off"
|
|
249
|
+
if delegated is True:
|
|
250
|
+
return "armed"
|
|
251
|
+
return "inert(undelegated)" if delegated is False else "inert(no cgroup v2)"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def current_brake_state(cfg) -> str:
|
|
255
|
+
"""Resolve the soft-brake state string for `cfg` from live cgroup facts --
|
|
256
|
+
the one-shot ``cgroup_memory_limits() -> cgroup_delegated(self_cgroup=...) ->
|
|
257
|
+
brake_report_state`` sequence, shared by peek (cli.common.emit) and doctor
|
|
258
|
+
so neither re-derives it inline."""
|
|
259
|
+
limits = metrics.cgroup_memory_limits()
|
|
260
|
+
delegated = metrics.cgroup_delegated(self_cgroup=limits["cgroup_path"])
|
|
261
|
+
return brake_report_state(cfg.monitor.host_health.brake.memory_high, delegated)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _release_brake(log_dir: Path, round_num: int, previous: str, *, reason: str) -> bool:
|
|
265
|
+
"""Restore memory.high on serve's own leaf to `previous` and emit the
|
|
266
|
+
matching event: ``memory_high_released`` on success, ``memory_high_write_failed``
|
|
267
|
+
(errno None -- restore only yields a bool) on OSError. Returns whether the
|
|
268
|
+
restore succeeded; callers own their own brake_engaged/brake_previous/
|
|
269
|
+
brake_restore_failed transitions off that result. Shared by _spawn_round's
|
|
270
|
+
mid-round recovery-release site and its unconditional round-end finally."""
|
|
271
|
+
if metrics.restore_leaf_memory_high(previous):
|
|
272
|
+
emit_memory_high_released(log_dir, round_num=round_num, reason=reason)
|
|
273
|
+
return True
|
|
274
|
+
emit_memory_high_write_failed(log_dir, round_num=round_num, errno=None)
|
|
275
|
+
return False
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _probe_and_emit_cgroup_defer(log_dir: Path, *, brake_memory_high: bool = False) -> bool:
|
|
205
279
|
"""Probe this process's cgroup v2 memory budget once at serve startup,
|
|
206
280
|
emit host_cgroup_memory_limit for observability, and return whether the
|
|
207
281
|
mid-round hard floor should defer to kernel cgroup-OOM: True only when
|
|
@@ -249,8 +323,22 @@ def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
|
|
|
249
323
|
the bounding ancestor's ``memory.high`` soft-throttle threshold
|
|
250
324
|
(``metrics.cgroup_memory_high``), ``None`` when unset -- for the same
|
|
251
325
|
reason: an operator asking "is MemoryHigh even set" is exactly this
|
|
252
|
-
release's field ask.
|
|
253
|
-
|
|
326
|
+
release's field ask.
|
|
327
|
+
|
|
328
|
+
A THIRD advisory, ``_UNDELEGATED_HINT``, reads ``brake_memory_high`` --
|
|
329
|
+
the caller-supplied ``[monitor.host_health.brake] memory_high`` config
|
|
330
|
+
switch -- alongside ``delegated``: when the operator turned that brake
|
|
331
|
+
on but this process's own cgroup leaf isn't confirmed delegated
|
|
332
|
+
(``delegated is not True``), the brake would silently write nothing, so
|
|
333
|
+
boot announces it up front even with no memory.max/memory.high
|
|
334
|
+
configured. Independently of the brake, the SAME hint also covers the
|
|
335
|
+
pre-existing "config is informational without delegation" case
|
|
336
|
+
(``delegated is False`` -- CONFIRMED not delegated, never the merely
|
|
337
|
+
unresolved ``None``) when memory_high is already set or the own_scope
|
|
338
|
+
hint above just fired. See the comment block above ``_UNDELEGATED_HINT``
|
|
339
|
+
for why those two triggers use different ``delegated`` strictness.
|
|
340
|
+
Advisory only -- this never changes the operator's cgroup or systemd
|
|
341
|
+
unit."""
|
|
254
342
|
limits = metrics.cgroup_memory_limits()
|
|
255
343
|
swap_total = metrics.swap_total_bytes()
|
|
256
344
|
# Reuse the leaf cgroup_memory_limits already resolved above -- skips
|
|
@@ -263,6 +351,8 @@ def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
|
|
|
263
351
|
# way as calling with no override would.
|
|
264
352
|
memory_high = metrics.cgroup_memory_high(self_cgroup=limits["cgroup_path"])
|
|
265
353
|
delegated = metrics.cgroup_delegated(self_cgroup=limits["cgroup_path"])
|
|
354
|
+
if brake_memory_high:
|
|
355
|
+
_BRAKE_ARMED_BY_LOG_DIR[log_dir] = delegated is True
|
|
266
356
|
swap_max = limits["memory_swap_max"]
|
|
267
357
|
swap_cap_pct = (
|
|
268
358
|
round(100.0 * swap_max / swap_total, 1) if swap_max is not None and swap_total > 0 else None
|
|
@@ -299,7 +389,9 @@ def _probe_and_emit_cgroup_defer(log_dir: Path) -> bool:
|
|
|
299
389
|
"throttle's PSI-full rise doesn't trip the mid-round floor"
|
|
300
390
|
)
|
|
301
391
|
advisories.append(hint)
|
|
302
|
-
if
|
|
392
|
+
if (brake_memory_high and delegated is not True) or (
|
|
393
|
+
delegated is False and (memory_high is not None or own_scope)
|
|
394
|
+
):
|
|
303
395
|
advisories.append(_UNDELEGATED_HINT)
|
|
304
396
|
advisory = "; ".join(advisories) or None
|
|
305
397
|
if advisory is not None:
|