Source code for ralph.pipeline.agent_retry_decision

"""Shared retry-decision core for failed agent invocations.

There is exactly ONE place where both the pipeline executor
(`_invoke_agent_with_recovery`) and the direct-MCP recovery loop
(`run_with_direct_mcp_recovery`) decide whether a failed attempt is retryable
and what the canonical next-attempt intent is. Routing both callers through
`resolve_retry_intent` makes the retry semantics impossible to drift apart.

drift-audit: This module owns the recovery-decision pipeline seam — the
ONLY `FailureClassifier(` site in `ralph/pipeline/`. The 8-file allowlist
is INVARIANT (8 files; 5 actual sites). When extending the recovery
decision surface, do NOT add a 6th `FailureClassifier(` site here — the
seam is already a single owner. New callers MUST route through
`should_reset_tool_registry(...)` (ralph/recovery/failure_classifier.py)
for classification, and through `resolve_retry_intent(...)` (this
module) for the canonical next-attempt intent. PA-003 procedure: pin
counts are invariant — do not raise them.
"""

from __future__ import annotations

from ralph.pipeline.agent_retry_intent import AgentRetryIntent, agent_retry_intent_for_failure
from ralph.pipeline.retryable_failure import retryable_agent_failure_reason
from ralph.recovery.failure_classifier import FailureClassifier


[docs] def resolve_retry_intent( exc: Exception, *, phase: str, agent: str | None, session_id: str | None, inactivity_error_type: type[Exception], ) -> AgentRetryIntent | None: """Return the canonical retry intent for a failed attempt, or None. None means the failure is not retryable. Otherwise the returned intent is the single source of truth for the next attempt's session action and tool-registry reset. ``session_id`` is the caller-resolved observed session id (the intent clears it when the failure semantics demand a fresh session). """ if type(exc).__name__ == "PiContextExhaustedExitError": return AgentRetryIntent( failure_reason=type(exc).__name__, skip_same_agent_retries=True, ) if retryable_agent_failure_reason(exc, inactivity_error_type) is None: return None classified = FailureClassifier().classify(exc, phase=phase, agent=agent) return agent_retry_intent_for_failure( failure_reason=type(exc).__name__, session_id=session_id, reset_tool_registry=classified.reset_tool_registry, )
__all__ = ["resolve_retry_intent"]