Source code for ralph.agents.invoke._invoke_options

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING

_INVOKE_OPTS_UNSET: object = object()

if TYPE_CHECKING:
    from collections.abc import Callable
    from pathlib import Path

    from ralph.agents.idle_watchdog import SubagentPidRegistry, WaitingStatusListener
    from ralph.agents.invoke._workspace import WorkspaceMonitor
    from ralph.agents.invoke._workspace_change_classifier import WorkspaceChangeClassifier
    from ralph.phases.required_artifacts import RequiredArtifact
    from ralph.process.monitor import SubagentPidSource


[docs] @dataclass(frozen=True) class InvokeOptions: """Options for agent invocation.""" # Optional workspace-monitor factory. Production callers leave this # ``None`` so :mod:`ralph.agents.invoke` constructs the real # ``WorkspaceMonitor`` (which starts a real watchdog observer). # Tests that only exercise routing / dispatch behavior inject a # factory that returns ``None`` (skip the observer entirely) or a # fake monitor so they do not block on the real watchdog observer's # ``start`` / ``stop`` cost under the 1-second per-test timeout # enforced by ``tests/conftest.py``. The factory signature mirrors # :func:`ralph.agents.invoke._start_workspace_monitor` so callers # can pass a thin ``lambda *args, **kwargs: None`` shortcut. workspace_monitor_factory: Callable[ [Path, WorkspaceChangeClassifier | None], WorkspaceMonitor | None ] | None = None model_flag: str | None = None session_id: str | None = None verbose: bool = False show_progress: bool = True workspace_path: Path | None = None extra_env: dict[str, str] | None = None unsafe_mode: bool = False idle_timeout_seconds: float | None = None drain_window_seconds: float | None = None max_waiting_on_child_seconds: float | None = None idle_poll_interval_seconds: float | None = None parent_exit_grace_seconds: float | None = None descendant_wait_timeout_seconds: float | None = None descendant_wait_poll_seconds: float | None = None process_exit_wait_seconds: float | None = None max_session_seconds: float | None = None waiting_status_interval_seconds: float | None = None suspect_waiting_on_child_seconds: float | None = None child_progress_ttl_seconds: float | None = None child_heartbeat_ttl_seconds: float | None = None child_stale_label_ttl_seconds: float | None = None child_exit_reconcile_seconds: float | None = None max_waiting_on_child_no_progress_seconds: float | None = None no_progress_quiet_seconds: float | None = None no_progress_quiet_minimum_invocation_seconds: float | None = None no_progress_quiet_heartbeat_ceiling_seconds: float | None | object = _INVOKE_OPTS_UNSET post_tool_result_progression_seconds: float | None = None repeated_error_consecutive_threshold: int | None = None repeated_error_window_count: int | None = None repeated_error_window_seconds: float | None = None activity_evidence_ttl_seconds: float | None = None workspace_change_weights: dict[str, float] | None = None process_monitor_enabled: bool | None = None subagent_output_capture_enabled: bool | None = None subagent_output_poll_interval_seconds: float | None = None os_descendant_only_ceiling_seconds: float | None | object = _INVOKE_OPTS_UNSET os_descendant_only_suspect_seconds: float | None | object = _INVOKE_OPTS_UNSET cpu_idle_seconds: float | None | object = _INVOKE_OPTS_UNSET log_growth_seconds: float | None | object = _INVOKE_OPTS_UNSET pure: bool = False system_prompt_file: str | None = None waiting_listener: WaitingStatusListener | None = None pre_output_listener: Callable[[], None] | None = None permission_prompt_listener: Callable[[str], None] | None = None required_artifact: RequiredArtifact | None = None explicit_completion_seen: bool = False captured_session_id: str | None = None initial_session_id: str | None = None settings_json: str | None = None stop_sentinel_path: Path | None = None # Live runtime signals from the pipeline that the watchdog consults # on every evaluate() call so the StuckClassifier gate can return # DUPLICATE_KILL (when the pipeline is already in a wait state) # or WAITING_ON_CONNECTIVITY (when the network is offline) and # defer the fire. Both providers are optional; the watchdog # falls back to "no live signal" when they are None. connectivity_state_provider: Callable[[], str | None] | None = None is_waiting_state_provider: Callable[[], bool] | None = None # R1 / R5 (Trustworthy Idle Watchdog spec): when the orchestrator # pre-builds a per-invocation ``SubagentPidRegistry`` and its # per-transport ``SubagentPidSource``, it threads them through # here so the SAME registry reaches both the strategy layer # (``strategy_for_command(..., subagent_pid_source=...)``) and # the parser layer (``stream_parsed_agent_activity(... # subagent_pid_registry=...)``). Without these fields, the # strategy layer builds a FRESH registry internally and the # parser-registered PIDs never reach the strategy's filtered # count -- the watchdog-visible filtered subagent count is # desynchronized from the parser's authoritative registration # set. The shared-registry contract is documented at # ``ralph/agents/registry.py:build_subagent_pid_registry``. # # Both fields default to ``None`` so the legacy direct-call # signature ``invoke_agent(config, prompt_file, options=...)`` # still works (the strategy layer builds a fresh registry # internally for backward compat with test fakes that pre-date # the R5 cross-transport wiring). subagent_pid_registry: SubagentPidRegistry | None = None subagent_pid_source: SubagentPidSource | None = None