cli-agent-runner 0.3.12__tar.gz → 0.3.13__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.13/.ralph/items.json +86 -0
- cli_agent_runner-0.3.13/.ralph/progress.md +47 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/CHANGELOG.md +13 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/PKG-INFO +1 -1
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/SECURITY.md +6 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_emit/memory.py +27 -3
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/_serve_cgroup.py +9 -2
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/metrics.py +28 -12
- cli_agent_runner-0.3.13/tests/_clock.py +95 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/conftest.py +295 -175
- cli_agent_runner-0.3.13/tests/e2e/growth_child.py +28 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/test_e2e_pre_oom.py +179 -19
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_spawn_round_mem_floor.py +26 -50
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/linux/test_memory_high_nudge_e2e.py +4 -4
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cgroup_probe.py +30 -0
- cli_agent_runner-0.3.13/tests/unit/test_growth_child_constants.py +8 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_host_health.py +20 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_metrics.py +5 -2
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_round_cgroup_spine.py +60 -32
- cli_agent_runner-0.3.12/tests/_clock.py +0 -53
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.codecov.yml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/dependabot.yml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.gitignore +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/.vulture-whitelist.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/BACKLOG.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/LICENSE +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/README.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/README.zh.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_bounded.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_emit/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_emit/agent.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_emit/rounds.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_emit/serve.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_install.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_lifecycle.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_monitor_state.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_notify.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_observe.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_plugin_scan.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_procwait.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_round_outcome.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_serve_policy.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/agent_runtime.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/api.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/_serve_round.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/doctor_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/serve_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/config/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/config/loader.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/config/models.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/config/parsers.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/config/validators.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/event_log.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/events.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/goal.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/host_health.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/phase_select.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/runner.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/startup_check.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/build.sh +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/deploy/Dockerfile +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/deploy/docker-compose.yml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/README.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/architecture.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/commands.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/configuration.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/events.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/memory-efficiency.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/migrations/0.2.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/migrations/0.3.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/plugins.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/recipes/container-pi.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/recipes/deploy-all-in-one.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/runbook.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/docs/thesis.md +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/pyproject.toml +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/conftest.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/contract/test_public_api_surface.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/test_e2e_pi_resume.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_auto_stop_e2e.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_cooperative_grace_property.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_cooperative_stop_signal.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_goal_steering_e2e.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_reap_property.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_spawn_round_wedged.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_architecture.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_bdd_test_conventions.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_brake_defaults_off.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_container_stop_bound.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_goal_firewall.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_hard_wall_signal.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_import_footprint.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_asyncio_select.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_direct_inspect_import.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_price_logic.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_vacuous_invariants.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_no_version_refs.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_procwait_shield.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_round_alloc_growth.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_throttle_stateless_imports.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_timeout_budget_invariant.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/linux/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime_container.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime_reap.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_apply_reap_grace_env.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_apply_resume_env.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_nested_strictness.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_resume_guard.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_cooperative_signal_env.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_doctor_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_event_log.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_events.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_exec_prefix.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_git_timeout.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_goal_assessor.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_goal_executor.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_goal_firewall_behavioral.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_goal_ledger.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_http_progress_epoch.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_kill_pgroup_shielding.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_loader_split.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_metrics_cgroup_usage.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_metrics_swap.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_dedup_rearm.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_loop.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_on_alert.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_notify.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_phase_select.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_plugin_manifest.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_plugin_scan_parity.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_plugins_config_fields.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_procwait.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_resolve_dirty_tree.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_round_outcome.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_round_outcome_agent_axis.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_run_bounded.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_policy.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_round.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_round_oom_killed.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_round_terminate.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_round_wait_exit.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_serve_wakeup.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_startup_check.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_terminate_round_wait_exit.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_throttle_epoch_bound.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.3.12 → cli_agent_runner-0.3.13}/tests/unit/test_vcs_state.py +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"runtime_contract": {
|
|
4
|
+
"source_docs": [],
|
|
5
|
+
"verification_gates": [
|
|
6
|
+
{
|
|
7
|
+
"name": "ruff",
|
|
8
|
+
"command": ".venv/bin/ruff check . && .venv/bin/ruff format --check ."
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"require_progress_append": true,
|
|
12
|
+
"require_one_item_per_iteration": true,
|
|
13
|
+
"require_commit": true
|
|
14
|
+
},
|
|
15
|
+
"items": [
|
|
16
|
+
{
|
|
17
|
+
"category": "functional",
|
|
18
|
+
"description": "metrics.sample() always includes psi_full_total, io_psi_some_avg10, and io_psi_full_avg10 (None when unread). _read_psi returns (some_avg10, full_avg10, full_total). host_health.memory_pressure verdict and context stay identical when those extra keys are injected.",
|
|
19
|
+
"steps": [
|
|
20
|
+
"Add or update tests in tests/unit/test_metrics.py for the triple return and the three sample keys.",
|
|
21
|
+
"Add tests/unit/test_host_health.py coverage that a PSI-healthy sample plus huge io_psi_* / psi_full_total yields the same Pressure as the sample without those keys.",
|
|
22
|
+
"Implement parsing of full-line total= and a second _read_psi call on /proc/pressure/io inside sample(); do not edit host_health.py.",
|
|
23
|
+
"Run .venv/bin/python -m pytest tests/unit/test_metrics.py tests/unit/test_host_health.py tests/unit/test_monitor_detectors.py -q and the ruff gate."
|
|
24
|
+
],
|
|
25
|
+
"passes": true,
|
|
26
|
+
"regression_notes": "76 passed (test_metrics + test_host_health + test_monitor_detectors); ruff check+format clean. host_health.py untouched. Darwin sample keys present as None."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"category": "functional",
|
|
30
|
+
"description": "host_cgroup_memory_limit events carry defer as a bool equal to _probe_and_emit_cgroup_defer's return value (True when both caps are finite and plausible, including MemorySwapMax=0).",
|
|
31
|
+
"steps": [
|
|
32
|
+
"Add tests/unit/test_cgroup_probe.py cases: memory_swap_max=0 with finite memory_max under host RAM returns True and emits defer true; memory_swap_max missing returns False and emits defer false.",
|
|
33
|
+
"Add defer: bool to emit_host_cgroup_memory_limit and pass the already-computed defer flag from _probe_and_emit_cgroup_defer.",
|
|
34
|
+
"Point the existing tests/e2e/test_e2e_pre_oom.py diagnostic at host_cgroup_memory_limit.defer.",
|
|
35
|
+
"Run .venv/bin/python -m pytest tests/unit/test_cgroup_probe.py -q and the ruff gate."
|
|
36
|
+
],
|
|
37
|
+
"passes": true,
|
|
38
|
+
"regression_notes": "55 passed (test_cgroup_probe); ruff check+format clean. MemorySwapMax=0 emits defer true; missing swap.max emits defer false. D-2 diagnostic names host_cgroup_memory_limit.defer."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"category": "functional",
|
|
42
|
+
"description": "round_cgroup_memory may carry optional flat io_psi_some_avg10, io_psi_full_avg10, and psi_full_total from metrics.sample() at emit time. They are omitted when unread. They are never copied into Pressure.context.",
|
|
43
|
+
"steps": [
|
|
44
|
+
"Extend tests/unit/test_serve_round_cgroup_spine.py to monkeypatch metrics.sample and assert the three fields on the emitted round_cgroup_memory event.",
|
|
45
|
+
"Thread optional kwargs through emit_round_cgroup_memory and _emit_round_cgroup_memory; call sample() once at emit.",
|
|
46
|
+
"Run .venv/bin/python -m pytest tests/unit/test_serve_round_cgroup_spine.py tests/unit/test_serve_round_oom_killed.py -q and the ruff gate."
|
|
47
|
+
],
|
|
48
|
+
"passes": true,
|
|
49
|
+
"regression_notes": "19 passed (test_serve_round_cgroup_spine + test_serve_round_oom_killed); ruff check+format clean. Optional PSI fields omitted when unread/None; present when sample() read them. Not copied into Pressure.context."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"category": "functional",
|
|
53
|
+
"description": "The paced pre-OOM child lives in tests/e2e/growth_child.py (8 MiB every 2.5s, page-touch). pi_growth_script installs that file instead of an inlined string. Importing the module does not start the infinite loop.",
|
|
54
|
+
"steps": [
|
|
55
|
+
"Create tests/e2e/growth_child.py with the existing constants and main() behind if __name__.",
|
|
56
|
+
"Add a constants test that imports the module and asserts _CHUNK_BYTES and _PACE_S.",
|
|
57
|
+
"Change tests/e2e/conftest.py pi_growth_script to ship that file.",
|
|
58
|
+
"Run the constants test plus the ruff gate."
|
|
59
|
+
],
|
|
60
|
+
"passes": true,
|
|
61
|
+
"regression_notes": "1 passed (test_growth_child_constants); ruff check+format clean. pi_growth_script ships tests/e2e/growth_child.py via base64; import does not start the loop."
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"category": "functional",
|
|
65
|
+
"description": "A skip-closed control-arm e2e exists: same unit as treatment except MemorySwapMax=0, MemoryHigh=120M kept, no oom.group and no OOMPolicy=kill. Unset AGENT_RUNNER_E2E_CGROUP and AGENT_RUNNER_E2E_PI, the new test skips. Assertions in source require defer true, no round_mem_terminated, no memory_high_engaged, events_oom_kill_delta>=1 or INVALID, DEFER-FIRST or KERNEL-FIRST named, growth child gone, host still answers.",
|
|
66
|
+
"steps": [
|
|
67
|
+
"Factor the systemd unit body so swap max is a parameter. Control uses 0. Do not add OOMPolicy or memory.oom.group.",
|
|
68
|
+
"Add the control-arm test and shared assertions. Gate with existing PI flag and/or AGENT_RUNNER_E2E_CGROUP. Control wait ceiling 180s.",
|
|
69
|
+
"Without those env flags, run .venv/bin/python -m pytest tests/e2e/test_e2e_pre_oom.py -q and confirm skip (not fail, not pass). Do not export the e2e flags. Run the ruff gate."
|
|
70
|
+
],
|
|
71
|
+
"passes": true,
|
|
72
|
+
"regression_notes": "3 skipped (test_e2e_pre_oom.py without AGENT_RUNNER_E2E_CGROUP/PI); ruff check+format clean. Control unit MemorySwapMax=0, MemoryHigh=120M kept, no oom.group/OOMPolicy. Assertions in source: defer true, no round_mem_terminated, no memory_high_engaged, events_oom_kill_delta>=1 or INVALID, DEFER-FIRST or KERNEL-FIRST named, growth child gone, host still answers."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"category": "functional",
|
|
76
|
+
"description": "Root SECURITY.md gains one paragraph: isolation is the operator's job (exec_prefix, dedicated user, no passwordless sudo, egress); agent-runner stays lifecycle-safety. CHANGELOG adds ## [0.3.13] - UNRELEASED. No docs/SECURITY.md. Optional results template under gitignored docs/internal/notes is allowed and must not be staged.",
|
|
77
|
+
"steps": [
|
|
78
|
+
"Edit SECURITY.md Containment or Threat model with the reframe paragraph. Keep existing recipe links.",
|
|
79
|
+
"Insert CHANGELOG ## [0.3.13] - UNRELEASED above 0.3.12. Do not claim uncapped-before-coma.",
|
|
80
|
+
"Confirm docs/SECURITY.md does not exist. Run tests/invariants/test_docs_index_complete.py tests/invariants/test_doc_claims_match_ssot.py and the ruff gate."
|
|
81
|
+
],
|
|
82
|
+
"passes": true,
|
|
83
|
+
"regression_notes": "4 passed (test_docs_index_complete + test_doc_claims_match_ssot); ruff check+format clean. docs/SECURITY.md absent. CHANGELOG [0.3.13] UNRELEASED; no uncapped-before-coma claim. Results template gitignored, not staged."
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
## 2026-09-18 sample() IO-PSI + psi_full_total
|
|
2
|
+
|
|
3
|
+
- item: metrics.sample() always includes psi_full_total, io_psi_some_avg10, and io_psi_full_avg10 (None when unread). _read_psi returns (some_avg10, full_avg10, full_total). host_health.memory_pressure verdict and context stay identical when those extra keys are injected.
|
|
4
|
+
- rationale: plan prefers ladder-blindness before defer-field / e2e fixture work. First unfinished item.
|
|
5
|
+
- files: agent_runner/metrics.py, tests/unit/test_metrics.py, tests/unit/test_host_health.py
|
|
6
|
+
- verification: `.venv/bin/python -m pytest tests/unit/test_metrics.py tests/unit/test_host_health.py tests/unit/test_monitor_detectors.py -q` → 76 passed; `.venv/bin/ruff check . && .venv/bin/ruff format --check .` clean
|
|
7
|
+
- notes: did not edit host_health.py or _NEW_MEM_SAMPLE_KEYS. Pre-existing pi-lens findings in cgroup_delegated / _brake_high_value left untouched (outside this item).
|
|
8
|
+
|
|
9
|
+
## 2026-09-18 host_cgroup_memory_limit.defer bool
|
|
10
|
+
|
|
11
|
+
- item: host_cgroup_memory_limit events carry defer as a bool equal to _probe_and_emit_cgroup_defer's return value (True when both caps are finite and plausible, including MemorySwapMax=0).
|
|
12
|
+
- rationale: plan prefers locking the defer-field contract before e2e fixture work. First remaining unfinished item after sample() IO-PSI.
|
|
13
|
+
- files: agent_runner/_emit/memory.py, agent_runner/cli/_serve_cgroup.py, tests/unit/test_cgroup_probe.py, tests/e2e/test_e2e_pre_oom.py
|
|
14
|
+
- verification: `.venv/bin/python -m pytest tests/unit/test_cgroup_probe.py -q` → 55 passed; `.venv/bin/ruff check . && .venv/bin/ruff format --check .` clean
|
|
15
|
+
- notes: defer is a required emit kwarg (every probe is yes/no). Did not touch runbook.md (docs item). Pre-existing pi-lens typing findings in _serve_cgroup left untouched.
|
|
16
|
+
|
|
17
|
+
## 2026-09-18 round_cgroup_memory corroborating PSI fields
|
|
18
|
+
|
|
19
|
+
- item: round_cgroup_memory may carry optional flat io_psi_some_avg10, io_psi_full_avg10, and psi_full_total from metrics.sample() at emit time. They are omitted when unread. They are never copied into Pressure.context.
|
|
20
|
+
- rationale: plan prefers locking ladder-blindness and defer-field contracts before e2e fixture work. First remaining unfinished item after host_cgroup_memory_limit.defer.
|
|
21
|
+
- files: agent_runner/_emit/memory.py, agent_runner/cli/_serve_cgroup.py, tests/unit/test_serve_round_cgroup_spine.py
|
|
22
|
+
- verification: `.venv/bin/python -m pytest tests/unit/test_serve_round_cgroup_spine.py tests/unit/test_serve_round_oom_killed.py -q` → 19 passed; `.venv/bin/ruff check . && .venv/bin/ruff format --check .` clean
|
|
23
|
+
- notes: assumed sample() runs only on the successful emit path (skip-no-op paths do not sample); each None key is omitted independently. Did not edit host_health.py, events.md, or runbook.md. Pre-existing pi-lens typing findings in _serve_cgroup left untouched.
|
|
24
|
+
|
|
25
|
+
## 2026-09-18 extract growth_child.py
|
|
26
|
+
|
|
27
|
+
- item: The paced pre-OOM child lives in tests/e2e/growth_child.py (8 MiB every 2.5s, page-touch). pi_growth_script installs that file instead of an inlined string. Importing the module does not start the infinite loop.
|
|
28
|
+
- rationale: plan prefers extracting growth_child.py after ladder-blindness/defer-field contracts, before skip-closed control tests. First remaining unfinished item.
|
|
29
|
+
- files: tests/e2e/growth_child.py, tests/e2e/conftest.py, tests/unit/test_growth_child_constants.py
|
|
30
|
+
- verification: `.venv/bin/python -m pytest tests/unit/test_growth_child_constants.py -q` → 1 passed; `.venv/bin/ruff check . && .venv/bin/ruff format --check .` clean
|
|
31
|
+
- notes: assumed the constants test lives under tests/unit because CI and build.sh --ignore=tests/e2e. pi_growth_script still base64-over-ssh (same install path), reading tests/e2e/growth_child.py bytes instead of an inlined string. Importing the module does not enter main().
|
|
32
|
+
|
|
33
|
+
## 2026-09-18 skip-closed control-arm e2e
|
|
34
|
+
|
|
35
|
+
- item: A skip-closed control-arm e2e exists: same unit as treatment except MemorySwapMax=0, MemoryHigh=120M kept, no oom.group and no OOMPolicy=kill. Unset AGENT_RUNNER_E2E_CGROUP and AGENT_RUNNER_E2E_PI, the new test skips. Assertions in source require defer true, no round_mem_terminated, no memory_high_engaged, events_oom_kill_delta>=1 or INVALID, DEFER-FIRST or KERNEL-FIRST named, growth child gone, host still answers.
|
|
36
|
+
- rationale: plan prefers skip-closed control tests after extracting growth_child.py. First remaining unfinished item.
|
|
37
|
+
- files: tests/e2e/conftest.py, tests/e2e/test_e2e_pre_oom.py
|
|
38
|
+
- verification: `.venv/bin/python -m pytest tests/e2e/test_e2e_pre_oom.py -q` → 3 skipped; `.venv/bin/ruff check . && .venv/bin/ruff format --check .` clean
|
|
39
|
+
- notes: assumed PI control pytest timeout includes +600 install budget matching treatment; local uses +120 per plan. Two thin wrappers (PI + local CGROUP) share `_assert_control`. Skip-closed (unset env => skip) is the honest local proof; did not export e2e flags or run sudo/systemctl. Did not add OOMPolicy or memory.oom.group.
|
|
40
|
+
|
|
41
|
+
## 2026-09-18 SECURITY reframe + CHANGELOG stub
|
|
42
|
+
|
|
43
|
+
- item: Root SECURITY.md gains one paragraph: isolation is the operator's job (exec_prefix, dedicated user, no passwordless sudo, egress); agent-runner stays lifecycle-safety. CHANGELOG adds ## [0.3.13] - UNRELEASED. No docs/SECURITY.md. Optional results template under gitignored docs/internal/notes is allowed and must not be staged.
|
|
44
|
+
- rationale: plan says docs last. Last remaining unfinished item.
|
|
45
|
+
- files: SECURITY.md, CHANGELOG.md
|
|
46
|
+
- verification: `.venv/bin/python -m pytest tests/invariants/test_docs_index_complete.py tests/invariants/test_doc_claims_match_ssot.py -q` → 4 passed; `.venv/bin/ruff check . && .venv/bin/ruff format --check .` clean; `docs/SECURITY.md` absent
|
|
47
|
+
- notes: assumed not naming a competitor in SECURITY.md (Ralph item asks for the operator-vs-lifecycle split). Did not edit runbook.md (not in the Ralph item). Results template written under gitignored docs/internal/notes and not staged. Did not claim uncapped-before-coma or a live two-arm run.
|
|
@@ -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.13] - 2026-09-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Two production cgroup modes are now checkable on the same paced growth child: treatment (`MemorySwapMax` infinity) still cooperatively terminates and reaps; control (`MemorySwapMax=0`) defers to kernel cgroup-OOM of the capped child. Both leave the host up. The uncapped-before-coma case stays inferred.
|
|
12
|
+
- `metrics.sample()` always includes corroborating `psi_full_total`, `io_psi_some_avg10`, and `io_psi_full_avg10` (`None` when unread). They are not kill rungs; `host_health.memory_pressure` stays blind to them.
|
|
13
|
+
- `host_cgroup_memory_limit` carries `defer` as a bool (`True` when both caps are finite and plausible, including `MemorySwapMax=0`).
|
|
14
|
+
- `round_cgroup_memory` may carry those PSI fields as optional flat keys at emit time; omitted when unread.
|
|
15
|
+
|
|
16
|
+
### Docs
|
|
17
|
+
- SECURITY.md: isolation is the operator's job (`exec_prefix`, dedicated user, no passwordless sudo, egress); agent-runner stays lifecycle-safety.
|
|
18
|
+
|
|
19
|
+
No config migration required. `peek --json` schema stays `2.6`.
|
|
20
|
+
|
|
8
21
|
## [0.3.12] - 2026-09-18
|
|
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.13
|
|
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
|
|
@@ -37,6 +37,12 @@ unprivileged user, inside a container or VM, without passwordless sudo, and
|
|
|
37
37
|
with egress limits. The supervisor's defenses bound the blast radius of a
|
|
38
38
|
*misbehaving* agent, not a *hostile* one.
|
|
39
39
|
|
|
40
|
+
Isolation is the operator's job. Isolation-enforcing supervisors exist;
|
|
41
|
+
agent-runner deliberately is not one of them. Lifecycle-safety stays ours
|
|
42
|
+
(round timeouts, process-group reaping, orphan stashing, auto-stop on
|
|
43
|
+
critical alerts). Containment stays yours: `[agent] exec_prefix`, a dedicated
|
|
44
|
+
unprivileged user, no passwordless sudo, and egress limits.
|
|
45
|
+
|
|
40
46
|
## Containment
|
|
41
47
|
|
|
42
48
|
If you need to bound what the agent can reach, run it inside a container (or
|
|
@@ -112,6 +112,7 @@ def emit_host_cgroup_memory_limit(
|
|
|
112
112
|
memory_max: int | None,
|
|
113
113
|
memory_swap_max: int | None,
|
|
114
114
|
cgroup_path: str | None,
|
|
115
|
+
defer: bool,
|
|
115
116
|
swap_total_bytes: int | None = None,
|
|
116
117
|
swap_cap_pct: float | None = None,
|
|
117
118
|
memory_high: int | None = None,
|
|
@@ -120,8 +121,11 @@ def emit_host_cgroup_memory_limit(
|
|
|
120
121
|
) -> None:
|
|
121
122
|
"""Emit once at serve startup: this process's cgroup v2 memory budget
|
|
122
123
|
(``metrics.cgroup_memory_limits``). ``None`` fields mean unlimited (or
|
|
123
|
-
cgroup v2 unavailable).
|
|
124
|
-
|
|
124
|
+
cgroup v2 unavailable). ``defer`` is the already-computed mid-round-floor
|
|
125
|
+
decision (True when both caps are finite and plausible, including
|
|
126
|
+
``MemorySwapMax=0``) -- the event carries that yes/no, not just the
|
|
127
|
+
inputs. Serve uses this once to decide whether the mid-round hard floor
|
|
128
|
+
can defer to kernel cgroup-OOM -- see
|
|
125
129
|
``emit_mem_pressure_deferred_to_cgroup`` below.
|
|
126
130
|
|
|
127
131
|
This also carries an optional startup advisory
|
|
@@ -152,6 +156,7 @@ def emit_host_cgroup_memory_limit(
|
|
|
152
156
|
memory_max=memory_max,
|
|
153
157
|
memory_swap_max=memory_swap_max,
|
|
154
158
|
cgroup_path=cgroup_path,
|
|
159
|
+
defer=defer,
|
|
155
160
|
swap_total_bytes=swap_total_bytes,
|
|
156
161
|
swap_cap_pct=swap_cap_pct,
|
|
157
162
|
memory_high=memory_high,
|
|
@@ -171,6 +176,9 @@ def emit_round_cgroup_memory(
|
|
|
171
176
|
events_oom_delta: int,
|
|
172
177
|
events_oom_kill_delta: int,
|
|
173
178
|
bounding_cgroup_path: str | None,
|
|
179
|
+
io_psi_some_avg10: float | None = None,
|
|
180
|
+
io_psi_full_avg10: float | None = None,
|
|
181
|
+
psi_full_total: float | int | None = None,
|
|
174
182
|
) -> None:
|
|
175
183
|
"""Emit once per round: the round's peak cgroup ``memory.current`` (+swap) and
|
|
176
184
|
the ``memory.events`` DELTAS (high/max/oom/oom_kill) over the round, read at the
|
|
@@ -185,9 +193,24 @@ def emit_round_cgroup_memory(
|
|
|
185
193
|
process's own) cgroup; here it names the BOUNDING ANCESTOR (the one whose
|
|
186
194
|
``memory.max`` actually binds). Same key, two meanings across the two
|
|
187
195
|
events an operator correlates would be a trap -- events.md pins field
|
|
188
|
-
semantics permanently once shipped, so the distinct name is chosen now.
|
|
196
|
+
semantics permanently once shipped, so the distinct name is chosen now.
|
|
197
|
+
|
|
198
|
+
Optional corroborating PSI fields (``io_psi_some_avg10``,
|
|
199
|
+
``io_psi_full_avg10``, ``psi_full_total``) come from ``metrics.sample()``
|
|
200
|
+
at emit time. Each is omitted when unread (``None``). They ride FLAT on
|
|
201
|
+
this event -- never copied into ``Pressure.context``, so the kill ladder
|
|
202
|
+
stays blind to them."""
|
|
189
203
|
from agent_runner.events import ROUND_CGROUP_MEMORY, emit
|
|
190
204
|
|
|
205
|
+
extra = {
|
|
206
|
+
k: v
|
|
207
|
+
for k, v in (
|
|
208
|
+
("io_psi_some_avg10", io_psi_some_avg10),
|
|
209
|
+
("io_psi_full_avg10", io_psi_full_avg10),
|
|
210
|
+
("psi_full_total", psi_full_total),
|
|
211
|
+
)
|
|
212
|
+
if v is not None
|
|
213
|
+
}
|
|
191
214
|
emit(
|
|
192
215
|
log_dir,
|
|
193
216
|
ROUND_CGROUP_MEMORY,
|
|
@@ -199,6 +222,7 @@ def emit_round_cgroup_memory(
|
|
|
199
222
|
events_oom_delta=events_oom_delta,
|
|
200
223
|
events_oom_kill_delta=events_oom_kill_delta,
|
|
201
224
|
bounding_cgroup_path=bounding_cgroup_path,
|
|
225
|
+
**extra,
|
|
202
226
|
)
|
|
203
227
|
|
|
204
228
|
|
|
@@ -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.13'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 13)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -128,6 +128,8 @@ def _emit_round_cgroup_memory(
|
|
|
128
128
|
def _delta(key: str) -> int:
|
|
129
129
|
return max(0, cur_ev.get(key, 0) - base_ev.get(key, 0))
|
|
130
130
|
|
|
131
|
+
# One sample() at emit -- corroborating IO/mem-PSI, omitted when unread.
|
|
132
|
+
s = metrics.sample()
|
|
131
133
|
emit_round_cgroup_memory(
|
|
132
134
|
log_dir,
|
|
133
135
|
round_num=round_num,
|
|
@@ -138,6 +140,9 @@ def _emit_round_cgroup_memory(
|
|
|
138
140
|
events_oom_delta=_delta("oom"),
|
|
139
141
|
events_oom_kill_delta=_delta("oom_kill"),
|
|
140
142
|
bounding_cgroup_path=state["bounding_cgroup_path"],
|
|
143
|
+
io_psi_some_avg10=s.get("io_psi_some_avg10"),
|
|
144
|
+
io_psi_full_avg10=s.get("io_psi_full_avg10"),
|
|
145
|
+
psi_full_total=s.get("psi_full_total"),
|
|
141
146
|
)
|
|
142
147
|
return cur, oom_kill_baseline
|
|
143
148
|
|
|
@@ -277,8 +282,9 @@ def _release_brake(log_dir: Path, round_num: int, previous: str, *, reason: str)
|
|
|
277
282
|
|
|
278
283
|
def _probe_and_emit_cgroup_defer(log_dir: Path, *, brake_memory_high: bool = False) -> bool:
|
|
279
284
|
"""Probe this process's cgroup v2 memory budget once at serve startup,
|
|
280
|
-
emit host_cgroup_memory_limit
|
|
281
|
-
|
|
285
|
+
emit host_cgroup_memory_limit (including the ``defer`` decision, not just
|
|
286
|
+
the inputs) for observability, and return whether the mid-round hard
|
|
287
|
+
floor should defer to kernel cgroup-OOM: True only when
|
|
282
288
|
BOTH memory.max and memory.swap.max are finite (the field host's
|
|
283
289
|
MemoryMax=320M + MemorySwapMax=160M shape) -- that budget is bounded end
|
|
284
290
|
to end, so cgroup-OOM WILL fire and contain the agent while the host
|
|
@@ -401,6 +407,7 @@ def _probe_and_emit_cgroup_defer(log_dir: Path, *, brake_memory_high: bool = Fal
|
|
|
401
407
|
memory_max=limits["memory_max"],
|
|
402
408
|
memory_swap_max=swap_max,
|
|
403
409
|
cgroup_path=limits["cgroup_path"],
|
|
410
|
+
defer=defer,
|
|
404
411
|
swap_total_bytes=swap_total,
|
|
405
412
|
swap_cap_pct=swap_cap_pct,
|
|
406
413
|
memory_high=memory_high,
|
|
@@ -17,14 +17,18 @@ from agent_runner.clock import SYSTEM_CLOCK
|
|
|
17
17
|
from agent_runner.events import now_iso_ms
|
|
18
18
|
|
|
19
19
|
_PSI_MEMORY_PATH = Path("/proc/pressure/memory")
|
|
20
|
+
_PSI_IO_PATH = Path("/proc/pressure/io")
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
def _read_psi(path: Path = _PSI_MEMORY_PATH) -> tuple[float, float] | None:
|
|
23
|
-
"""Parse
|
|
23
|
+
def _read_psi(path: Path = _PSI_MEMORY_PATH) -> tuple[float, float, int | None] | None:
|
|
24
|
+
"""Parse a PSI file's ``some``/``full`` ``avg10`` and full-line ``total``.
|
|
24
25
|
|
|
25
|
-
Returns ``
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
Returns ``(some_avg10, full_avg10, full_total)``. ``full_avg10`` is ``0.0``
|
|
27
|
+
when the full line is missing; ``full_total`` is ``int(total=)`` from the
|
|
28
|
+
full line, else ``None``. Returns ``None`` when the file is absent
|
|
29
|
+
(non-Linux, or a kernel built without ``CONFIG_PSI``) or unreadable
|
|
30
|
+
(``psi=0`` boot param) — the caller (``host_health``) degrades
|
|
31
|
+
gracefully down the signal ladder.
|
|
28
32
|
"""
|
|
29
33
|
try:
|
|
30
34
|
text = path.read_text(encoding="utf-8")
|
|
@@ -32,11 +36,17 @@ def _read_psi(path: Path = _PSI_MEMORY_PATH) -> tuple[float, float] | None:
|
|
|
32
36
|
return None
|
|
33
37
|
some_avg10: float | None = None
|
|
34
38
|
full_avg10: float | None = None
|
|
39
|
+
full_total: int | None = None
|
|
35
40
|
for line in text.splitlines():
|
|
36
41
|
parts = line.split()
|
|
37
42
|
if not parts:
|
|
38
43
|
continue
|
|
39
44
|
fields = dict(p.split("=", 1) for p in parts[1:] if "=" in p)
|
|
45
|
+
if parts[0] == "full":
|
|
46
|
+
try:
|
|
47
|
+
full_total = int(fields["total"])
|
|
48
|
+
except (KeyError, ValueError):
|
|
49
|
+
pass
|
|
40
50
|
raw = fields.get("avg10")
|
|
41
51
|
if raw is None:
|
|
42
52
|
continue
|
|
@@ -50,7 +60,7 @@ def _read_psi(path: Path = _PSI_MEMORY_PATH) -> tuple[float, float] | None:
|
|
|
50
60
|
full_avg10 = value
|
|
51
61
|
if some_avg10 is None:
|
|
52
62
|
return None
|
|
53
|
-
return some_avg10, full_avg10 if full_avg10 is not None else 0.0
|
|
63
|
+
return some_avg10, full_avg10 if full_avg10 is not None else 0.0, full_total
|
|
54
64
|
|
|
55
65
|
|
|
56
66
|
def sample() -> dict[str, Any]:
|
|
@@ -58,22 +68,28 @@ def sample() -> dict[str, Any]:
|
|
|
58
68
|
|
|
59
69
|
Deliberately NOT ``collect()`` (which shells out to ``pgrep`` for
|
|
60
70
|
``agent_process_count``, see ``_count_agent_processes`` below) — this is
|
|
61
|
-
psutil counters +
|
|
62
|
-
~10s in a hot loop (e.g. a serve-loop mid-round check).
|
|
63
|
-
is the pure interpreter of what this returns; this
|
|
71
|
+
psutil counters + optional ``/proc/pressure/{memory,io}`` reads, safe to
|
|
72
|
+
call every ~10s in a hot loop (e.g. a serve-loop mid-round check).
|
|
73
|
+
``host_health`` is the pure interpreter of what this returns; this
|
|
74
|
+
function only samples. IO-PSI and mem-PSI ``total`` are corroborating
|
|
75
|
+
fields the ladder ignores.
|
|
64
76
|
|
|
65
77
|
``swap_sout`` is cumulative (bytes swapped out since boot) — callers
|
|
66
78
|
wanting a rate/delta diff two samples themselves.
|
|
67
79
|
"""
|
|
68
80
|
vm = psutil.virtual_memory()
|
|
69
81
|
swap = psutil.swap_memory()
|
|
70
|
-
|
|
82
|
+
mem = _read_psi() or (None, None, None)
|
|
83
|
+
io_some, io_full, _ = _read_psi(_PSI_IO_PATH) or (None, None, None)
|
|
71
84
|
return {
|
|
72
85
|
"mem_available_mb": vm.available // (1024 * 1024),
|
|
73
86
|
"mem_free_mb": vm.free // (1024 * 1024),
|
|
74
87
|
"swap_sout": swap.sout,
|
|
75
|
-
"psi_some_avg10":
|
|
76
|
-
"psi_full_avg10":
|
|
88
|
+
"psi_some_avg10": mem[0],
|
|
89
|
+
"psi_full_avg10": mem[1],
|
|
90
|
+
"psi_full_total": mem[2],
|
|
91
|
+
"io_psi_some_avg10": io_some,
|
|
92
|
+
"io_psi_full_avg10": io_full,
|
|
77
93
|
}
|
|
78
94
|
|
|
79
95
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Test clocks — FakeClock and TickingClock, both the full Clock surface.
|
|
2
|
+
|
|
3
|
+
Lives in tests/ (not agent_runner/) so it has no production consumer to keep
|
|
4
|
+
vulture happy.
|
|
5
|
+
|
|
6
|
+
* ``FakeClock`` pins epoch, monotonic, UTC-now and tz-now together; ``sleep``
|
|
7
|
+
advances virtual time, so a chunked pause loop terminates deterministically
|
|
8
|
+
instead of needing a monkeypatched ``stop`` flag.
|
|
9
|
+
* ``TickingClock`` is for real-subprocess poll loops: ``monotonic()`` jumps
|
|
10
|
+
``step`` per call so a 10s sample interval elapses without waiting 10s;
|
|
11
|
+
``sleep()`` is a real short block so the child can actually exit. Do not
|
|
12
|
+
substitute FakeClock there — FakeClock.sleep never yields to a live child.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import time
|
|
18
|
+
from datetime import UTC, datetime
|
|
19
|
+
from zoneinfo import ZoneInfo
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FakeClock:
|
|
23
|
+
def __init__(self, epoch: float = 1_700_000_000.0, *, start: str | None = None):
|
|
24
|
+
"""``start``, when given, is an ISO-8601 UTC timestamp (e.g.
|
|
25
|
+
``"2026-09-16T00:00:00Z"``) converted to ``epoch`` -- a more readable
|
|
26
|
+
alternative to a raw epoch float for tests that pin a calendar date."""
|
|
27
|
+
if start is not None:
|
|
28
|
+
epoch = datetime.fromisoformat(start.replace("Z", "+00:00")).timestamp()
|
|
29
|
+
self._epoch = epoch
|
|
30
|
+
self._mono = 0.0
|
|
31
|
+
self.slept: list[float] = []
|
|
32
|
+
|
|
33
|
+
# --- Clock surface -------------------------------------------------------
|
|
34
|
+
def epoch(self) -> float:
|
|
35
|
+
return self._epoch
|
|
36
|
+
|
|
37
|
+
def monotonic(self) -> float:
|
|
38
|
+
return self._mono
|
|
39
|
+
|
|
40
|
+
def sleep(self, seconds: float) -> None:
|
|
41
|
+
self.slept.append(seconds)
|
|
42
|
+
self.advance(seconds) # virtual time moves so pause loops make progress
|
|
43
|
+
|
|
44
|
+
def now_utc(self) -> datetime:
|
|
45
|
+
return datetime.fromtimestamp(self._epoch, UTC)
|
|
46
|
+
|
|
47
|
+
def now_in_zone(self, tz_name: str | None) -> datetime:
|
|
48
|
+
if tz_name is None:
|
|
49
|
+
return self.now_utc().astimezone()
|
|
50
|
+
return self.now_utc().astimezone(ZoneInfo(tz_name))
|
|
51
|
+
|
|
52
|
+
# --- test controls -------------------------------------------------------
|
|
53
|
+
def advance(self, seconds: float) -> None:
|
|
54
|
+
self._epoch += seconds
|
|
55
|
+
self._mono += seconds
|
|
56
|
+
|
|
57
|
+
def warp_epoch(self, delta: float) -> None:
|
|
58
|
+
"""Shift the WALL clock only (simulates an NTP step); monotonic unmoved —
|
|
59
|
+
so a deadline measured on monotonic must be unaffected."""
|
|
60
|
+
self._epoch += delta
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TickingClock:
|
|
64
|
+
"""Clock for real-subprocess poll loops.
|
|
65
|
+
|
|
66
|
+
``monotonic()`` advances by ``step`` on every call so a ~10s sample interval
|
|
67
|
+
elapses without waiting ~10 real seconds. ``sleep()`` is a real short block
|
|
68
|
+
and does **not** advance monotonic — the poll fallback's per-tick pacing,
|
|
69
|
+
so a live child gets repeated chances to exit. Wall methods (``epoch`` /
|
|
70
|
+
``now_utc`` / ``now_in_zone``) sit on a fixed epoch so this is a full
|
|
71
|
+
``Clock``, not a two-method stub.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, step: float = 5.0, epoch: float = 1_700_000_000.0):
|
|
75
|
+
self._mono = 0.0
|
|
76
|
+
self._step = step
|
|
77
|
+
self._epoch = epoch
|
|
78
|
+
|
|
79
|
+
def epoch(self) -> float:
|
|
80
|
+
return self._epoch
|
|
81
|
+
|
|
82
|
+
def monotonic(self) -> float:
|
|
83
|
+
self._mono += self._step
|
|
84
|
+
return self._mono
|
|
85
|
+
|
|
86
|
+
def sleep(self, seconds: float) -> None:
|
|
87
|
+
time.sleep(seconds)
|
|
88
|
+
|
|
89
|
+
def now_utc(self) -> datetime:
|
|
90
|
+
return datetime.fromtimestamp(self._epoch, UTC)
|
|
91
|
+
|
|
92
|
+
def now_in_zone(self, tz_name: str | None) -> datetime:
|
|
93
|
+
if tz_name is None:
|
|
94
|
+
return self.now_utc().astimezone()
|
|
95
|
+
return self.now_utc().astimezone(ZoneInfo(tz_name))
|