cli-agent-runner 0.2.20__tar.gz → 0.2.21__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.2.20 → cli_agent_runner-0.2.21}/.vulture-whitelist.py +1 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/CHANGELOG.md +13 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/PKG-INFO +1 -1
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/SECURITY.md +20 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_version.py +2 -2
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/agent_runtime.py +33 -8
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/serve_cmd.py +13 -1
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/config/models.py +11 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/config/parsers.py +9 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/runner.py +1 -1
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/startup_check.py +41 -2
- cli_agent_runner-0.2.21/deploy/Dockerfile +24 -0
- cli_agent_runner-0.2.21/deploy/docker-compose.yml +18 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/README.md +2 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/commands.md +1 -1
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/configuration.md +5 -1
- cli_agent_runner-0.2.21/docs/recipes/container-pi.md +179 -0
- cli_agent_runner-0.2.21/docs/recipes/deploy-all-in-one.md +49 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_architecture.py +1 -0
- cli_agent_runner-0.2.21/tests/invariants/test_container_stop_bound.py +18 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime_container.py +46 -3
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config.py +41 -0
- cli_agent_runner-0.2.21/tests/unit/test_exec_prefix.py +150 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_startup_check.py +51 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.codecov.yml +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.github/workflows/ci.yml +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.github/workflows/release.yml +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/.gitignore +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/BACKLOG.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/CODE_OF_CONDUCT.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/CONTRIBUTING.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/LICENSE +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/README.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/README.zh.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_docgen.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_emit/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_emit/agent.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_emit/memory.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_emit/rounds.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_emit/serve.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_monitor_registry.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_monitor_state.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_plugin_scan.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_redact.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_registry.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_resolve.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_round_outcome.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_round_support.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_serve_policy.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_substrate.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/_throttle.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/api.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/api_types.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/_constants.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/claude_rate_limit.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/codewhale.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/gemini.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/kimi.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/builtin_plugins/pi.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/__main__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/_serve_cgroup.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/_serve_round.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/common.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/events_cmd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/init_cmd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/install_cmd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/migrate_cmd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/monitor_cmd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/peek_cmd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/round_cmd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/service_cmd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/cli/upgrade_cmd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/clock.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/config/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/config/errors.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/config/loader.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/config/validators.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/context_store.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/defenses.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/events.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/hooks.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/host_health.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/http_progress.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/lifecycle.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/metrics.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/migrations.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/monitor.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/phase_select.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/aider.toml +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/claude.toml +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/codewhale.toml +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/gemini.toml +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/kimi.toml +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/presets/pi.toml +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/prompt_loader.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/remote_relay.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/round_log.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/round_view.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/scaffold.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/schedule.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/service_unit.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/agent_runner/vcs_state.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/build.sh +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/architecture.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/events.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/long-running-agents.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/memory-efficiency.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/migrations/0.2.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/plugins.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/quickstart.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/recipes/aider.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/recipes/codewhale.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/recipes/kimi.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/recipes/pi.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/runbook.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/docs/thesis.md +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/pyproject.toml +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/_clock.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/_test_helpers.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/conftest.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/contract/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/contract/test_public_api_surface.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/e2e/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/e2e/conftest.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/e2e/test_e2e_graceful_stop.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/e2e/test_e2e_install_systemd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/e2e/test_e2e_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/e2e/test_e2e_round_lifecycle.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/fixtures/cli-real-output/claude-2.1.143-assistant-tool-use.jsonl +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/fixtures/cli-real-output/claude-2.1.143-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/fixtures/cli-real-output/gemini-0.42.0-result-event.jsonl +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/generate_vulture_whitelist.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_auto_stop_e2e.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_bounded_run.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_context_enricher_namespacing.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_dirty_handler_seam.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_fresh_eyes_signal.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_grace_kill_emission.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_install_dry_run.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_monitor_seeded.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_plugin_detector_loaded.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_plugin_owned_paths.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_plugin_real_flow.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_prompt_delivery_stdin.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_run_one_round_with_fake_agent.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_scaffold_presets.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_serve_loop.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_spawn_round_mem_floor.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_spawn_round_wedged.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_substrate_fingerprint.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/integration/test_transient_error_backoff.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/_docs.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/_event_scan.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_atomic_write_enforced.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_authored_facts_declared.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_auto_stop_policy_ssot.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_bdd_test_conventions.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_classification_ssot.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_config_error_consistency.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_config_value_set_ssot.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_dataclass_fields_have_producers.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_builtin_plugins.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_claims_match_ssot.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_cli_claims.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_operator_surface.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_test_pointers_exist.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_transient_error_claims.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_doc_usage_event_fields.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_docs_generated.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_docs_index_complete.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_entry_points_resolve.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_event_int_coercion.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_event_kind_registry.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_event_kinds_ssot.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_events_doc_contract.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_gen_blocks_name_their_source.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_hook_contract_docs.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_import_footprint.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_layer_2_loop_size.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_migrations_registry_ssot.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_module_boundaries.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_module_sizes.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_monitor_self_exclusion.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_ai_signatures.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_internal_refs.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_price_logic.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_pytest_skip_on_parse_fail.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_raw_time.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_vacuous_invariants.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_no_version_refs.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_peek_schema_version.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_phase_select_stateless.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_plugins_config_stable.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_readme_zh_is_pointer.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_repo_constants_patched_in_tests.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_round_alloc_growth.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_round_log_decode_boundary.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_round_num_single_source.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_round_result_stable.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_set_diff_for_auto_tool_classification.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_stash_uses_sha_not_index.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_timeout_budget_invariant.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_upstream_schema_canary.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/invariants/test_vulture_whitelist_generated.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/literate/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/literate/parser.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/literate/test_parser.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/literate/test_quickstart.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/__init__.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime_grace.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime_marker_scan.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime_progress.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime_reap.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_agent_runtime_signal_name.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_assemble_prompt.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_events_stream.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_install.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_observation.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_read_round_num.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_resolve_phase.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_service.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_api_types.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_auto_stop_gating.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_catalogs.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cgroup_probe.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_claude_error_detector.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_claude_foreign_schema.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli_common.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli_init_install.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli_main_boundary.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli_monitor_http.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli_service_peek_monitor.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_cli_upgrade.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_codewhale_code_precedence.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_codewhale_plugin.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_coerce_int_poison.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_fresh_eyes.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_max_rounds.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_nested_strictness.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_prompt_delivery.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_schedule.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_stop_file.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_strictness.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_substrate_fingerprint_paths.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_config_transient_error_action.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_context_store.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_control_char_config.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_default_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_defenses.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_detect_hung_latch.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_detector_isolation.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_detector_protocol.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_dirty_handlers.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_docgen.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_effective_throttle_view.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_emit_config_migrated.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_emit_schedule.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_emit_schedule_phase_skipped.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_events.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_events_cmd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_events_cmd_rollover.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_events_cmd_tail.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_events_jsonl_decode.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_example_dirty_handler.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_example_plugin.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_exit_cause.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_exit_classifier.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_fresh_eyes_trigger.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_gemini_plugin.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_git_timeout.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_hook_failure_isolation.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_hook_redact_order.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_hooks.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_host_health.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_http_progress.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_http_progress_epoch.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_init_entry_points.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_interruptible_sleep_should_stop.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_kimi_plugin.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_lifecycle.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_metrics.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_metrics_cgroup_usage.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_metrics_swap.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_migrate_cmd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_migrations.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_assembly.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_cmd_severity.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_dedup_identity.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_dedup_rearm.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_detect_anomaly_repetitive.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_detect_rate_limit.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_detect_supervisor_stale.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_detectors.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_event_tail.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_loop.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_on_alert.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_remote.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_round_deferred_stale.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_monitor_schedule_pause.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_network_blip_signal_guard.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_open_round_log.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_orphan_chain_single_pass.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_outer_round_ceiling.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_peek_argparse.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_peek_project_isolation.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_peek_schedule.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_peek_select.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_phase_agent_launch.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_phase_profiles.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_phase_select.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_pi_plugin.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_plugin_constants.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_plugin_scan_parity.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_presets.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_primary_prompt_file_home.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_prompt_loader.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_read_json_hardening.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_redact.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_resolve.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_round_cmd.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_round_cmd_sigterm.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_round_log_helpers.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_round_outcome.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_round_outcome_agent_axis.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_round_view.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_runner.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_runner_sidecar_ordering.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_runner_throttle.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_scaffold.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_schedule.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_cmd_bounded.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_config_broken.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_crash_loop.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_mem_gate.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_phase_schedule.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_policy.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_round.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_round_cgroup_spine.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_round_log.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_round_oom_killed.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_round_terminate.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_schedule_gate.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_sentinel.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_serve_startup_hooks.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_service_mode.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_service_unit.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_startup_battery_exit.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_substrate.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_supervisor_stale_self_blind.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_throttle_back_off_ladder.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_throttle_epoch_bound.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_throttle_plugin_classification.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_upgrade_migrate.py +0 -0
- {cli_agent_runner-0.2.20 → cli_agent_runner-0.2.21}/tests/unit/test_vcs_state.py +0 -0
|
@@ -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.2.21] - 2026-09-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `[agent] exec_prefix`: an optional token list prepended to the agent command at spawn (with `{work_dir}` substituted), so the agent can run inside a container you supply (`docker`/`podman run …`) while agent-runner supervises the resulting process from outside. The agent's identity — throttle, crash-loop, usage plugins — still keys on `command`, unchanged.
|
|
12
|
+
- Startup check: a stdin-delivery agent whose container `exec_prefix` omits `-i`/`--interactive` now fails loudly at boot instead of silently hanging until the round-timeout kill.
|
|
13
|
+
- Docs: container recipes (`docs/recipes/container-pi.md`, with Kimi/Claude/rootless-podman deltas), an all-in-one deploy recipe (`deploy/` + `docs/recipes/deploy-all-in-one.md`), and a `SECURITY.md` containment section.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- The best-effort container stop no longer reports a false "stop failed" when a `--rm` container has already exited on its own; an already-removed container now counts as a successful stop.
|
|
17
|
+
|
|
18
|
+
### Notes
|
|
19
|
+
- Not breaking: no `agent-runner migrate` step, no systemd unit change.
|
|
20
|
+
|
|
8
21
|
## [0.2.20] - 2026-09-09
|
|
9
22
|
|
|
10
23
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cli-agent-runner
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.21
|
|
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,26 @@ 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
|
+
## Containment
|
|
41
|
+
|
|
42
|
+
If you need to bound what the agent can reach, run it inside a container (or
|
|
43
|
+
VM) you supply, via `[agent] exec_prefix` — see
|
|
44
|
+
[docs/recipes/container-pi.md](docs/recipes/container-pi.md). The container
|
|
45
|
+
(or VM) enforces the isolation; agent-runner still just watches the
|
|
46
|
+
resulting process from outside, the same way it watches a bare agent
|
|
47
|
+
process.
|
|
48
|
+
|
|
49
|
+
One limit worth knowing: agent-runner's own process-tree defenses (reaping a
|
|
50
|
+
hung round's descendants) only see processes in its own subtree, and a
|
|
51
|
+
container-runtime-managed process usually isn't one of them. On a stuck
|
|
52
|
+
round, the one remaining lever is stopping the container by the id
|
|
53
|
+
agent-runner captured at spawn, which only works for a plain `docker
|
|
54
|
+
run …`/`podman run …` invocation — not one wrapped in `sudo` or other global
|
|
55
|
+
flags (see container-pi.md's footguns). Using a container this way gives you
|
|
56
|
+
a narrower safety net than the host-level reaping a bare agent process gets,
|
|
57
|
+
not a wider one — keep running it as a dedicated unprivileged user with no
|
|
58
|
+
passwordless sudo and tight egress limits regardless.
|
|
59
|
+
|
|
40
60
|
## Scope
|
|
41
61
|
|
|
42
62
|
In scope: the `agent_runner` Python package, its CLI, its bundled systemd
|
|
@@ -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.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.21'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 21)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -26,6 +26,11 @@ from agent_runner.clock import SYSTEM_CLOCK, Clock
|
|
|
26
26
|
|
|
27
27
|
REAP_GRACE_S = 5
|
|
28
28
|
|
|
29
|
+
# Wall-clock bound on the best-effort `<runtime> stop` subprocess. Pinned by an
|
|
30
|
+
# invariant test to stay <= _serve_policy._ROUND_TERM_GRACE_S so serve's
|
|
31
|
+
# round-terminate escalation never SIGKILLs round_cmd mid-stop.
|
|
32
|
+
_CONTAINER_STOP_TIMEOUT_S = REAP_GRACE_S + 5
|
|
33
|
+
|
|
29
34
|
# Backstop cap on the stored child "name" (see _live_children). The primary
|
|
30
35
|
# source (kernel comm) is already bounded (15 bytes on Linux); this only
|
|
31
36
|
# guards the argv[0]-basename fallback used when comm is unavailable.
|
|
@@ -362,6 +367,20 @@ def _detect_container_run(command: list[str]) -> tuple[str, int] | None:
|
|
|
362
367
|
return None
|
|
363
368
|
|
|
364
369
|
|
|
370
|
+
# Matches the docker/podman flags that attach the container's stdin: `-i`, any
|
|
371
|
+
# short cluster containing `i` (`-it`, `-ti`, `-di`), and `--interactive[=…]`;
|
|
372
|
+
# NOT `--init` (double-dash, so the single-dash alternative can't consume it).
|
|
373
|
+
_INTERACTIVE_FLAG = re.compile(r"^(-[a-z]*i[a-z]*|--interactive(=.*)?)$")
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _exec_prefix_is_interactive(exec_prefix: list[str]) -> bool:
|
|
377
|
+
"""True when the operator's ``exec_prefix`` carries a stdin-attaching flag.
|
|
378
|
+
Only the prefix's own docker flags matter -- the wrapped ``command`` runs
|
|
379
|
+
INSIDE the container, so an ``-i`` there is irrelevant to whether the
|
|
380
|
+
runtime forwards stdin."""
|
|
381
|
+
return any(_INTERACTIVE_FLAG.match(tok) for tok in exec_prefix)
|
|
382
|
+
|
|
383
|
+
|
|
365
384
|
# `docker run`/`podman run` OPTIONS known to take a separate value token --
|
|
366
385
|
# used only to walk PAST them without mistaking their value for IMAGE (e.g.
|
|
367
386
|
# `--name foo`: without this, a naive scan would stop at "foo" thinking it's
|
|
@@ -479,10 +498,12 @@ def _best_effort_container_stop(
|
|
|
479
498
|
``(container_id, stop_ok)``: `container_id` is None when no id could be
|
|
480
499
|
recovered (the container never actually started, or the cidfile is still
|
|
481
500
|
empty) -- `stop_ok` is then also None, meaning no stop was attempted.
|
|
482
|
-
Otherwise `stop_ok` is
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
this
|
|
501
|
+
Otherwise `stop_ok` is True when the `stop` subprocess exited zero, or
|
|
502
|
+
when it failed because the container was already gone (a `--rm`
|
|
503
|
+
container can self-remove before our stop lands -- that's not a failure
|
|
504
|
+
to report). Every other failure here is swallowed -- this is advisory
|
|
505
|
+
best-effort, never a guarantee; the caller's loud warning + event is the
|
|
506
|
+
actual floor when this doesn't land."""
|
|
486
507
|
if cidfile is None:
|
|
487
508
|
return None, None
|
|
488
509
|
try:
|
|
@@ -493,12 +514,16 @@ def _best_effort_container_stop(
|
|
|
493
514
|
return None, None
|
|
494
515
|
try:
|
|
495
516
|
result = subprocess.run(
|
|
496
|
-
[runtime, "stop", cid],
|
|
517
|
+
[runtime, "stop", "-t", str(REAP_GRACE_S), cid],
|
|
497
518
|
stdout=subprocess.DEVNULL,
|
|
498
|
-
stderr=subprocess.
|
|
499
|
-
timeout=
|
|
519
|
+
stderr=subprocess.PIPE,
|
|
520
|
+
timeout=_CONTAINER_STOP_TIMEOUT_S,
|
|
500
521
|
)
|
|
501
|
-
|
|
522
|
+
if result.returncode == 0:
|
|
523
|
+
return cid, True
|
|
524
|
+
# A `--rm` container that already exited is GONE, not orphaned — not a failure.
|
|
525
|
+
stderr = (result.stderr or b"").decode("utf-8", "replace").lower()
|
|
526
|
+
return cid, "no such container" in stderr
|
|
502
527
|
except (OSError, subprocess.TimeoutExpired):
|
|
503
528
|
return cid, False
|
|
504
529
|
|
|
@@ -29,6 +29,7 @@ from agent_runner._throttle import (
|
|
|
29
29
|
round_outcome,
|
|
30
30
|
round_was_mem_terminated,
|
|
31
31
|
)
|
|
32
|
+
from agent_runner.agent_runtime import _detect_container_run
|
|
32
33
|
from agent_runner.api import (
|
|
33
34
|
check_self_terminated_sentinel,
|
|
34
35
|
emit_config_broken,
|
|
@@ -611,7 +612,9 @@ def cmd(args) -> int:
|
|
|
611
612
|
return fail_code
|
|
612
613
|
stop_file = cfg.runtime.stop_file # cache: same pattern as effective_max_rounds
|
|
613
614
|
work_dir = cfg.runtime.work_dir
|
|
614
|
-
|
|
615
|
+
# Emitted exactly once per serve lifetime -- the per-round defer decision below
|
|
616
|
+
# reuses this bool rather than re-probing/re-emitting every round.
|
|
617
|
+
cgroup_probe_defer = _probe_and_emit_cgroup_defer(log_dir)
|
|
615
618
|
rounds_completed = 0
|
|
616
619
|
# Three independent consecutive-failure counters, one per breaker: b12
|
|
617
620
|
# crash-loop (unknown short crashes), mem-loop (mem-terminated
|
|
@@ -685,6 +688,15 @@ def cmd(args) -> int:
|
|
|
685
688
|
]
|
|
686
689
|
if phase_arg is not None:
|
|
687
690
|
round_argv += ["--phase", phase_arg]
|
|
691
|
+
# Phase-aware, per-round: a phase can override [phases.<name>.agent]
|
|
692
|
+
# command to a bare container run even when the base agent isn't one
|
|
693
|
+
# (exec_prefix is base-only, but command isn't) -- computing this once
|
|
694
|
+
# from the base agent would wrongly keep deferring a containerized
|
|
695
|
+
# phase's mid-round kill to dockerd's own cgroup-OOM.
|
|
696
|
+
defer_to_cgroup = cgroup_probe_defer and (
|
|
697
|
+
_detect_container_run(cfg.profile_for(phase_arg).agent.spawn_command(work_dir))
|
|
698
|
+
is None
|
|
699
|
+
)
|
|
688
700
|
r_returncode = _spawn_round(
|
|
689
701
|
round_argv,
|
|
690
702
|
round_log_path,
|
|
@@ -30,6 +30,7 @@ class AgentConfig:
|
|
|
30
30
|
name: str | None = None
|
|
31
31
|
env: dict[str, str] = field(default_factory=dict)
|
|
32
32
|
prompt_delivery: Literal["argv", "stdin"] = "argv"
|
|
33
|
+
exec_prefix: list[str] = field(default_factory=list)
|
|
33
34
|
|
|
34
35
|
@property
|
|
35
36
|
def binary(self) -> str | None:
|
|
@@ -40,6 +41,16 @@ class AgentConfig:
|
|
|
40
41
|
serve and runner don't each re-spell ``Path(command[0]).name``."""
|
|
41
42
|
return Path(self.command[0]).name if self.command else None
|
|
42
43
|
|
|
44
|
+
def spawn_command(self, work_dir: Path) -> list[str]:
|
|
45
|
+
"""The effective argv to spawn: an opaque operator-supplied ``exec_prefix``
|
|
46
|
+
(e.g. ``docker run … img``) spliced BEFORE ``command``, with the one
|
|
47
|
+
``{work_dir}`` token substituted IN THE PREFIX ONLY. ``command`` (and thus
|
|
48
|
+
``binary``) is appended verbatim and never rewritten, so the
|
|
49
|
+
throttle/crash-loop/hook identity join stays keyed on the real agent. Empty
|
|
50
|
+
``exec_prefix`` ⇒ ``command`` unchanged. The SSOT both the spawn (runner) and
|
|
51
|
+
the startup battery resolve, so they can't drift."""
|
|
52
|
+
return [t.replace("{work_dir}", str(work_dir)) for t in self.exec_prefix] + self.command
|
|
53
|
+
|
|
43
54
|
|
|
44
55
|
@dataclass(frozen=True)
|
|
45
56
|
class RuntimeConfig:
|
|
@@ -106,12 +106,16 @@ def _parse_agent(
|
|
|
106
106
|
f"{field_prefix} command: must be a non-empty list; run `agent-runner migrate`"
|
|
107
107
|
)
|
|
108
108
|
env_d = _require_table(agent_d, "env", label=f"{field_prefix.strip('[]')}.env")
|
|
109
|
+
exec_prefix = _require_str_list(
|
|
110
|
+
agent_d.get("exec_prefix", []), field=f"{field_prefix} exec_prefix"
|
|
111
|
+
)
|
|
109
112
|
return AgentConfig(
|
|
110
113
|
command=command,
|
|
111
114
|
prompt_arg_template=prompt_arg_template,
|
|
112
115
|
name=agent_d.get("name"),
|
|
113
116
|
env={str(k): str(v) for k, v in env_d.items()},
|
|
114
117
|
prompt_delivery=prompt_delivery, # type: ignore[arg-type] # narrowed above
|
|
118
|
+
exec_prefix=exec_prefix,
|
|
115
119
|
)
|
|
116
120
|
|
|
117
121
|
|
|
@@ -214,6 +218,11 @@ def _parse_phase_overrides(
|
|
|
214
218
|
agent_sub = value["agent"]
|
|
215
219
|
if not isinstance(agent_sub, dict):
|
|
216
220
|
raise ConfigError(f"[phases.{phase_name}.agent] must be a table")
|
|
221
|
+
if "exec_prefix" in agent_sub:
|
|
222
|
+
raise ConfigError(
|
|
223
|
+
f"[phases.{phase_name}.agent] exec_prefix: not allowed per-phase — set "
|
|
224
|
+
"exec_prefix once on the base [agent] table (it applies to every phase)"
|
|
225
|
+
)
|
|
217
226
|
merged = {**agent_d, **agent_sub}
|
|
218
227
|
phase_agent = _parse_agent(
|
|
219
228
|
merged,
|
|
@@ -554,7 +554,7 @@ def _run_one_round_inner(cfg: Config, *, phase_override: str | None = None) -> R
|
|
|
554
554
|
)
|
|
555
555
|
|
|
556
556
|
result = agent_runtime.run(
|
|
557
|
-
command=profile.agent.
|
|
557
|
+
command=profile.agent.spawn_command(cfg.runtime.work_dir),
|
|
558
558
|
prompt_arg_template=profile.agent.prompt_arg_template,
|
|
559
559
|
prompt=prompt,
|
|
560
560
|
prompt_delivery=profile.agent.prompt_delivery,
|
|
@@ -50,13 +50,16 @@ def _check_log_dir(cfg: Config) -> CheckResult:
|
|
|
50
50
|
def _check_agent_target(agent: AgentConfig, work_dir: Path, name: str) -> CheckResult:
|
|
51
51
|
if not agent.command:
|
|
52
52
|
return CheckResult(name, False, "agent.command is empty", permanent=True)
|
|
53
|
-
cli = agent.
|
|
53
|
+
cli = agent.spawn_command(work_dir)[0]
|
|
54
54
|
# Validate with the exact resolution the spawn uses (agent_runtime owns
|
|
55
55
|
# the model): slash-containing commands resolve against work_dir (the
|
|
56
56
|
# child's cwd); bare names use the CHILD's PATH ([agent.env] may set it).
|
|
57
57
|
resolved = agent_runtime.resolve_exec_target(cli, work_dir, env_path=agent.env.get("PATH"))
|
|
58
58
|
if resolved is None:
|
|
59
59
|
relative = "/" in cli
|
|
60
|
+
# cli is exec_prefix[0] when a prefix is set (spawn_command splices the
|
|
61
|
+
# prefix first) -- pointing the fix at agent.command[0] would be wrong.
|
|
62
|
+
target = "[agent] exec_prefix[0]" if agent.exec_prefix else "agent.command[0]"
|
|
60
63
|
return CheckResult(
|
|
61
64
|
name,
|
|
62
65
|
False,
|
|
@@ -68,13 +71,41 @@ def _check_agent_target(agent: AgentConfig, work_dir: Path, name: str) -> CheckR
|
|
|
68
71
|
how_to_fix=(
|
|
69
72
|
"fix the path relative to runtime.work_dir, or use an absolute path"
|
|
70
73
|
if relative
|
|
71
|
-
else f"install {cli} or set
|
|
74
|
+
else f"install {cli} or set {target} to its absolute path"
|
|
72
75
|
),
|
|
73
76
|
permanent=True,
|
|
74
77
|
)
|
|
75
78
|
return CheckResult(name, True)
|
|
76
79
|
|
|
77
80
|
|
|
81
|
+
def _check_stdin_container_interactive(
|
|
82
|
+
agent: AgentConfig, work_dir: Path, name: str
|
|
83
|
+
) -> CheckResult:
|
|
84
|
+
"""A stdin-delivery agent whose container run drops stdin (no -i/--interactive)
|
|
85
|
+
silently hangs until the round-timeout kill -- fail loud at boot instead."""
|
|
86
|
+
if agent.prompt_delivery != "stdin":
|
|
87
|
+
return CheckResult(name, True)
|
|
88
|
+
argv = agent.spawn_command(work_dir)
|
|
89
|
+
if agent_runtime._detect_container_run(argv) is None:
|
|
90
|
+
return CheckResult(name, True)
|
|
91
|
+
# Whether docker forwards stdin depends ONLY on docker's own flags
|
|
92
|
+
# (exec_prefix) -- agent.command runs INSIDE the container, so an -i/
|
|
93
|
+
# --interactive there is irrelevant and must not satisfy this check.
|
|
94
|
+
if agent_runtime._exec_prefix_is_interactive(agent.exec_prefix):
|
|
95
|
+
return CheckResult(name, True)
|
|
96
|
+
|
|
97
|
+
return CheckResult(
|
|
98
|
+
name,
|
|
99
|
+
False,
|
|
100
|
+
reason="stdin prompt delivery into a container, but exec_prefix lacks -i/--interactive",
|
|
101
|
+
how_to_fix=(
|
|
102
|
+
'add "-i" to [agent] exec_prefix (docker drops stdin without it '
|
|
103
|
+
"→ the agent hangs until the round-timeout kill)"
|
|
104
|
+
),
|
|
105
|
+
permanent=True,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
78
109
|
def _check_work_dir_is_git(cfg: Config) -> CheckResult:
|
|
79
110
|
from agent_runner.vcs_state import GitTimeout, is_git_repo
|
|
80
111
|
|
|
@@ -194,6 +225,14 @@ def _agent_cli_checks(cfg: Config) -> list[CheckResult]:
|
|
|
194
225
|
profile = cfg.profile_for(phase)
|
|
195
226
|
name = "agent_cli_in_path" if phase is None else f"agent_cli_in_path:{phase}"
|
|
196
227
|
results.append(_check_agent_target(profile.agent, cfg.runtime.work_dir, name))
|
|
228
|
+
stdin_name = (
|
|
229
|
+
"stdin_container_interactive"
|
|
230
|
+
if phase is None
|
|
231
|
+
else f"stdin_container_interactive:{phase}"
|
|
232
|
+
)
|
|
233
|
+
results.append(
|
|
234
|
+
_check_stdin_container_interactive(profile.agent, cfg.runtime.work_dir, stdin_name)
|
|
235
|
+
)
|
|
197
236
|
return results
|
|
198
237
|
|
|
199
238
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Template Dockerfile for running the whole unit (supervisor + agent CLI) in
|
|
2
|
+
# one container. This is a recipe to copy and adapt into your own project —
|
|
3
|
+
# see docs/recipes/deploy-all-in-one.md — not a published or maintained image.
|
|
4
|
+
|
|
5
|
+
FROM python:3.12-slim
|
|
6
|
+
|
|
7
|
+
# git is required: agent-runner's dirty-tree detection shells out to it.
|
|
8
|
+
RUN apt-get update && apt-get install -y --no-install-recommends git \
|
|
9
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
10
|
+
|
|
11
|
+
RUN pip install --no-cache-dir cli-agent-runner
|
|
12
|
+
# Pin a specific release for reproducible builds if you need one, e.g.
|
|
13
|
+
# `cli-agent-runner==X.Y.Z`.
|
|
14
|
+
|
|
15
|
+
# --- Agent CLI slot -----------------------------------------------------
|
|
16
|
+
# agent-runner is CLI-agnostic and ships none. Install whichever agent you
|
|
17
|
+
# configured in agent-runner.toml here, e.g. (needs a Node base image, or an
|
|
18
|
+
# added Node install step first):
|
|
19
|
+
# RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent
|
|
20
|
+
# -------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
WORKDIR /work
|
|
23
|
+
ENTRYPOINT ["agent-runner"]
|
|
24
|
+
CMD ["serve"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Template compose file for the all-in-one deploy recipe — copy alongside
|
|
2
|
+
# Dockerfile into your own project and adapt. See
|
|
3
|
+
# docs/recipes/deploy-all-in-one.md.
|
|
4
|
+
|
|
5
|
+
services:
|
|
6
|
+
agent-runner:
|
|
7
|
+
build: .
|
|
8
|
+
# Mount your project (it must contain agent-runner.toml at its root,
|
|
9
|
+
# matching [runtime] work_dir = "." in that file) at /work.
|
|
10
|
+
volumes:
|
|
11
|
+
- .:/work
|
|
12
|
+
working_dir: /work
|
|
13
|
+
environment:
|
|
14
|
+
# Value-less: pass through from the host shell. Add whatever your
|
|
15
|
+
# agent CLI needs the same way.
|
|
16
|
+
- ANTHROPIC_API_KEY
|
|
17
|
+
command: ["serve"]
|
|
18
|
+
restart: unless-stopped
|
|
@@ -29,6 +29,8 @@ move it to the doc whose charter it belongs to (see the decision procedure in
|
|
|
29
29
|
| [long-running-agents.md](long-running-agents.md) | Context-rot essay: fresh eyes, long-lineage runs |
|
|
30
30
|
| [recipes/aider.md](recipes/aider.md) | Per-CLI walkthrough — aider |
|
|
31
31
|
| [recipes/codewhale.md](recipes/codewhale.md) | Per-CLI walkthrough — codewhale |
|
|
32
|
+
| [recipes/container-pi.md](recipes/container-pi.md) | Per-CLI walkthrough — pi (+ kimi/claude/podman deltas) run in a container via `exec_prefix` |
|
|
33
|
+
| [recipes/deploy-all-in-one.md](recipes/deploy-all-in-one.md) | Recipe — supervisor + agent CLI in one container |
|
|
32
34
|
| [recipes/kimi.md](recipes/kimi.md) | Per-CLI walkthrough — Kimi Code CLI + Kimi K3 via the claude preset |
|
|
33
35
|
| [recipes/pi.md](recipes/pi.md) | Per-CLI walkthrough — Pi Coding Agent + Kimi K3 via a Moonshot provider |
|
|
34
36
|
|
|
@@ -275,7 +275,7 @@ The transforms `migrate` applies (generated from the registry):
|
|
|
275
275
|
- unknown [schedule] key(s) rejected in 0.2.12; delete them (allowed: ['pause_windows', 'run_windows', 'timezone'])
|
|
276
276
|
- monitor.anomaly_repetitive_threshold > anomaly_repetitive_window: lower the threshold or raise the window so the detector can fire
|
|
277
277
|
- [<table>] given as a scalar, not a table; give it real [table] content (no auto-fix possible)
|
|
278
|
-
- unknown [agent] key(s) rejected in 0.2.13; delete them (allowed: ['command', 'env', 'name', 'prompt_arg_template', 'prompt_delivery'])
|
|
278
|
+
- unknown [agent] key(s) rejected in 0.2.13; delete them (allowed: ['command', 'env', 'exec_prefix', 'name', 'prompt_arg_template', 'prompt_delivery'])
|
|
279
279
|
- 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_log_retention', 'round_timeout_s', 'stop_file', 'substrate_fingerprint_paths', 'transient_error_action', 'work_dir'])
|
|
280
280
|
- unknown [vcs] key(s) rejected in 0.2.13; delete them (allowed: ['dirty_action', 'stash_idempotency_s'])
|
|
281
281
|
- 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'])
|
|
@@ -30,6 +30,7 @@ running with newly-set `dirty_action = "auto_commit"` is undefined).
|
|
|
30
30
|
| `name` | `str \| None` | None |
|
|
31
31
|
| `env` | `dict[str, str]` | {} |
|
|
32
32
|
| `prompt_delivery` | `Literal['argv', 'stdin']` | 'argv' |
|
|
33
|
+
| `exec_prefix` | `list[str]` | [] |
|
|
33
34
|
|
|
34
35
|
### `[runtime]`
|
|
35
36
|
|
|
@@ -311,7 +312,7 @@ sub-tables reject unknown keys: `agent` and `runtime` as noted below, and
|
|
|
311
312
|
|
|
312
313
|
| Sub-table | Overrides | Fields accepted |
|
|
313
314
|
|---|---|---|
|
|
314
|
-
| `[phases.<name>.agent]` | `[agent]` | any `[agent]` field; field-merged onto the base `[agent]`, then validated (so the `stdin` + `{prompt}` cross-check runs on the merged result) |
|
|
315
|
+
| `[phases.<name>.agent]` | `[agent]` | any `[agent]` field except `exec_prefix` (base-only, see below); field-merged onto the base `[agent]`, then validated (so the `stdin` + `{prompt}` cross-check runs on the merged result) |
|
|
315
316
|
| `[phases.<name>.runtime]` | `[runtime]` | `round_timeout_s`, `disable_pre_round_hooks` only |
|
|
316
317
|
| `[phases.<name>.schedule]` | `[schedule]` | any `[schedule]` field; **replaces** the global windows wholesale |
|
|
317
318
|
| `[phases.<name>.prompt]` | `[prompt]` | `files` only |
|
|
@@ -343,6 +344,9 @@ the base config regardless of phase:
|
|
|
343
344
|
`[monitor]`, and `[plugins]` — global only.
|
|
344
345
|
- `[prompt]` fields other than `files` (`context_injection_mode`,
|
|
345
346
|
`inject_context`, `concat_separator`, …).
|
|
347
|
+
- **`agent.exec_prefix`** — set once on the base `[agent]` table; it applies to
|
|
348
|
+
every phase. A `[phases.<name>.agent]` sub-table that sets its own
|
|
349
|
+
`exec_prefix` is rejected at load.
|
|
346
350
|
|
|
347
351
|
### `phase_policy` — `wait` vs `skip`
|
|
348
352
|
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Running pi inside a container
|
|
2
|
+
|
|
3
|
+
This is [pi.md](pi.md)'s agent, run inside a container you supply, via
|
|
4
|
+
`[agent] exec_prefix` (schema: [configuration.md](../configuration.md)).
|
|
5
|
+
`exec_prefix` is an opaque list spliced onto the front of `command` at spawn —
|
|
6
|
+
so the effective process is `docker run … my-pi-image pi --mode json`, and
|
|
7
|
+
agent-runner watches that `docker run` client as an ordinary process, exactly
|
|
8
|
+
as it would watch a bare `pi` process. The container (and the OS underneath
|
|
9
|
+
it) enforces the isolation; agent-runner does not sandbox the agent itself —
|
|
10
|
+
it runs the agent in a container you supply and watches from outside.
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
- Docker (or Podman — see the rootless delta below) installed on the
|
|
15
|
+
supervisor host.
|
|
16
|
+
- Your user in the `docker` group — **not** `sudo` (see Footguns).
|
|
17
|
+
- An image you build yourself, e.g.:
|
|
18
|
+
```dockerfile
|
|
19
|
+
FROM node:20-slim
|
|
20
|
+
RUN npm i -g --ignore-scripts @earendil-works/pi-coding-agent
|
|
21
|
+
# the template runs as -u 1000:1000 with HOME=/home/agent, so the image
|
|
22
|
+
# must provide that dir owned by the UID (or $HOME-relative writes fail):
|
|
23
|
+
RUN mkdir -p /home/agent && chown 1000:1000 /home/agent
|
|
24
|
+
```
|
|
25
|
+
- A provider/model configured for pi — same as [pi.md](pi.md)'s Provider auth
|
|
26
|
+
section.
|
|
27
|
+
- A git repo as `work_dir`, bind-mounted into the container at `/work`.
|
|
28
|
+
|
|
29
|
+
## Config
|
|
30
|
+
|
|
31
|
+
```toml
|
|
32
|
+
[agent]
|
|
33
|
+
command = ["pi", "--mode", "json"]
|
|
34
|
+
exec_prefix = ["docker", "run", "--rm", "-i", "--init", "--stop-timeout", "5",
|
|
35
|
+
"-u", "1000:1000", "-e", "HOME=/home/agent",
|
|
36
|
+
"-v", "{work_dir}:/work:Z", "-w", "/work",
|
|
37
|
+
"-e", "ANTHROPIC_API_KEY", "my-pi-image"]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`{work_dir}` is the one templated token — agent-runner substitutes it with
|
|
41
|
+
the run's absolute `work_dir` at spawn; every other token passes through
|
|
42
|
+
untouched. `command` stays `["pi", "--mode", "json"]`, so pi's identity
|
|
43
|
+
(throttle / crash-loop / usage-plugin keying, all keyed on `command[0]`) is
|
|
44
|
+
unaffected by running inside docker.
|
|
45
|
+
|
|
46
|
+
### Why each flag
|
|
47
|
+
|
|
48
|
+
- **`--rm`** — remove the container on exit. Without it, containers
|
|
49
|
+
accumulate one per round.
|
|
50
|
+
- **`-i`** — keep stdin open. pi's `prompt_delivery = "stdin"` needs it;
|
|
51
|
+
without `-i`, docker drops stdin and the agent hangs until
|
|
52
|
+
`round_timeout_s` kills it. agent-runner checks this at startup and
|
|
53
|
+
refuses to start a stdin-delivery config whose `exec_prefix` is missing
|
|
54
|
+
`-i`.
|
|
55
|
+
- **`--init`** — run an init process as PID 1 inside the container, so
|
|
56
|
+
SIGTERM reaches pi and in-container zombies get reaped. Without it, a
|
|
57
|
+
shell-form entrypoint as PID 1 can ignore SIGTERM outright.
|
|
58
|
+
- **`--stop-timeout 5`** — bound `docker stop`'s grace period so teardown
|
|
59
|
+
after a stop stays short and predictable.
|
|
60
|
+
- **`-u 1000:1000`** — run as your host UID, not root. A root-owned file
|
|
61
|
+
written into the bind-mounted `work_dir` poisons agent-runner's host-side
|
|
62
|
+
dirty-tree stash/commit, and trips git's "dubious ownership" check
|
|
63
|
+
in-container. Use `id -u`/`id -g` if your UID isn't 1000.
|
|
64
|
+
- **`-e HOME=/home/agent`** — an arbitrary UID has no `/etc/passwd` entry, so
|
|
65
|
+
`~` expansion breaks inside the container; set `HOME` explicitly.
|
|
66
|
+
- **`-v {work_dir}:/work:Z`** — bind-mount the run's `work_dir`. `:Z`
|
|
67
|
+
relabels the mount for SELinux hosts (Fedora/RHEL); drop it elsewhere.
|
|
68
|
+
- **`-w /work`** — pi has no `--cwd` flag (see [pi.md](pi.md)), so the
|
|
69
|
+
container's working directory has to already be the mount.
|
|
70
|
+
- **`-e ANTHROPIC_API_KEY`** (value-less) — forwards the key from
|
|
71
|
+
agent-runner's own environment into the container. **Never write
|
|
72
|
+
`-e ANTHROPIC_API_KEY=sk-…`** — a valued `-e` puts the secret straight into
|
|
73
|
+
the `docker run` argv, which lands in `ps` output and `/proc/<pid>/cmdline`
|
|
74
|
+
for any local user to read. Add every other provider key the same,
|
|
75
|
+
value-less way.
|
|
76
|
+
- **`my-pi-image`** — the image you built above.
|
|
77
|
+
|
|
78
|
+
## Auth
|
|
79
|
+
|
|
80
|
+
Prefer env vars, as above — there's no state to lose when `--rm` removes the
|
|
81
|
+
container each round. If your provider needs a credential *file* (e.g.
|
|
82
|
+
`pi /login`'s `~/.pi/agent/auth.json`), mount it **outside** `work_dir` —
|
|
83
|
+
mounting it inside would show up as tracked-file churn to agent-runner's
|
|
84
|
+
dirty-tree detector every round:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
-v /home/you/.pi-container-auth:/home/agent/.pi/agent:ro
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Footguns
|
|
91
|
+
|
|
92
|
+
These aren't enforced by agent-runner (`exec_prefix` is opaque) — get them
|
|
93
|
+
right yourself:
|
|
94
|
+
|
|
95
|
+
- **`sudo docker run` loses the clean stop.** agent-runner's container-orphan
|
|
96
|
+
defense only injects a `--cidfile` (and so can attempt a clean `docker
|
|
97
|
+
stop` on a stuck round) for the literal `docker run …` shape. A
|
|
98
|
+
`sudo`-wrapped or globally-flagged form is still *detected* — you get the
|
|
99
|
+
warning and event — but not covered by the clean stop. Put your user in
|
|
100
|
+
the `docker` group instead of prefixing `sudo`.
|
|
101
|
+
- **Missing `--rm`** accumulates one container per round, forever.
|
|
102
|
+
- **`--network none`** breaks the agent outright — it can't reach the model
|
|
103
|
+
API.
|
|
104
|
+
- **Missing `-u`** runs the container as root — see the root-owned-files
|
|
105
|
+
footgun above.
|
|
106
|
+
- **`-d`/`--detach`** — never use it. Detached `docker run` exits 0
|
|
107
|
+
immediately while the container is still starting, so agent-runner sees an
|
|
108
|
+
instant "successful" round with no agent work actually done.
|
|
109
|
+
- **`-t` without a real TTY** fails fast — docker allocates a pseudo-TTY and
|
|
110
|
+
errors when the supervisor's own stdin/stdout aren't one, which under a
|
|
111
|
+
service manager they usually aren't.
|
|
112
|
+
- **`--sig-proxy=false`** disables the relay of agent-runner's SIGTERM into
|
|
113
|
+
the container for cooperative shutdown. Leave it at its default (`true`).
|
|
114
|
+
|
|
115
|
+
## Monitoring
|
|
116
|
+
|
|
117
|
+
`agent_process_count` in `peek`/metrics reads `0` for a containerized round —
|
|
118
|
+
expected, not a failure. The host-side probe counts processes named after the
|
|
119
|
+
agent (`pi`), but the host process is `docker`/`podman`; the agent runs in the
|
|
120
|
+
container's own PID namespace where the probe can't see it. Round liveness is
|
|
121
|
+
tracked by the container process agent-runner supervises directly, not this
|
|
122
|
+
count.
|
|
123
|
+
|
|
124
|
+
## Not for tiny edge hosts
|
|
125
|
+
|
|
126
|
+
This recipe assumes a host with room for a container runtime on top of the
|
|
127
|
+
agent's own (non-trivial) memory footprint. It isn't a fit for a small edge
|
|
128
|
+
board — run pi bare there instead (see [pi.md](pi.md)).
|
|
129
|
+
|
|
130
|
+
## Deltas
|
|
131
|
+
|
|
132
|
+
### kimi — ENV-only, no mount
|
|
133
|
+
|
|
134
|
+
[kimi.md](kimi.md)'s CLI is fully configured over four env vars and needs no
|
|
135
|
+
file at all — prefer this shape whenever your CLI supports it, since there's
|
|
136
|
+
nothing to bind-mount:
|
|
137
|
+
|
|
138
|
+
```toml
|
|
139
|
+
[agent]
|
|
140
|
+
command = ["kimi", "--output-format", "stream-json"]
|
|
141
|
+
exec_prefix = ["docker", "run", "--rm", "-i", "--init", "--stop-timeout", "5",
|
|
142
|
+
"-u", "1000:1000", "-e", "HOME=/home/agent",
|
|
143
|
+
"-v", "{work_dir}:/work:Z", "-w", "/work",
|
|
144
|
+
"-e", "KIMI_MODEL_NAME", "-e", "KIMI_MODEL_API_KEY",
|
|
145
|
+
"-e", "KIMI_MODEL_BASE_URL", "-e", "KIMI_MODEL_PROVIDER_TYPE",
|
|
146
|
+
"my-kimi-image"]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### claude — root rejection + config mount
|
|
150
|
+
|
|
151
|
+
Claude Code refuses `--dangerously-skip-permissions` when run as root, so
|
|
152
|
+
`-u` is mandatory here, not just recommended. If you authenticate via
|
|
153
|
+
`~/.claude` instead of an API key, mount it outside `work_dir` and point
|
|
154
|
+
`CLAUDE_CONFIG_DIR` at the mount:
|
|
155
|
+
|
|
156
|
+
```toml
|
|
157
|
+
exec_prefix = ["docker", "run", "--rm", "-i", "--init", "--stop-timeout", "5",
|
|
158
|
+
"-u", "1000:1000", "-e", "HOME=/home/agent",
|
|
159
|
+
"-v", "{work_dir}:/work:Z", "-w", "/work",
|
|
160
|
+
"-v", "/home/you/.claude-container:/home/agent/.claude:ro",
|
|
161
|
+
"-e", "CLAUDE_CONFIG_DIR=/home/agent/.claude",
|
|
162
|
+
"my-claude-image"]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### rootless podman — `--userns=keep-id` instead of `-u`
|
|
166
|
+
|
|
167
|
+
Podman rootless already runs as your user; swap `-u 1000:1000` for
|
|
168
|
+
`--userns=keep-id`, which maps your host UID inside the container without
|
|
169
|
+
needing root at all:
|
|
170
|
+
|
|
171
|
+
```toml
|
|
172
|
+
exec_prefix = ["podman", "run", "--rm", "-i", "--init", "--stop-timeout", "5",
|
|
173
|
+
"--userns=keep-id", "-e", "HOME=/home/agent",
|
|
174
|
+
"-v", "{work_dir}:/work:Z", "-w", "/work",
|
|
175
|
+
"-e", "ANTHROPIC_API_KEY", "my-pi-image"]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
See also: [docs/configuration.md](../configuration.md),
|
|
179
|
+
[SECURITY.md](../../SECURITY.md#containment).
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Running the whole unit in one container
|
|
2
|
+
|
|
3
|
+
This is a **recipe** — `deploy/Dockerfile` and `deploy/docker-compose.yml`
|
|
4
|
+
are templates to copy and adapt into your own project, not a published or
|
|
5
|
+
maintained image.
|
|
6
|
+
|
|
7
|
+
Where [container-pi.md](container-pi.md) boxes the *agent* and keeps the
|
|
8
|
+
supervisor on the host, this recipe does the opposite: the supervisor and
|
|
9
|
+
the agent CLI both run inside one container, and that container is the unit
|
|
10
|
+
you bound against the host (filesystem, network, resources). agent-runner
|
|
11
|
+
still supervises the agent CLI as an ordinary in-container subprocess — this
|
|
12
|
+
container doesn't add any isolation between the supervisor and the agent, it
|
|
13
|
+
only bounds the whole unit against the host it runs on.
|
|
14
|
+
|
|
15
|
+
## When to use this vs `exec_prefix`
|
|
16
|
+
|
|
17
|
+
- **`exec_prefix`** ([container-pi.md](container-pi.md)) — box the agent
|
|
18
|
+
only; the supervisor keeps its normal host-level view (events, `peek`,
|
|
19
|
+
host PSI/memory signals). Prefer this by default.
|
|
20
|
+
- **This recipe** — you want one image that *is* the deployable unit (e.g.
|
|
21
|
+
shipping the whole thing to a host you don't otherwise manage). In
|
|
22
|
+
exchange, the supervisor's view of memory/PSI pressure is now the
|
|
23
|
+
container's own, not the host's.
|
|
24
|
+
|
|
25
|
+
## Files
|
|
26
|
+
|
|
27
|
+
- `deploy/Dockerfile` — `python:3.12-slim` + `pip install cli-agent-runner` +
|
|
28
|
+
a commented-out slot for your agent CLI's install step.
|
|
29
|
+
- `deploy/docker-compose.yml` — builds the image, mounts your project (which
|
|
30
|
+
should contain `agent-runner.toml`) at `/work`, runs `serve`.
|
|
31
|
+
|
|
32
|
+
## Use it
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cp deploy/Dockerfile deploy/docker-compose.yml your-project/
|
|
36
|
+
cd your-project
|
|
37
|
+
# edit Dockerfile: uncomment/add your agent CLI's install step
|
|
38
|
+
# agent-runner.toml already exists at the project root (agent-runner init)
|
|
39
|
+
docker compose up -d
|
|
40
|
+
docker compose logs -f
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Not for tiny edge hosts
|
|
44
|
+
|
|
45
|
+
Same caveat as [container-pi.md](container-pi.md): don't reach for this on a
|
|
46
|
+
small edge board. It's a fat-host/homelab/server recipe.
|
|
47
|
+
|
|
48
|
+
See also: [docs/quickstart.md](../quickstart.md),
|
|
49
|
+
[SECURITY.md](../../SECURITY.md#containment).
|