cli-agent-runner 0.3.4__tar.gz → 0.3.5__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.4 → cli_agent_runner-0.3.5}/.vulture-whitelist.py +2 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/CHANGELOG.md +5 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/PKG-INFO +1 -1
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_plugin_manifest.py +20 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_serve_policy.py +13 -2
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/agent_runtime.py +16 -4
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/_serve_round.py +6 -4
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/common.py +8 -2
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/doctor_cmd.py +15 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/serve_cmd.py +49 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/config/models.py +26 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/config/parsers.py +24 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/runner.py +24 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/commands.md +1 -1
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/configuration.md +15 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/memory-efficiency.md +23 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/plugins.md +2 -2
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_auto_stop_e2e.py +9 -1
- cli_agent_runner-0.3.5/tests/integration/test_cooperative_grace_property.py +142 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_run_one_round_with_fake_agent.py +26 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_spawn_round_wedged.py +14 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_architecture.py +1 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_peek_schema_version.py +1 -1
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_timeout_budget_invariant.py +32 -1
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime_grace.py +82 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime_reap.py +53 -0
- cli_agent_runner-0.3.5/tests/unit/test_apply_reap_grace_env.py +92 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config.py +44 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_doctor_cmd.py +9 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_kill_pgroup_shielding.py +24 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_peek_sandbox_block.py +26 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugin_manifest.py +44 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_runner.py +35 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_service_unit.py +4 -3
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.codecov.yml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/dependabot.yml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/.gitignore +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/BACKLOG.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/LICENSE +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/README.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/README.zh.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/SECURITY.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_emit/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_emit/agent.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_emit/memory.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_emit/plugins.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_emit/rounds.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_emit/serve.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_install.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_lifecycle.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_monitor_state.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_notify.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_observe.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_plugin_checksum.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_plugin_sandbox.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_plugin_scan.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_procwait.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_round_outcome.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_sandbox_probe.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/api.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/_serve_cgroup.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/config/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/config/loader.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/config/validators.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/events.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/host_health.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/phase_select.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/build.sh +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/deploy/Dockerfile +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/deploy/docker-compose.yml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/README.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/architecture.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/events.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/migrations/0.2.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/migrations/0.3.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/recipes/container-pi.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/recipes/deploy-all-in-one.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/runbook.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/docs/thesis.md +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/pyproject.toml +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/_clock.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/conftest.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/contract/test_public_api_surface.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_reap_property.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_spawn_round_mem_floor.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_bdd_test_conventions.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_builtin_plugin_names_sync.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_container_stop_bound.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_import_footprint.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_kill_tests_not_skipped.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_asyncio_select.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_price_logic.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_vacuous_invariants.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_no_version_refs.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_procwait_shield.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_round_alloc_growth.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/linux/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/linux/test_plugin_sandbox_kill.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime_container.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_builtin_provenance.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cgroup_probe.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_collapse_spawn_decisions.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_nested_strictness.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_dispatch_dirty_owner_branch.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_doctor_sandbox.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_events.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_example_dirty_handler.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_exec_prefix.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_gate_serve_boot.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_git_timeout.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_host_health.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_http_progress_epoch.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_load_fail_closed.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_loader_split.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_manifest_spawn_hooks.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_metrics_cgroup_usage.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_metrics_swap.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_dedup_rearm.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_loop.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_on_alert.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_notify.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_phase_select.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugin_checksum.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugin_name_mismatch.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugin_sandbox_wire.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugin_scan_parity.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_plugins_config_fields.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_primary_prompt_file_home.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_procwait.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_round_outcome.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_round_outcome_agent_axis.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_sandbox_probe.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_policy.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_round.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_round_oom_killed.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_round_terminate.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_round_wait_exit.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_serve_wakeup.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_spawn_seam.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_terminate_round_wait_exit.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_throttle_epoch_bound.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.3.4 → cli_agent_runner-0.3.5}/tests/unit/test_vcs_state.py +0 -0
|
@@ -158,6 +158,7 @@ overlaps
|
|
|
158
158
|
plan
|
|
159
159
|
sandbox
|
|
160
160
|
cgroup
|
|
161
|
+
sigterm_grace
|
|
161
162
|
|
|
162
163
|
# agent_runner.config.models.AgentConfig
|
|
163
164
|
command
|
|
@@ -167,6 +168,7 @@ env
|
|
|
167
168
|
prompt_delivery
|
|
168
169
|
exec_prefix
|
|
169
170
|
terminal_marker
|
|
171
|
+
sigterm_grace_s
|
|
170
172
|
|
|
171
173
|
# agent_runner.config.models.Config
|
|
172
174
|
agent
|
|
@@ -5,6 +5,11 @@ 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.5] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `[agent] sigterm_grace_s` (default 10): the SIGTERM→SIGKILL grace a round gives its agent when the agent's preset declares itself cooperative (the built-in `gemini` preset does), so an agent that catches SIGTERM to flush or commit its work gets time to finish. A non-cooperative agent (e.g. `claude`) keeps the previous 5 s. Capped at 12 s, inside the supervisor's 15 s wait for the round; `peek --json` (schema `2.4`) and `doctor` report the resolved grace per agent.
|
|
12
|
+
|
|
8
13
|
## [0.3.4] - 2026-09-15
|
|
9
14
|
|
|
10
15
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
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
|
|
@@ -62,6 +62,26 @@ def cooperative_manifest_names() -> list[str]:
|
|
|
62
62
|
return [m.name for m in _LOADED_MANIFESTS if m.sigterm_cooperative]
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
def is_cooperative_agent(agent_binary: str | None) -> bool:
|
|
66
|
+
"""Whether the agent's preset declares itself SIGTERM-cooperative.
|
|
67
|
+
`None in [...]` is safely False, so no None-guard is needed."""
|
|
68
|
+
return agent_binary in cooperative_manifest_names()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def resolve_sigterm_grace_s(agent_binary: str | None, sigterm_grace_s: int) -> int:
|
|
72
|
+
"""The SIGTERM->SIGKILL grace this round's agent actually gets:
|
|
73
|
+
`sigterm_grace_s` (config's [agent] sigterm_grace_s, boot-capped at
|
|
74
|
+
_ROUND_TERM_GRACE_S) when `agent_binary` names a manifest declaring
|
|
75
|
+
sigterm_cooperative=True, else agent_runtime.REAP_GRACE_S (5s)
|
|
76
|
+
unchanged. One function, two callers: cli.serve_cmd._apply_reap_grace_env
|
|
77
|
+
(what the agent actually gets) and doctor/peek (what an operator sees)."""
|
|
78
|
+
from agent_runner.agent_runtime import REAP_GRACE_S
|
|
79
|
+
|
|
80
|
+
if is_cooperative_agent(agent_binary):
|
|
81
|
+
return sigterm_grace_s
|
|
82
|
+
return REAP_GRACE_S
|
|
83
|
+
|
|
84
|
+
|
|
65
85
|
def register_manifest(
|
|
66
86
|
manifest: PluginManifest,
|
|
67
87
|
*,
|
|
@@ -312,13 +312,24 @@ def timeout_budget(round_budget_s: int) -> tuple[int, int]:
|
|
|
312
312
|
round subprocess (``api.outer_round_ceiling_s``): ``round_budget_s``
|
|
313
313
|
plus reap grace + git-commit ceiling + hook allowance, so it only trips
|
|
314
314
|
when the round supervisor itself is wedged, never during its own
|
|
315
|
-
bounded post-round cleanup.
|
|
315
|
+
bounded post-round cleanup. The reap margin must cover the widest grace
|
|
316
|
+
ANY round can be configured with -- a cooperative agent's
|
|
317
|
+
``sigterm_grace_s`` is boot-capped at ``_ROUND_TERM_GRACE_S``
|
|
318
|
+
(``config/models.py``'s mirror of this module's single source), not
|
|
319
|
+
the non-cooperative default ``_REAP_GRACE_S``, so the margin is
|
|
320
|
+
``max(_REAP_GRACE_S, _ROUND_TERM_GRACE_S)`` -- else a long cooperative
|
|
321
|
+
grace could trip the outer wall-clock ceiling mid-grace.
|
|
316
322
|
- ``timeout_stop_sec`` — systemd's ``TimeoutStopSec``
|
|
317
323
|
(``service_unit.py``): must clear ``outer_ceiling_s`` by enough for a
|
|
318
324
|
SIGTERM to reach and drain the round (``_ROUND_TERM_GRACE_S``) plus a
|
|
319
325
|
stop-request overhead pad, so `systemctl stop` never SIGKILLs a round
|
|
320
326
|
that is draining normally.
|
|
321
327
|
"""
|
|
322
|
-
outer_ceiling_s =
|
|
328
|
+
outer_ceiling_s = (
|
|
329
|
+
round_budget_s
|
|
330
|
+
+ max(_REAP_GRACE_S, _ROUND_TERM_GRACE_S)
|
|
331
|
+
+ _GIT_COMMIT_TIMEOUT_S
|
|
332
|
+
+ _HOOK_ALLOWANCE_S
|
|
333
|
+
)
|
|
323
334
|
timeout_stop_sec = outer_ceiling_s + _ROUND_TERM_GRACE_S + _STOP_GRACE_MARGIN_S
|
|
324
335
|
return timeout_stop_sec, outer_ceiling_s
|
|
@@ -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.5'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 5)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -161,7 +161,9 @@ def _wait_exit_shielded(
|
|
|
161
161
|
continue
|
|
162
162
|
|
|
163
163
|
|
|
164
|
-
def _kill_pgroup(
|
|
164
|
+
def _kill_pgroup(
|
|
165
|
+
proc: subprocess.Popen, clock: Clock = SYSTEM_CLOCK, reap_grace_s: int = REAP_GRACE_S
|
|
166
|
+
) -> None:
|
|
165
167
|
"""SIGTERM the pgroup, grace, then SIGKILL — the reap primitive shared by
|
|
166
168
|
the round-timeout path and ``run``'s BaseException handler (which fires on
|
|
167
169
|
a SIGTERM landing while the round is unwinding from a first one).
|
|
@@ -185,7 +187,7 @@ def _kill_pgroup(proc: subprocess.Popen, clock: Clock = SYSTEM_CLOCK) -> None:
|
|
|
185
187
|
except OSError:
|
|
186
188
|
pass
|
|
187
189
|
deadline = (
|
|
188
|
-
clock.monotonic() +
|
|
190
|
+
clock.monotonic() + reap_grace_s
|
|
189
191
|
) # monotonic: an NTP step must not stretch/skip the reap
|
|
190
192
|
_wait_exit_shielded(proc, deadline=deadline, clock=clock)
|
|
191
193
|
# "exited" or "timeout": either way, fall through to the SIGKILL last-resort
|
|
@@ -574,6 +576,7 @@ def _terminate_agent(
|
|
|
574
576
|
container_runtime: str | None,
|
|
575
577
|
container_cidfile: Path | None,
|
|
576
578
|
on_container_orphan_risk: Callable[[str, str | None, bool | None], None] | None,
|
|
579
|
+
reap_grace_s: int = REAP_GRACE_S,
|
|
577
580
|
) -> None:
|
|
578
581
|
"""Reap the agent pgroup (``_kill_pgroup``), then -- for a
|
|
579
582
|
container-launching command only -- make a best-effort container `stop`
|
|
@@ -581,7 +584,7 @@ def _terminate_agent(
|
|
|
581
584
|
hard-kill entry point ``run()`` uses for all three of its termination
|
|
582
585
|
paths (R1128 wall-clock, grace-kill, and the BaseException reap), so the
|
|
583
586
|
container handling isn't duplicated three times."""
|
|
584
|
-
_kill_pgroup(proc, clock)
|
|
587
|
+
_kill_pgroup(proc, clock, reap_grace_s=reap_grace_s)
|
|
585
588
|
if container_runtime is not None:
|
|
586
589
|
cid, stop_ok = _best_effort_container_stop(container_runtime, container_cidfile)
|
|
587
590
|
if on_container_orphan_risk is not None:
|
|
@@ -606,10 +609,16 @@ def run(
|
|
|
606
609
|
grace_kill_ignore_patterns: list[re.Pattern[str]] | None = None,
|
|
607
610
|
on_container_orphan_risk: Callable[[str, str | None, bool | None], None] | None = None,
|
|
608
611
|
clock: Clock = SYSTEM_CLOCK,
|
|
612
|
+
reap_grace_s: int = REAP_GRACE_S,
|
|
609
613
|
) -> RunResult:
|
|
610
614
|
"""Spawn the agent subprocess and wait for exit or timeout.
|
|
611
615
|
|
|
612
|
-
Wall-clock timeout (R1128). On timeout: SIGTERM pgroup →
|
|
616
|
+
Wall-clock timeout (R1128). On timeout: SIGTERM pgroup → reap_grace_s → SIGKILL.
|
|
617
|
+
|
|
618
|
+
reap_grace_s: the SIGTERM->SIGKILL grace passed straight through to every
|
|
619
|
+
``_terminate_agent`` call site below (R1128 wall-clock, grace-kill, and
|
|
620
|
+
the BaseException reap) -- one deadline value, threaded end to end.
|
|
621
|
+
Defaults to REAP_GRACE_S so every existing caller is unchanged.
|
|
613
622
|
|
|
614
623
|
work_dir: the agent child's working directory; callers pass the
|
|
615
624
|
already-absolute cfg.runtime.work_dir. CLIs with no --cwd flag of their
|
|
@@ -776,6 +785,7 @@ def run(
|
|
|
776
785
|
container_runtime=container_runtime,
|
|
777
786
|
container_cidfile=container_cidfile,
|
|
778
787
|
on_container_orphan_risk=_report_orphan_once,
|
|
788
|
+
reap_grace_s=reap_grace_s,
|
|
779
789
|
)
|
|
780
790
|
duration = clock.monotonic() - start
|
|
781
791
|
exit_code = proc.returncode if proc.returncode is not None else -1
|
|
@@ -820,6 +830,7 @@ def run(
|
|
|
820
830
|
container_runtime=container_runtime,
|
|
821
831
|
container_cidfile=container_cidfile,
|
|
822
832
|
on_container_orphan_risk=_report_orphan_once,
|
|
833
|
+
reap_grace_s=reap_grace_s,
|
|
823
834
|
)
|
|
824
835
|
duration = clock.monotonic() - start
|
|
825
836
|
exit_code = proc.returncode if proc.returncode is not None else -1
|
|
@@ -860,6 +871,7 @@ def run(
|
|
|
860
871
|
container_runtime=container_runtime,
|
|
861
872
|
container_cidfile=container_cidfile,
|
|
862
873
|
on_container_orphan_risk=_report_orphan_once,
|
|
874
|
+
reap_grace_s=reap_grace_s,
|
|
863
875
|
)
|
|
864
876
|
raise
|
|
865
877
|
finally:
|
|
@@ -362,10 +362,12 @@ def _maybe_defer_for_spawn_hooks(
|
|
|
362
362
|
|
|
363
363
|
|
|
364
364
|
# Grace after TERMing a wedged round before killpg: the round's own SIGTERM handler
|
|
365
|
-
# reaps its agent pgroup
|
|
366
|
-
#
|
|
367
|
-
#
|
|
368
|
-
#
|
|
365
|
+
# reaps its agent pgroup -- up to agent_runtime.REAP_GRACE_S for a non-cooperative
|
|
366
|
+
# agent, or up to the configured (boot-capped) config.models._MAX_SIGTERM_GRACE_S
|
|
367
|
+
# for a cooperative one -- then exits, so allow that plus margin. test_spawn_round_wedged
|
|
368
|
+
# asserts it stays > _MAX_SIGTERM_GRACE_S so the two never drift. Single-sourced in
|
|
369
|
+
# _serve_policy (imported above) -- api.py imports the same constant, so this and the
|
|
370
|
+
# out-of-process kill path can no longer drift apart.
|
|
369
371
|
#
|
|
370
372
|
# _ROUND_UNREAPED_RC (the D-state-leader sentinel returned below) is likewise
|
|
371
373
|
# single-sourced in _serve_policy (imported above), not defined here.
|
|
@@ -19,7 +19,7 @@ from agent_runner.hooks import plugin_context_enrichers, post_round_hooks, pre_r
|
|
|
19
19
|
from agent_runner.monitor import plugin_detectors
|
|
20
20
|
from agent_runner.vcs_state import plugin_owned_paths
|
|
21
21
|
|
|
22
|
-
PEEK_SCHEMA_VERSION = "2.
|
|
22
|
+
PEEK_SCHEMA_VERSION = "2.4"
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def cfg_from_args(args) -> Config:
|
|
@@ -98,7 +98,10 @@ def emit(value: Any, *, json_mode: bool, cfg: Config | None = None) -> None:
|
|
|
98
98
|
if json_mode:
|
|
99
99
|
if isinstance(value, ProjectState):
|
|
100
100
|
from agent_runner import _sandbox_probe, disabled_plugin_names
|
|
101
|
-
from agent_runner._plugin_manifest import
|
|
101
|
+
from agent_runner._plugin_manifest import (
|
|
102
|
+
cooperative_manifest_names,
|
|
103
|
+
resolve_sigterm_grace_s,
|
|
104
|
+
)
|
|
102
105
|
|
|
103
106
|
plugins_block = {
|
|
104
107
|
"event_kinds": plugin_event_kinds(),
|
|
@@ -112,6 +115,9 @@ def emit(value: Any, *, json_mode: bool, cfg: Config | None = None) -> None:
|
|
|
112
115
|
}
|
|
113
116
|
if cfg is not None:
|
|
114
117
|
plugins_block.update(_sandbox_probe.peek_snapshot(cfg))
|
|
118
|
+
plugins_block["resolved_sigterm_grace_s"] = resolve_sigterm_grace_s(
|
|
119
|
+
cfg.agent.binary, cfg.agent.sigterm_grace_s
|
|
120
|
+
)
|
|
115
121
|
wrapped = {
|
|
116
122
|
"schema_version": PEEK_SCHEMA_VERSION,
|
|
117
123
|
"plugins": plugins_block,
|
|
@@ -17,6 +17,7 @@ class DoctorReport:
|
|
|
17
17
|
plan: list[dict]
|
|
18
18
|
sandbox: dict
|
|
19
19
|
cgroup: dict
|
|
20
|
+
sigterm_grace: dict
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def add_parser(sub, parent) -> None:
|
|
@@ -65,12 +66,21 @@ def cmd_doctor(args) -> int:
|
|
|
65
66
|
from agent_runner._sandbox_probe import doctor_snapshot
|
|
66
67
|
|
|
67
68
|
cfg = cfg_from_args(args)
|
|
69
|
+
is_cooperative = _plugin_manifest.is_cooperative_agent(cfg.agent.binary)
|
|
70
|
+
sigterm_grace = {
|
|
71
|
+
"agent": cfg.agent.binary,
|
|
72
|
+
"cooperative": is_cooperative,
|
|
73
|
+
"resolved_s": _plugin_manifest.resolve_sigterm_grace_s(
|
|
74
|
+
cfg.agent.binary, cfg.agent.sigterm_grace_s
|
|
75
|
+
),
|
|
76
|
+
}
|
|
68
77
|
report = DoctorReport(
|
|
69
78
|
checks=startup_check.run_battery(cfg),
|
|
70
79
|
overlaps=phase_select.find_phase_window_overlaps(cfg),
|
|
71
80
|
plan=_plan(cfg, args.rounds),
|
|
72
81
|
sandbox=doctor_snapshot(cfg),
|
|
73
82
|
cgroup=_cgroup_report(),
|
|
83
|
+
sigterm_grace=sigterm_grace,
|
|
74
84
|
)
|
|
75
85
|
json_mode = getattr(args, "json", False)
|
|
76
86
|
if json_mode:
|
|
@@ -122,6 +132,11 @@ def _format(report: DoctorReport) -> str:
|
|
|
122
132
|
lines.append(f' {name} = "{digest}"')
|
|
123
133
|
cooperative = _plugin_manifest.cooperative_manifest_names()
|
|
124
134
|
lines.append(f"cooperative presets: {', '.join(sorted(cooperative)) or '(none)'}")
|
|
135
|
+
sg = report.sigterm_grace
|
|
136
|
+
lines.append(
|
|
137
|
+
f"sigterm grace: {sg['resolved_s']}s "
|
|
138
|
+
f"({'cooperative' if sg['cooperative'] else 'non-cooperative'}, agent={sg['agent']})"
|
|
139
|
+
)
|
|
125
140
|
cgroup = report.cgroup
|
|
126
141
|
lines.append("cgroup:")
|
|
127
142
|
lines.append(f" cgroup_path: {cgroup['cgroup_path']}")
|
|
@@ -598,6 +598,51 @@ def _apply_round_num_env(round_env: dict, round_num: int) -> None:
|
|
|
598
598
|
round_env["AGENT_RUNNER_ROUND_NUM"] = str(round_num)
|
|
599
599
|
|
|
600
600
|
|
|
601
|
+
def _resolve_reap_grace_by_phase(cfg) -> dict[str | None, int]:
|
|
602
|
+
"""Precompute this serve lifetime's per-phase SIGTERM grace ONCE --
|
|
603
|
+
boot-invariant like cgroup_probe_defer/overlaps below (for a fixed
|
|
604
|
+
phase_arg, resolve_sigterm_grace_s depends only on the per-phase-static
|
|
605
|
+
agent binary + sigterm_grace_s + the once-populated plugin-manifest
|
|
606
|
+
registry), so _apply_reap_grace_env does a single dict lookup per round
|
|
607
|
+
instead of re-resolving from scratch. Keyed by the finite known
|
|
608
|
+
phase_arg set: None (base) plus every configured [phases.<name>]
|
|
609
|
+
override."""
|
|
610
|
+
from agent_runner._plugin_manifest import resolve_sigterm_grace_s
|
|
611
|
+
|
|
612
|
+
phase_args: list[str | None] = [None, *cfg.phases.overrides.keys()]
|
|
613
|
+
return {
|
|
614
|
+
phase_arg: resolve_sigterm_grace_s(
|
|
615
|
+
cfg.profile_for(phase_arg).agent.binary,
|
|
616
|
+
cfg.profile_for(phase_arg).agent.sigterm_grace_s,
|
|
617
|
+
)
|
|
618
|
+
for phase_arg in phase_args
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def _apply_reap_grace_env(
|
|
623
|
+
cfg, round_env: dict, phase_arg: str | None, grace_by_phase: dict[str | None, int]
|
|
624
|
+
) -> None:
|
|
625
|
+
"""Publish this round's agent's SIGTERM grace via
|
|
626
|
+
AGENT_RUNNER_REAP_GRACE_S -- the same single-source-from-serve pattern
|
|
627
|
+
AGENT_RUNNER_ROUND_NUM uses, so the round child's grace can't skew from
|
|
628
|
+
the phase serve actually selected this round (see
|
|
629
|
+
_plugin_manifest.resolve_sigterm_grace_s for the decision itself). The
|
|
630
|
+
grace is looked up in `grace_by_phase` (precomputed once per serve
|
|
631
|
+
lifetime by _resolve_reap_grace_by_phase) rather than re-resolved here --
|
|
632
|
+
but the env write itself stays UNCONDITIONAL every round, so a
|
|
633
|
+
cooperative<->non-cooperative phase transition still overwrites the
|
|
634
|
+
previous round's value."""
|
|
635
|
+
from agent_runner._plugin_manifest import resolve_sigterm_grace_s
|
|
636
|
+
|
|
637
|
+
grace = grace_by_phase.get(phase_arg)
|
|
638
|
+
if grace is None:
|
|
639
|
+
# Defensive only -- every phase_arg serve can select is one of the
|
|
640
|
+
# keys _resolve_reap_grace_by_phase precomputed from this same cfg.
|
|
641
|
+
agent = cfg.profile_for(phase_arg).agent
|
|
642
|
+
grace = resolve_sigterm_grace_s(agent.binary, agent.sigterm_grace_s)
|
|
643
|
+
round_env["AGENT_RUNNER_REAP_GRACE_S"] = str(grace)
|
|
644
|
+
|
|
645
|
+
|
|
601
646
|
def _capture_substrate(work_dir, cfg, log_dir, round_num, *, when):
|
|
602
647
|
"""Snapshot git-head + paths-hash and emit the round-substrate event for `when`."""
|
|
603
648
|
git_head = compute_git_head(work_dir)
|
|
@@ -689,6 +734,9 @@ def cmd(args) -> int:
|
|
|
689
734
|
# Static config check -- phase-window collisions can't change mid-lifetime, so
|
|
690
735
|
# (like the cgroup probe above) this runs once at boot, not once per round.
|
|
691
736
|
overlaps = phase_select.find_phase_window_overlaps(cfg)
|
|
737
|
+
# Boot-invariant per-phase SIGTERM grace -- resolved once here, looked up
|
|
738
|
+
# (not re-resolved) every round by _apply_reap_grace_env below.
|
|
739
|
+
grace_by_phase = _resolve_reap_grace_by_phase(cfg)
|
|
692
740
|
if overlaps:
|
|
693
741
|
detail = phase_select.describe_overlaps(overlaps)
|
|
694
742
|
_release_serve_lock(serve_lock_fd)
|
|
@@ -756,6 +804,7 @@ def cmd(args) -> int:
|
|
|
756
804
|
_capture_substrate(work_dir, cfg, log_dir, round_num, when="before")
|
|
757
805
|
_apply_fresh_eyes(cfg, log_dir, round_num, round_env)
|
|
758
806
|
_apply_round_num_env(round_env, round_num)
|
|
807
|
+
_apply_reap_grace_env(cfg, round_env, phase_arg, grace_by_phase)
|
|
759
808
|
round_log_path = log_dir / f"round-{round_num}.log"
|
|
760
809
|
round_started = SYSTEM_CLOCK.monotonic()
|
|
761
810
|
round_argv = [
|
|
@@ -28,6 +28,24 @@ _VALID_PROMPT_DELIVERY: frozenset[str] = frozenset({"argv", "stdin"})
|
|
|
28
28
|
# this literal but is decoupled to avoid an agent_runtime -> config import.
|
|
29
29
|
DEFAULT_TERMINAL_MARKER = '"type":"result"'
|
|
30
30
|
|
|
31
|
+
# Default [agent] sigterm_grace_s: the SIGTERM->SIGKILL grace a cooperative
|
|
32
|
+
# agent's preset gets from the round leader (see AgentConfig.sigterm_grace_s).
|
|
33
|
+
DEFAULT_SIGTERM_GRACE_S = 10
|
|
34
|
+
|
|
35
|
+
# Ceiling for [agent] sigterm_grace_s: sits a STRICT ~3s BELOW _serve_policy's
|
|
36
|
+
# _ROUND_TERM_GRACE_S (15, the supervisor's own wait for the round leader) --
|
|
37
|
+
# NOT equal to it. The margin matters on the out-of-process `agent-runner kill`
|
|
38
|
+
# path (_lifecycle._terminate_round_pid), which SIGKILLs only the leader pid
|
|
39
|
+
# with no stray-reap: without it, a leader whose own killpg(SIGKILL) of its
|
|
40
|
+
# agent fires at the same instant the supervisor SIGKILLs the leader could lose
|
|
41
|
+
# the race, leaving the agent permanently orphaned. config/ cannot import
|
|
42
|
+
# _serve_policy (a cycle: _serve_policy already imports agent_runner.config for
|
|
43
|
+
# ConfigError), so this is a LITERAL mirror-with-margin, pinned by
|
|
44
|
+
# tests/invariants/test_timeout_budget_invariant.py against drift, the same
|
|
45
|
+
# pattern _serve_policy uses for its own agent_runtime.REAP_GRACE_S /
|
|
46
|
+
# vcs_state.GIT_COMMIT_TIMEOUT_S mirrors.
|
|
47
|
+
_MAX_SIGTERM_GRACE_S = 12
|
|
48
|
+
|
|
31
49
|
|
|
32
50
|
@dataclass(frozen=True)
|
|
33
51
|
class AgentConfig:
|
|
@@ -42,6 +60,14 @@ class AgentConfig:
|
|
|
42
60
|
the agent's terminal record (see ``agent_runtime.run``'s marker scan).
|
|
43
61
|
Defaults to claude's own JSONL token so existing configs are unaffected;
|
|
44
62
|
an empty string opts out of the marker scan entirely (wall-clock-ceiling-only)."""
|
|
63
|
+
sigterm_grace_s: int = DEFAULT_SIGTERM_GRACE_S
|
|
64
|
+
"""The SIGTERM->SIGKILL grace this round's leader gives its agent, applied
|
|
65
|
+
only when the agent's preset declares itself cooperative (sigterm_cooperative
|
|
66
|
+
on its PluginManifest) -- a non-cooperative agent always gets the fixed
|
|
67
|
+
agent_runtime.REAP_GRACE_S instead. Capped at _MAX_SIGTERM_GRACE_S, which sits
|
|
68
|
+
strictly inside (not equal to) _serve_policy._ROUND_TERM_GRACE_S, the
|
|
69
|
+
supervisor's own wait for the round leader -- a value above the cap is
|
|
70
|
+
rejected at boot, not clamped."""
|
|
45
71
|
|
|
46
72
|
@property
|
|
47
73
|
def binary(self) -> str | None:
|
|
@@ -22,6 +22,7 @@ from agent_runner.config.models import (
|
|
|
22
22
|
_HOST_HEALTH_DISK_ALLOWED_FIELDS,
|
|
23
23
|
_HOST_HEALTH_MEMORY_ALLOWED_FIELDS,
|
|
24
24
|
_HOST_HEALTH_PRESSURE_ALLOWED_FIELDS,
|
|
25
|
+
_MAX_SIGTERM_GRACE_S,
|
|
25
26
|
_MONITOR_ALLOWED_FIELDS,
|
|
26
27
|
_MONITOR_HOST_HEALTH_ALLOWED_FIELDS,
|
|
27
28
|
_PHASE_OVERRIDE_ALLOWED_FIELDS,
|
|
@@ -36,6 +37,7 @@ from agent_runner.config.models import (
|
|
|
36
37
|
_VALID_SANDBOX_MODES,
|
|
37
38
|
_VALID_TRANSIENT_ERROR_ACTIONS,
|
|
38
39
|
_VCS_ALLOWED_FIELDS,
|
|
40
|
+
DEFAULT_SIGTERM_GRACE_S,
|
|
39
41
|
DEFAULT_TERMINAL_MARKER,
|
|
40
42
|
AgentConfig,
|
|
41
43
|
MonitorConfig,
|
|
@@ -67,6 +69,23 @@ from agent_runner.config.validators import (
|
|
|
67
69
|
)
|
|
68
70
|
|
|
69
71
|
|
|
72
|
+
def _validate_sigterm_grace_s(value: Any, *, field: str) -> int:
|
|
73
|
+
"""Validate [agent] sigterm_grace_s (or a per-phase override): positive
|
|
74
|
+
int, capped at _MAX_SIGTERM_GRACE_S -- a value strictly inside (not equal
|
|
75
|
+
to) the supervisor's own wait for the round leader, preserving leader-exit
|
|
76
|
+
margin. Mirrors _validate_remote_failure_tolerance's require-then-cap
|
|
77
|
+
shape; `field` is threaded through (unlike that single-call-site
|
|
78
|
+
validator) since this one validates both the base [agent] table and each
|
|
79
|
+
[phases.<name>.agent] override, under different prefixes."""
|
|
80
|
+
v = _require_positive_int(value, field=field)
|
|
81
|
+
if v > _MAX_SIGTERM_GRACE_S:
|
|
82
|
+
raise ConfigError(
|
|
83
|
+
f"{field}: must be <= {_MAX_SIGTERM_GRACE_S} "
|
|
84
|
+
f"(strictly inside the supervisor's own wait for the round leader), got {v}"
|
|
85
|
+
)
|
|
86
|
+
return v
|
|
87
|
+
|
|
88
|
+
|
|
70
89
|
def _parse_agent(
|
|
71
90
|
agent_d: dict, *, field_prefix: str, require_prompt_placeholder: bool = True
|
|
72
91
|
) -> AgentConfig:
|
|
@@ -119,6 +138,10 @@ def _parse_agent(
|
|
|
119
138
|
agent_d.get("exec_prefix", []), field=f"{field_prefix} exec_prefix"
|
|
120
139
|
)
|
|
121
140
|
terminal_marker = str(agent_d.get("terminal_marker", DEFAULT_TERMINAL_MARKER))
|
|
141
|
+
sigterm_grace_s = _validate_sigterm_grace_s(
|
|
142
|
+
agent_d.get("sigterm_grace_s", DEFAULT_SIGTERM_GRACE_S),
|
|
143
|
+
field=f"{field_prefix} sigterm_grace_s",
|
|
144
|
+
)
|
|
122
145
|
return AgentConfig(
|
|
123
146
|
command=command,
|
|
124
147
|
prompt_arg_template=prompt_arg_template,
|
|
@@ -127,6 +150,7 @@ def _parse_agent(
|
|
|
127
150
|
prompt_delivery=prompt_delivery, # type: ignore[arg-type] # narrowed above
|
|
128
151
|
exec_prefix=exec_prefix,
|
|
129
152
|
terminal_marker=terminal_marker,
|
|
153
|
+
sigterm_grace_s=sigterm_grace_s,
|
|
130
154
|
)
|
|
131
155
|
|
|
132
156
|
|
|
@@ -190,6 +190,29 @@ def _resolve_round_num(log_dir: Path) -> int:
|
|
|
190
190
|
return next_round_num(log_dir)
|
|
191
191
|
|
|
192
192
|
|
|
193
|
+
def _resolve_reap_grace_s() -> int:
|
|
194
|
+
"""The SIGTERM->SIGKILL grace this round's agent gets, published by serve
|
|
195
|
+
via AGENT_RUNNER_REAP_GRACE_S (cli/serve_cmd.py's _apply_reap_grace_env)
|
|
196
|
+
so the child never re-derives (and skews) serve's own cooperative-manifest
|
|
197
|
+
resolution; a standalone `agent-runner round` (no env) falls back to
|
|
198
|
+
agent_runtime.REAP_GRACE_S -- the same non-cooperative default -- when the
|
|
199
|
+
env var is absent, unparseable, or non-positive (a leaked <=0 value would
|
|
200
|
+
make a past deadline and kill with zero grace; serve only ever publishes a
|
|
201
|
+
boot-validated positive value or REAP_GRACE_S, so this only guards a
|
|
202
|
+
hand-set/leaked standalone env)."""
|
|
203
|
+
from agent_runner.agent_runtime import REAP_GRACE_S
|
|
204
|
+
|
|
205
|
+
raw = os.environ.get("AGENT_RUNNER_REAP_GRACE_S")
|
|
206
|
+
if raw is not None:
|
|
207
|
+
try:
|
|
208
|
+
parsed = int(raw)
|
|
209
|
+
except ValueError:
|
|
210
|
+
parsed = None
|
|
211
|
+
if parsed is not None and parsed > 0:
|
|
212
|
+
return parsed
|
|
213
|
+
return REAP_GRACE_S
|
|
214
|
+
|
|
215
|
+
|
|
193
216
|
def _previous_block(prev: context_store.Status | None, dirty_last: bool) -> dict[str, Any] | None:
|
|
194
217
|
if prev is None:
|
|
195
218
|
return None
|
|
@@ -573,6 +596,7 @@ def _run_one_round_inner(cfg: Config, *, phase_override: str | None = None) -> R
|
|
|
573
596
|
on_grace_extended=_grace_extended_emit,
|
|
574
597
|
grace_kill_ignore_patterns=grace_kill_ignore_patterns,
|
|
575
598
|
on_container_orphan_risk=_container_orphan_risk_emit,
|
|
599
|
+
reap_grace_s=_resolve_reap_grace_s(),
|
|
576
600
|
)
|
|
577
601
|
events.emit(
|
|
578
602
|
log_dir,
|
|
@@ -294,7 +294,7 @@ The transforms `migrate` applies (generated from the registry):
|
|
|
294
294
|
- unknown [schedule] key(s) rejected in 0.2.12; delete them (allowed: ['pause_windows', 'run_windows', 'timezone'])
|
|
295
295
|
- monitor.anomaly_repetitive_threshold > anomaly_repetitive_window: lower the threshold or raise the window so the detector can fire
|
|
296
296
|
- [<table>] given as a scalar, not a table; give it real [table] content (no auto-fix possible)
|
|
297
|
-
- unknown [agent] key(s) rejected in 0.2.13; delete them (allowed: ['command', 'env', 'exec_prefix', 'name', 'prompt_arg_template', 'prompt_delivery', 'terminal_marker'])
|
|
297
|
+
- unknown [agent] key(s) rejected in 0.2.13; delete them (allowed: ['command', 'env', 'exec_prefix', 'name', 'prompt_arg_template', 'prompt_delivery', 'sigterm_grace_s', 'terminal_marker'])
|
|
298
298
|
- unknown [runtime] key(s) rejected in 0.2.13; delete them (allowed: ['disable_pre_round_hooks', 'dry_run', 'fresh_eyes_every_n', 'grace_kill_ignore_patterns', 'log_dir', 'max_grace_after_result_s', 'max_rounds', 'narrative_file', 'restart_delay_s', 'round_budget_s', 'round_log_retention', 'stop_file', 'substrate_fingerprint_paths', 'transient_error_action', 'work_dir'])
|
|
299
299
|
- unknown [vcs] key(s) rejected in 0.2.13; delete them (allowed: ['dirty_action', 'stash_idempotency_s'])
|
|
300
300
|
- unknown [monitor] key(s) rejected in 0.2.13; delete them (allowed: ['anomaly_repetitive_threshold', 'anomaly_repetitive_window', 'auth_fail_hint', 'auth_fail_patterns', 'auto_stop_on', 'host_health', 'remote_failure_tolerance_s', 'round_progress_interval_s', 'supervisor_stale_threshold_s'])
|
|
@@ -32,6 +32,7 @@ running with newly-set `dirty_action = "auto_commit"` is undefined).
|
|
|
32
32
|
| `prompt_delivery` | `Literal['argv', 'stdin']` | 'argv' |
|
|
33
33
|
| `exec_prefix` | `list[str]` | [] |
|
|
34
34
|
| `terminal_marker` | `str` | '"type":"result"' |
|
|
35
|
+
| `sigterm_grace_s` | `int` | 10 |
|
|
35
36
|
|
|
36
37
|
### `[runtime]`
|
|
37
38
|
|
|
@@ -184,6 +185,20 @@ grace-kill never engages and a round runs to the wall-clock
|
|
|
184
185
|
`round_budget_s` ceiling instead. An empty string disables the marker scan
|
|
185
186
|
outright — an honest opt-out, not an empty-string-matches-everything trap.
|
|
186
187
|
|
|
188
|
+
### `agent.sigterm_grace_s` (0.3.5+)
|
|
189
|
+
|
|
190
|
+
Type: int, default `10`, max `12`
|
|
191
|
+
|
|
192
|
+
The SIGTERM→SIGKILL grace the round leader gives ITS AGENT — but only when
|
|
193
|
+
the configured agent's preset declares `sigterm_cooperative = true` (the
|
|
194
|
+
built-in `gemini` preset does; see `peek --json`'s `plugins.sigterm_cooperative`
|
|
195
|
+
or `doctor`'s "cooperative presets" line). A non-cooperative agent (e.g.
|
|
196
|
+
`claude`) always gets the fixed 5s grace regardless of this setting. Capped
|
|
197
|
+
at 12 — 3 s inside the supervisor's own 15 s wait for the round
|
|
198
|
+
(`_ROUND_TERM_GRACE_S`) — a config above that is rejected at boot, not
|
|
199
|
+
silently clamped: a grace longer than the supervisor waits for the leader
|
|
200
|
+
would let the supervisor SIGKILL the leader mid-wrap-up, defeating the point.
|
|
201
|
+
|
|
187
202
|
### `runtime.round_log_retention`
|
|
188
203
|
|
|
189
204
|
**Pruning is opt-in. `0` — the default — never prunes anything.** Round logs
|
|
@@ -473,6 +473,29 @@ The decision RULES are byte-identical: none of this changes the inputs→verdict
|
|
|
473
473
|
|
|
474
474
|
Numbers are dev-host-relative (the macOS `kqueue` harness). The Linux `pidfd` fast path is exercised only on CI (ubuntu runners); the real 462 MB / 256 MB constrained-host wake-latency and `pidfd`/`kqueue`-under-swap-pressure confirmation stays deferred to 0.3.5 while that host is offline — measured on dev/CI, documented as deferred for the constrained host, never reported as zero-cost.
|
|
475
475
|
|
|
476
|
+
## 0.3.4 → 0.3.5 (2026-09-15)
|
|
477
|
+
|
|
478
|
+
"Cooperative wrap-up grace": a configurable, manifest-gated SIGTERM→SIGKILL grace threaded to the round leader's actual reap deadline (a cooperative agent like `gemini` gets `[agent] sigterm_grace_s`, default 10 s; `claude` keeps 5 s). Same macOS harness. <!-- authored: default sigterm_grace_s / REAP_GRACE_S; SSOT agent_runner/config/models.py, agent_runner/agent_runtime.py -->
|
|
479
|
+
|
|
480
|
+
### 1. Import/startup RSS
|
|
481
|
+
|
|
482
|
+
| | 0.3.4 | 0.3.5 | Δ |
|
|
483
|
+
|---|---|---|---|
|
|
484
|
+
| RSS (avg of 5 cold runs) | 24.0 MB | 24.1 MB | ~0 MB (flat, within noise) |
|
|
485
|
+
| RSS range | 23.95–24.12 MB | 23.91–24.25 MB | |
|
|
486
|
+
|
|
487
|
+
Flat: the change is a config field, a pure grace-resolution function, an env round-trip, and two observability fields — no new module on the cold-startup graph, no new dependency (base stays `psutil>=5.9`). `test_import_footprint.py` + `test_round_alloc_growth.py` green.
|
|
488
|
+
|
|
489
|
+
### 2. Efficiency
|
|
490
|
+
|
|
491
|
+
No new internal wait — the grace window rides the v0.3.4 `wait_exit` deadline (zero polling during the grace), so a 12 s cooperative grace costs zero wakeups, same as the old 5 s. Two structural wins rather than a headline number:
|
|
492
|
+
- **One grace code path, not two.** The reap deadline is now a single threaded value (defaulting to `REAP_GRACE_S`) instead of a hardcoded constant plus a would-be cooperative branch — the branch was never created, closing the v0.3.3 dark-code shape at the source.
|
|
493
|
+
- **No wasted force-kills.** A cooperative agent that finishes flushing within its grace is reaped normally instead of SIGKILLed mid-write — work that the old fixed 5 s could truncate now completes. The behavioral efficiency win, measured by the wall-clock PROPERTY test (`test_cooperative_grace_property.py`), not RSS.
|
|
494
|
+
|
|
495
|
+
### 3. Constrained-host (honesty)
|
|
496
|
+
|
|
497
|
+
Dev-host-relative (macOS). The grace's real value shows on the constrained fleet (a `gemini` round flushing under memory pressure before an early-SIGTERM nudge) — that nudge is the 462 MB-constrained-host-gated v0.3.7 work; this release ships only the correct, configurable grace it will use. No new constrained-host claim.
|
|
498
|
+
|
|
476
499
|
## Enforcement
|
|
477
500
|
|
|
478
501
|
Four invariant tests keep these numbers from drifting silently:
|
|
@@ -188,7 +188,7 @@ The round itself continues — a broken plugin must not crash the supervisor.
|
|
|
188
188
|
|
|
189
189
|
```json
|
|
190
190
|
{
|
|
191
|
-
"schema_version": "2.
|
|
191
|
+
"schema_version": "2.4",
|
|
192
192
|
"plugins": {
|
|
193
193
|
"event_kinds": [...],
|
|
194
194
|
"context_enrichers": ["current_branch"],
|
|
@@ -550,7 +550,7 @@ detector. Other plugin detectors and all builtins still run normally.
|
|
|
550
550
|
|
|
551
551
|
```json
|
|
552
552
|
{
|
|
553
|
-
"schema_version": "2.
|
|
553
|
+
"schema_version": "2.4",
|
|
554
554
|
"plugins": {
|
|
555
555
|
"event_kinds": [...],
|
|
556
556
|
"context_enrichers": [...],
|
|
@@ -221,7 +221,15 @@ def test_kill_should_reap_round_and_agent_pgroup_when_using_holder_sidecar(
|
|
|
221
221
|
|
|
222
222
|
assert result.active is False
|
|
223
223
|
round_proc.wait(timeout=20) # own child: an OS condition, not an event
|
|
224
|
-
|
|
224
|
+
# Poll, don't assert-immediately: the agent (a grandchild reparented to
|
|
225
|
+
# init once the leader is reaped) is SIGKILLed by the holder-sidecar reap,
|
|
226
|
+
# but init reaps its zombie asynchronously -- a sub-30ms window where
|
|
227
|
+
# pid_alive() still reads the reaping pid as alive (surfaced as a flake
|
|
228
|
+
# only under CI load). A genuinely orphaned agent rides out its lifetime,
|
|
229
|
+
# so this poll fails after the timeout for the real bug.
|
|
230
|
+
assert poll_until(lambda: not pid_alive(agent_pid[0]), timeout_s=10), (
|
|
231
|
+
"the round's agent was orphaned by kill()"
|
|
232
|
+
)
|
|
225
233
|
assert serve_stub.poll() is not None # the serve stand-in was reaped too
|
|
226
234
|
finally:
|
|
227
235
|
for p in (round_proc, serve_stub):
|