cli-agent-runner 0.3.5__tar.gz → 0.3.7__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.5 → cli_agent_runner-0.3.7}/.vulture-whitelist.py +7 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/CHANGELOG.md +13 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/PKG-INFO +1 -1
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_emit/__init__.py +6 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_emit/memory.py +64 -1
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_lifecycle.py +16 -7
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_notify.py +15 -6
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_plugin_sandbox.py +93 -13
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_procwait.py +38 -15
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/agent_runtime.py +11 -5
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/api.py +3 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/_serve_cgroup.py +103 -11
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/_serve_round.py +149 -17
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/common.py +5 -1
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/doctor_cmd.py +21 -4
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/serve_cmd.py +22 -7
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/config/__init__.py +1 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/config/models.py +36 -9
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/config/parsers.py +41 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/events.py +3 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/metrics.py +125 -6
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/architecture.md +3 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/commands.md +5 -1
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/configuration.md +4 -2
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/memory-efficiency.md +61 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/plugins.md +2 -2
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/runbook.md +4 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/contract/test_public_api_surface.py +3 -0
- cli_agent_runner-0.3.7/tests/integration/test_reap_property.py +314 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_spawn_round_mem_floor.py +521 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_architecture.py +17 -3
- cli_agent_runner-0.3.7/tests/invariants/test_brake_defaults_off.py +46 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_event_kinds_ssot.py +5 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_layer_2_loop_size.py +1 -1
- cli_agent_runner-0.3.7/tests/invariants/test_no_direct_inspect_import.py +47 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_peek_schema_version.py +3 -1
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_timeout_budget_invariant.py +14 -3
- cli_agent_runner-0.3.7/tests/linux/test_memory_high_nudge_e2e.py +180 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_reap.py +27 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_cgroup_probe.py +279 -3
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_config.py +20 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_config_nested_strictness.py +61 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_doctor_cmd.py +84 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_exec_prefix.py +3 -3
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_notify.py +16 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_sandbox_wire.py +252 -1
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_procwait.py +14 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_round_outcome.py +45 -0
- cli_agent_runner-0.3.7/tests/unit/test_serve_round.py +81 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_spawn_seam.py +61 -1
- cli_agent_runner-0.3.5/tests/integration/test_reap_property.py +0 -153
- cli_agent_runner-0.3.5/tests/unit/test_serve_round.py +0 -34
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/.codecov.yml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/.github/dependabot.yml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/.gitignore +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/BACKLOG.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/LICENSE +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/README.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/README.zh.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/SECURITY.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_emit/agent.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_emit/plugins.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_emit/rounds.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_emit/serve.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_install.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_monitor_state.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_observe.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_plugin_checksum.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_plugin_scan.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_round_outcome.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_sandbox_probe.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_serve_policy.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/config/loader.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/config/validators.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/host_health.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/phase_select.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/runner.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/build.sh +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/deploy/Dockerfile +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/deploy/docker-compose.yml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/README.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/events.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/migrations/0.2.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/migrations/0.3.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/recipes/container-pi.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/recipes/deploy-all-in-one.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/docs/thesis.md +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/pyproject.toml +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/__init__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/_clock.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/conftest.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_auto_stop_e2e.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_cooperative_grace_property.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_spawn_round_wedged.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_bdd_test_conventions.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_builtin_plugin_names_sync.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_container_stop_bound.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_import_footprint.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_kill_tests_not_skipped.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_no_asyncio_select.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_no_price_logic.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_no_vacuous_invariants.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_no_version_refs.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_procwait_shield.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_round_alloc_growth.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/linux/__init__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/linux/test_plugin_sandbox_kill.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_container.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_apply_reap_grace_env.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_builtin_provenance.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_collapse_spawn_decisions.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_dispatch_dirty_owner_branch.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_doctor_sandbox.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_events.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_example_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_gate_serve_boot.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_git_timeout.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_host_health.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_http_progress_epoch.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_kill_pgroup_shielding.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_load_fail_closed.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_loader_split.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_manifest_spawn_hooks.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_metrics_cgroup_usage.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_metrics_swap.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_dedup_rearm.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_loop.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_on_alert.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_peek_sandbox_block.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_phase_select.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_checksum.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_name_mismatch.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_plugin_scan_parity.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_plugins_config_fields.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_primary_prompt_file_home.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_round_outcome_agent_axis.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_sandbox_probe.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_policy.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_oom_killed.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_terminate.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_round_wait_exit.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_serve_wakeup.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_terminate_round_wait_exit.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_throttle_epoch_bound.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.3.5 → cli_agent_runner-0.3.7}/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
|
|
@@ -5,6 +5,19 @@ 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.7] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- 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.
|
|
12
|
+
- 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.
|
|
13
|
+
- 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>)`.
|
|
14
|
+
|
|
15
|
+
## [0.3.6] - 2026-09-15
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- `agent-runner kill` now reaps a stuck round's detached descendants (a child that `setsid()`'d off its process group) the same way `serve stop` already does, so a forced stop never leaves an orphan behind.
|
|
19
|
+
- A stop (`SIGTERM` / `serve stop`) during a sandboxed third-party spawn hook, or during a plugin-requested `defer`, now takes effect immediately instead of after up to 30 s — and the confined hook's child process is always reaped, never left running. A background process the hook itself forks is not reaped; its abandoned output pipe is dropped after a bounded 2 s drain wait (on a normal hook exit, surfaced as `hook_failed`) rather than stalling the stop.
|
|
20
|
+
|
|
8
21
|
## [0.3.5] - 2026-09-15
|
|
9
22
|
|
|
10
23
|
### 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.7
|
|
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
|
|
@@ -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
|
|
|
@@ -246,6 +256,59 @@ def emit_mem_pressure_deferred_to_cgroup(
|
|
|
246
256
|
emit(log_dir, MEM_PRESSURE_DEFERRED_TO_CGROUP, pid=pid, signal=signal, message=message)
|
|
247
257
|
|
|
248
258
|
|
|
259
|
+
def emit_memory_high_engaged(
|
|
260
|
+
log_dir: Path,
|
|
261
|
+
*,
|
|
262
|
+
round_num: int,
|
|
263
|
+
previous: str,
|
|
264
|
+
written: int,
|
|
265
|
+
memory_current: int,
|
|
266
|
+
write_ms: int,
|
|
267
|
+
signal: str,
|
|
268
|
+
context: dict,
|
|
269
|
+
rate_mb_per_min: float | None,
|
|
270
|
+
) -> None:
|
|
271
|
+
"""Emit when the reversible cgroup memory.high soft-brake engaged on serve's
|
|
272
|
+
own leaf under sustained warning pressure. ``previous``/``written`` bracket
|
|
273
|
+
the reversible write; ``signal``/``context`` are the triggering Pressure;
|
|
274
|
+
``rate_mb_per_min`` rides along as growth-rate context for Pi calibration."""
|
|
275
|
+
from agent_runner.events import MEMORY_HIGH_ENGAGED, emit
|
|
276
|
+
|
|
277
|
+
emit(
|
|
278
|
+
log_dir,
|
|
279
|
+
MEMORY_HIGH_ENGAGED,
|
|
280
|
+
round_num=round_num,
|
|
281
|
+
previous=previous,
|
|
282
|
+
written=written,
|
|
283
|
+
memory_current=memory_current,
|
|
284
|
+
write_ms=write_ms,
|
|
285
|
+
signal=signal,
|
|
286
|
+
context=context,
|
|
287
|
+
rate_mb_per_min=rate_mb_per_min,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def emit_memory_high_released(log_dir: Path, *, round_num: int, reason: str) -> None:
|
|
292
|
+
"""Emit when the soft-brake was restored to its stashed value. ``reason`` is
|
|
293
|
+
``recovered`` (N healthy ticks) or ``round_end`` (the finally restore, which
|
|
294
|
+
covers every exit path out of the round incl. wedged/exception)."""
|
|
295
|
+
from agent_runner.events import MEMORY_HIGH_RELEASED, emit
|
|
296
|
+
|
|
297
|
+
emit(log_dir, MEMORY_HIGH_RELEASED, round_num=round_num, reason=reason)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def emit_memory_high_write_failed(log_dir: Path, *, round_num: int, errno: int | None) -> None:
|
|
301
|
+
"""Emit once when a memory.high write (engage or restore) hit an OSError --
|
|
302
|
+
fail-open: the brake disarms for the round and serve continues unthrottled,
|
|
303
|
+
exactly as when the brake is configured off. ``errno`` is the real OSError
|
|
304
|
+
code from the engage path; the restore path (which only sees a bool from
|
|
305
|
+
:func:`agent_runner.metrics.restore_leaf_memory_high`) passes ``None`` --
|
|
306
|
+
never ``0``, which conventionally reads as POSIX success."""
|
|
307
|
+
from agent_runner.events import MEMORY_HIGH_WRITE_FAILED, emit
|
|
308
|
+
|
|
309
|
+
emit(log_dir, MEMORY_HIGH_WRITE_FAILED, round_num=round_num, errno=errno)
|
|
310
|
+
|
|
311
|
+
|
|
249
312
|
def emit_cgroup_growth_rate_warning(
|
|
250
313
|
log_dir: Path,
|
|
251
314
|
*,
|
|
@@ -181,17 +181,26 @@ def _terminate_round_pid(pid: int) -> None:
|
|
|
181
181
|
"""TERM-first -> grace -> SIGKILL a bare round-child pid.
|
|
182
182
|
|
|
183
183
|
Mirrors ``serve_cmd._terminate_round``'s shape, but this runs from a
|
|
184
|
-
SEPARATE CLI process (``kill()``) that only has the pid
|
|
184
|
+
SEPARATE CLI process (``kill()``) that only has the pid -- not serve's own
|
|
185
185
|
``Popen`` handle. Never ``killpg``: the round is ``start_new_session=True``,
|
|
186
|
-
so
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
186
|
+
so a plain SIGTERM here fires the round's own SIGTERM -> KeyboardInterrupt
|
|
187
|
+
handler (``round_cmd.py``), which walks the link to the agent pgroup and
|
|
188
|
+
reaps it via ``_kill_pgroup`` (itself snapshotting and reaping any stray).
|
|
189
|
+
SIGKILL is only the last-resort escalation for a round that never even runs
|
|
190
|
+
its handler -- and on THAT path the leader's own cooperative reap never
|
|
191
|
+
happened, so we snapshot the leader's descendants up front (while the leader
|
|
192
|
+
subtree is still resolvable) via a bare-pid shim and reap the strays here:
|
|
193
|
+
a descendant that ``setsid()``'d off the leader's pgroup would otherwise
|
|
194
|
+
survive the leader's death, orphaned. Symmetric with ``serve stop``."""
|
|
195
|
+
import types
|
|
196
|
+
|
|
197
|
+
from agent_runner import agent_runtime
|
|
198
|
+
|
|
199
|
+
stray = agent_runtime._snapshot_stray_descendants(types.SimpleNamespace(pid=pid))
|
|
192
200
|
send_signal_to_pid(pid, signal.SIGTERM)
|
|
193
201
|
if _await_pid_exit(pid, _ROUND_TERM_GRACE_S):
|
|
194
202
|
send_signal_to_pid(pid, signal.SIGKILL)
|
|
203
|
+
agent_runtime._kill_stray_descendants(stray)
|
|
195
204
|
|
|
196
205
|
|
|
197
206
|
def stop(project: str | Path) -> ServiceStatus:
|
|
@@ -78,11 +78,14 @@ def drain(fd: int) -> None:
|
|
|
78
78
|
``select()`` on it instantly ready -- an undrained fd busy-spins its
|
|
79
79
|
caller at 100% CPU forever after the first wake, since a level-triggered
|
|
80
80
|
fd stays readable until read. Advisory: the bytes carry no payload and are
|
|
81
|
-
discarded.
|
|
82
|
-
the pause/sleep waits
|
|
83
|
-
|
|
84
|
-
(``
|
|
85
|
-
|
|
81
|
+
discarded. Two callers: :meth:`Listener.wait` self-drains its own fd on the
|
|
82
|
+
wake branch (the pause/sleep waits ``_interruptible_sleep``/``_pause_poll``
|
|
83
|
+
are the consumers of a live ``Listener``); and the sandboxed-hook trampoline
|
|
84
|
+
(``_plugin_sandbox._run_child_process``, P3) drains the CALLER-OWNED wake fd
|
|
85
|
+
it was handed on the ``"woken"`` branch of its exit wait, before re-checking
|
|
86
|
+
``should_stop()`` -- that fd is the serve listener's, watched by
|
|
87
|
+
``wait_exit`` for the duration of the hook, so the trampoline must drain it
|
|
88
|
+
itself rather than leave the next wait instantly ready.
|
|
86
89
|
"""
|
|
87
90
|
while True:
|
|
88
91
|
try:
|
|
@@ -179,7 +182,13 @@ class Listener:
|
|
|
179
182
|
"""
|
|
180
183
|
deadline = clock.monotonic() + timeout_s
|
|
181
184
|
remaining = max(0.0, deadline - clock.monotonic())
|
|
182
|
-
|
|
185
|
+
try:
|
|
186
|
+
ready, _, _ = select.select([self.fd], [], [], remaining)
|
|
187
|
+
except ValueError:
|
|
188
|
+
# fd >= FD_SETSIZE (1024) makes select.select raise ValueError --
|
|
189
|
+
# degrade to a plain timed sleep instead of crashing.
|
|
190
|
+
clock.sleep(remaining)
|
|
191
|
+
return False
|
|
183
192
|
if not ready:
|
|
184
193
|
return False
|
|
185
194
|
|
|
@@ -17,12 +17,15 @@ from __future__ import annotations
|
|
|
17
17
|
import json
|
|
18
18
|
import subprocess
|
|
19
19
|
import sys
|
|
20
|
+
from collections.abc import Callable
|
|
20
21
|
from pathlib import Path
|
|
21
22
|
from typing import Literal
|
|
22
23
|
|
|
23
|
-
from agent_runner import hooks
|
|
24
|
+
from agent_runner import _notify, hooks
|
|
25
|
+
from agent_runner._procwait import wait_exit
|
|
24
26
|
from agent_runner._registry import resolve_entry_target
|
|
25
27
|
from agent_runner.api_types import DirtyOutcome, SpawnDecision
|
|
28
|
+
from agent_runner.clock import SYSTEM_CLOCK
|
|
26
29
|
|
|
27
30
|
_TRAMPOLINE_TIMEOUT_S = 30.0 # wall-clock; not config-tunable this release
|
|
28
31
|
_MAX_WIRE_BYTES = 64 * 1024
|
|
@@ -30,6 +33,17 @@ _MAX_STDERR_BYTES = 8 * 1024 # hard read cap; a runaway child can't OOM the sup
|
|
|
30
33
|
_REF_CAP = 256
|
|
31
34
|
_STDERR_CAP = 512
|
|
32
35
|
|
|
36
|
+
# Total wall budget for draining a confined child's pipes AFTER it is reaped. A
|
|
37
|
+
# process the hook itself fork()'d inherits the child's stdout/stderr and can hold
|
|
38
|
+
# them open past the child's own death, so the drain join is BOUNDED, not
|
|
39
|
+
# unbounded -- otherwise a single such pipe-holder stalls the stop forever. The
|
|
40
|
+
# pair of joins shares ONE absolute deadline (not _DRAIN_JOIN_S each), and
|
|
41
|
+
# _DRAIN_JOIN_S + 3 <= _lifecycle._PID_SIGNAL_GRACE_S leaves >=3 s of the
|
|
42
|
+
# stop-confirm window for the rest of the unwind (pinned by
|
|
43
|
+
# tests/invariants/test_timeout_budget_invariant.py; a literal mirror -- this
|
|
44
|
+
# module must never import _lifecycle in production).
|
|
45
|
+
_DRAIN_JOIN_S = 2
|
|
46
|
+
|
|
33
47
|
# The child's environment is built default-DENY from this allowlist -- NOT copied
|
|
34
48
|
# from the parent's, which holds the real agent secrets (ANTHROPIC_*, and every
|
|
35
49
|
# *_API_KEY/*_TOKEN/*_SECRET agent_runtime injects). A confined plugin is Python
|
|
@@ -126,6 +140,13 @@ def _ctx_from_wire(data: dict) -> hooks.HookContext:
|
|
|
126
140
|
# ---------------------------------------------------------------------------
|
|
127
141
|
|
|
128
142
|
|
|
143
|
+
class SpawnHookInterrupted(Exception): # noqa: N818 -- a control-flow signal, not an error
|
|
144
|
+
"""Raised by ``_run_child_process`` when ``should_stop()`` fires on a wake
|
|
145
|
+
during a sandboxed spawn hook -- distinct from a plugin failure so the seam
|
|
146
|
+
returns ``None`` WITHOUT emitting ``hook_failed`` (a stop is not the plugin's
|
|
147
|
+
fault). Never raised on the dirty round path, which passes no ``wake_fd``."""
|
|
148
|
+
|
|
149
|
+
|
|
129
150
|
def run_hook_sandboxed(
|
|
130
151
|
hook_kind: Literal["spawn_hook", "dirty_handler"],
|
|
131
152
|
module_path: str,
|
|
@@ -137,6 +158,8 @@ def run_hook_sandboxed(
|
|
|
137
158
|
dirty_files: list[str] | None = None,
|
|
138
159
|
view: hooks.SpawnView | None = None,
|
|
139
160
|
timeout_s: float = _TRAMPOLINE_TIMEOUT_S,
|
|
161
|
+
wake_fd: int | None = None,
|
|
162
|
+
should_stop: Callable[[], bool] | None = None,
|
|
140
163
|
) -> SpawnDecision | DirtyOutcome | None:
|
|
141
164
|
"""Launch the confinement child for one third-party hook call and return its
|
|
142
165
|
validated outcome. ``module_path``/``attr_path`` locate the plugin the child
|
|
@@ -172,6 +195,8 @@ def run_hook_sandboxed(
|
|
|
172
195
|
],
|
|
173
196
|
json.dumps(payload).encode("utf-8"),
|
|
174
197
|
timeout_s,
|
|
198
|
+
wake_fd=wake_fd,
|
|
199
|
+
should_stop=should_stop,
|
|
175
200
|
)
|
|
176
201
|
if returncode < 0:
|
|
177
202
|
from agent_runner.api import emit_plugin_sandbox_kill
|
|
@@ -224,14 +249,50 @@ def _drain_capped(stream, cap: int, out: list[bytes]) -> None:
|
|
|
224
249
|
out.append(bytes(data))
|
|
225
250
|
|
|
226
251
|
|
|
252
|
+
def _join_drain_threads(threads: tuple, budget_s: float) -> bool:
|
|
253
|
+
"""Join the drain threads under ONE absolute deadline -- the whole set is
|
|
254
|
+
bounded by ``budget_s``, NOT ``budget_s`` each -- so a process the hook
|
|
255
|
+
``fork()``'d that still holds the reaped child's output pipe open cannot stall
|
|
256
|
+
the join past ``budget_s``. Returns True iff every thread finished draining
|
|
257
|
+
within the budget (the normal-exit path raises on False; the ``BaseException``
|
|
258
|
+
cleanup path ignores it, since it is already unwinding)."""
|
|
259
|
+
join_deadline = SYSTEM_CLOCK.monotonic() + budget_s
|
|
260
|
+
for t in threads:
|
|
261
|
+
t.join(max(0.0, join_deadline - SYSTEM_CLOCK.monotonic()))
|
|
262
|
+
return not any(t.is_alive() for t in threads)
|
|
263
|
+
|
|
264
|
+
|
|
227
265
|
def _run_child_process(
|
|
228
|
-
argv: list[str],
|
|
266
|
+
argv: list[str],
|
|
267
|
+
stdin_bytes: bytes,
|
|
268
|
+
timeout_s: float,
|
|
269
|
+
*,
|
|
270
|
+
wake_fd: int | None = None,
|
|
271
|
+
should_stop: Callable[[], bool] | None = None,
|
|
229
272
|
) -> tuple[int, bytes, bytes]:
|
|
230
273
|
"""Spawn the confined child with the minimal env and read BOUNDED stdout/stderr
|
|
231
274
|
concurrently (a thread per stream), so a plugin printing without limit within
|
|
232
275
|
the wall-timeout cannot exhaust supervisor memory. Concurrent draining also
|
|
233
|
-
avoids a full-pipe deadlock.
|
|
234
|
-
|
|
276
|
+
avoids a full-pipe deadlock.
|
|
277
|
+
|
|
278
|
+
The exit wait is the fd-driven ``_procwait.wait_exit`` (not a sleep-poll):
|
|
279
|
+
``"exited"`` reaps and returns; ``"timeout"`` kills and raises
|
|
280
|
+
``TimeoutExpired`` (the caller isolates it as ``hook_failed``); ``"woken"``
|
|
281
|
+
(only when ``wake_fd`` is set) drains the advisory byte and, if
|
|
282
|
+
``should_stop()`` is now true, kills the child and raises
|
|
283
|
+
``SpawnHookInterrupted`` -- otherwise a stray ring re-enters the wait on the
|
|
284
|
+
SAME ABSOLUTE deadline so it never shortens the hook's budget. The whole wait
|
|
285
|
+
is wrapped so the confined child NEVER outlives its parent: any BaseException
|
|
286
|
+
(a round ``KeyboardInterrupt``, a timeout, an interrupt) kills and reaps the
|
|
287
|
+
child before propagating.
|
|
288
|
+
|
|
289
|
+
A process the hook itself ``fork()``s is NOT reaped (that child has no process
|
|
290
|
+
group of its own -- see the Option-B follow-up), and it can inherit and hold
|
|
291
|
+
the child's stdout/stderr open past the child's own death; so the drain joins
|
|
292
|
+
are BOUNDED (``_DRAIN_JOIN_S``, one absolute deadline across the pair) at BOTH
|
|
293
|
+
the normal-exit tail and the ``BaseException`` cleanup -- such a pipe-holder can
|
|
294
|
+
never stall the parent. On the normal path a timed-out drain surfaces as a
|
|
295
|
+
``RuntimeError`` the seam isolates as ``hook_failed``."""
|
|
235
296
|
import threading
|
|
236
297
|
|
|
237
298
|
proc = subprocess.Popen(
|
|
@@ -243,9 +304,17 @@ def _run_child_process(
|
|
|
243
304
|
)
|
|
244
305
|
out_box: list[bytes] = []
|
|
245
306
|
err_box: list[bytes] = []
|
|
246
|
-
#
|
|
247
|
-
|
|
248
|
-
|
|
307
|
+
# daemon=True is load-bearing, not cosmetic: nothing in agent_runner/ calls
|
|
308
|
+
# os._exit, so a non-daemon drain thread still blocked on a fork()'d
|
|
309
|
+
# pipe-holder's read() would be joined by threading._shutdown() at interpreter
|
|
310
|
+
# exit and re-hang the stop the bounded join below just rescued. Matches the
|
|
311
|
+
# repo's other pump threads (remote_relay._pump, agent_runtime._write_stdin).
|
|
312
|
+
t_out = threading.Thread(
|
|
313
|
+
target=_drain_capped, args=(proc.stdout, _MAX_WIRE_BYTES + 1, out_box), daemon=True
|
|
314
|
+
)
|
|
315
|
+
t_err = threading.Thread(
|
|
316
|
+
target=_drain_capped, args=(proc.stderr, _MAX_STDERR_BYTES, err_box), daemon=True
|
|
317
|
+
)
|
|
249
318
|
t_out.start()
|
|
250
319
|
t_err.start()
|
|
251
320
|
try:
|
|
@@ -255,15 +324,26 @@ def _run_child_process(
|
|
|
255
324
|
pass
|
|
256
325
|
finally:
|
|
257
326
|
proc.stdin.close()
|
|
258
|
-
|
|
259
|
-
|
|
327
|
+
deadline = SYSTEM_CLOCK.monotonic() + timeout_s
|
|
328
|
+
while True:
|
|
329
|
+
outcome = wait_exit(proc, deadline=deadline, wake_fd=wake_fd)
|
|
330
|
+
if outcome == "exited":
|
|
331
|
+
proc.wait()
|
|
332
|
+
break
|
|
333
|
+
if outcome == "timeout":
|
|
334
|
+
raise subprocess.TimeoutExpired(proc.args, timeout_s)
|
|
335
|
+
_notify.drain(wake_fd)
|
|
336
|
+
if should_stop is not None and should_stop():
|
|
337
|
+
raise SpawnHookInterrupted(f"spawn hook interrupted for {argv[0]!r}")
|
|
338
|
+
except BaseException:
|
|
260
339
|
proc.kill()
|
|
261
340
|
proc.wait()
|
|
262
|
-
t_out
|
|
263
|
-
t_err.join()
|
|
341
|
+
_join_drain_threads((t_out, t_err), _DRAIN_JOIN_S)
|
|
264
342
|
raise
|
|
265
|
-
t_out
|
|
266
|
-
|
|
343
|
+
if not _join_drain_threads((t_out, t_err), _DRAIN_JOIN_S):
|
|
344
|
+
raise RuntimeError(
|
|
345
|
+
"drain timed out: a process the hook forked still holds its output pipe (not reaped)"
|
|
346
|
+
)
|
|
267
347
|
return proc.returncode, out_box[0], err_box[0]
|
|
268
348
|
|
|
269
349
|
|
|
@@ -96,12 +96,13 @@ def wait_exit(
|
|
|
96
96
|
*,
|
|
97
97
|
deadline: float,
|
|
98
98
|
clock: Clock = SYSTEM_CLOCK,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
``
|
|
103
|
-
|
|
104
|
-
|
|
99
|
+
wake_fd: int | None = None,
|
|
100
|
+
) -> Literal["exited", "timeout", "woken"]:
|
|
101
|
+
"""Block until ``proc`` exits, ``deadline`` (a ``clock.monotonic()``
|
|
102
|
+
timestamp) is reached, or ``wake_fd`` becomes readable -- whichever is
|
|
103
|
+
soonest. Never calls ``os.waitpid``/``proc.wait``/``proc.poll`` on the
|
|
104
|
+
fast path -- a caller that gets back ``"exited"`` must reap ``proc``
|
|
105
|
+
itself right after this returns (see the module docstring).
|
|
105
106
|
|
|
106
107
|
Opens and closes its own ``exit_fd(proc)`` registration internally, once
|
|
107
108
|
per call, in a single ``select.select`` -- callers invoke this at most
|
|
@@ -110,16 +111,27 @@ def wait_exit(
|
|
|
110
111
|
across calls.
|
|
111
112
|
|
|
112
113
|
Returns ``"exited"`` (the caller should immediately reap, e.g.
|
|
113
|
-
``proc.wait()``)
|
|
114
|
-
caller re-evaluates its own logic)
|
|
115
|
-
|
|
116
|
-
``
|
|
117
|
-
|
|
114
|
+
``proc.wait()``), ``"timeout"`` (deadline reached, proc still running,
|
|
115
|
+
caller re-evaluates its own logic), or ``"woken"`` (only possible when
|
|
116
|
+
``wake_fd`` is given: it became readable before either of the other two).
|
|
117
|
+
When ``proc`` has already exited by the time ``deadline`` is reached, the
|
|
118
|
+
exit fd is ready in the SAME ``select`` call that also times out -- the
|
|
119
|
+
exit always wins that tie, so an already-dead proc is never mistakenly
|
|
120
|
+
reported as ``"timeout"``. Likewise, when both the exit fd and
|
|
121
|
+
``wake_fd`` are ready in the same ``select`` call, the exit fd wins --
|
|
122
|
+
an already-dead proc is never mistakenly reported as ``"woken"``.
|
|
123
|
+
``wake_fd`` is caller-owned: this function neither opens, closes, nor
|
|
124
|
+
drains it (the caller drains it after a ``"woken"`` return); ``_procwait``
|
|
125
|
+
never imports ``_notify``.
|
|
118
126
|
|
|
119
127
|
Fallback (``exit_fd(proc)`` is ``None``): polls ``proc.poll()`` at a
|
|
120
128
|
short, fixed cadence via ``clock.sleep``, with the same two return
|
|
121
|
-
meanings
|
|
122
|
-
|
|
129
|
+
meanings (``wake_fd`` is IGNORED on this path -- a documented latency
|
|
130
|
+
degrade, not a "woken" report), so a test driving this path with a fake
|
|
131
|
+
clock still makes progress without blocking on real wall time. The same
|
|
132
|
+
poll fallback is also used when the fast path's ``select.select`` raises
|
|
133
|
+
``ValueError`` (a fd >= ``FD_SETSIZE``, 1024, is unselectable) instead of
|
|
134
|
+
crashing.
|
|
123
135
|
|
|
124
136
|
Short-circuits to ``"exited"`` when ``proc.returncode`` is already set:
|
|
125
137
|
this owner has already reaped it, so its pid is freed (and may be reused).
|
|
@@ -139,8 +151,19 @@ def wait_exit(
|
|
|
139
151
|
|
|
140
152
|
try:
|
|
141
153
|
remaining = max(0.0, deadline - clock.monotonic())
|
|
142
|
-
|
|
143
|
-
|
|
154
|
+
rlist = [fd, wake_fd] if wake_fd is not None else [fd]
|
|
155
|
+
try:
|
|
156
|
+
ready, _, _ = select.select(rlist, [], [], remaining)
|
|
157
|
+
except ValueError:
|
|
158
|
+
# fd >= FD_SETSIZE (1024) makes select.select raise ValueError --
|
|
159
|
+
# degrade to the poll fallback (its documented latency) instead
|
|
160
|
+
# of crashing.
|
|
161
|
+
return _wait_exit_by_polling(proc, deadline=deadline, clock=clock)
|
|
162
|
+
if fd in ready:
|
|
163
|
+
return "exited"
|
|
164
|
+
if ready:
|
|
165
|
+
return "woken"
|
|
166
|
+
return "timeout"
|
|
144
167
|
finally:
|
|
145
168
|
os.close(fd)
|
|
146
169
|
|
|
@@ -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.7'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 7)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -141,7 +141,7 @@ def _snapshot_stray_descendants(proc: subprocess.Popen) -> list[dict]:
|
|
|
141
141
|
while the leader (and hence its subtree) is still alive -- both reap sites
|
|
142
142
|
(``_kill_pgroup`` and ``cli/_serve_round._terminate_round``) open with it, so
|
|
143
143
|
the capture-before-signal ordering lives in ONE place, not copy-pasted."""
|
|
144
|
-
stray, _ignored = _live_children(proc)
|
|
144
|
+
stray, _ignored = _live_children(proc, max_n=None)
|
|
145
145
|
_capture_descendant_pgids(stray)
|
|
146
146
|
return stray
|
|
147
147
|
|
|
@@ -206,7 +206,7 @@ def _live_children(
|
|
|
206
206
|
proc: subprocess.Popen,
|
|
207
207
|
*,
|
|
208
208
|
ignore_patterns: list[re.Pattern[str]] | None = None,
|
|
209
|
-
max_n: int = 5,
|
|
209
|
+
max_n: int | None = 5,
|
|
210
210
|
) -> tuple[list[dict], list[dict]]:
|
|
211
211
|
"""Live (non-zombie) descendants of ``proc``, split into ``(live, ignored)``.
|
|
212
212
|
|
|
@@ -217,6 +217,12 @@ def _live_children(
|
|
|
217
217
|
events-*.jsonl. Ignore-pattern MATCHING runs against the full cmdline
|
|
218
218
|
(detection unchanged); only what we STORE is minimized.
|
|
219
219
|
|
|
220
|
+
``max_n`` (default 5) bounds only the event-persisted lists this returns
|
|
221
|
+
to a caller that writes them to events-*.jsonl. The reap snapshot
|
|
222
|
+
(``_snapshot_stray_descendants``) passes ``max_n=None`` — a real subtree
|
|
223
|
+
can exceed 5 live descendants, and undercounting it here would leave the
|
|
224
|
+
excess unreaped, not merely under-logged.
|
|
225
|
+
|
|
220
226
|
The stored name is read from the kernel-reported process name (comm —
|
|
221
227
|
e.g. /proc/<pid>/comm on Linux), not from ``Path(argv[0]).name``: comm is
|
|
222
228
|
derived from the exec()'d binary, not from argv[0], and is itself
|
|
@@ -251,11 +257,11 @@ def _live_children(
|
|
|
251
257
|
matched = p.pattern
|
|
252
258
|
break
|
|
253
259
|
if matched is not None:
|
|
254
|
-
if len(ignored) < max_n:
|
|
260
|
+
if max_n is None or len(ignored) < max_n:
|
|
255
261
|
ignored.append({"name": name, "pid": pid, "matched": matched})
|
|
256
|
-
elif len(live) < max_n:
|
|
262
|
+
elif max_n is None or len(live) < max_n:
|
|
257
263
|
live.append({"name": name, "pid": pid})
|
|
258
|
-
if len(live) >= max_n and len(ignored) >= max_n:
|
|
264
|
+
if max_n is not None and len(live) >= max_n and len(ignored) >= max_n:
|
|
259
265
|
break
|
|
260
266
|
return live, ignored
|
|
261
267
|
|
|
@@ -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,
|