Source code for ralph.agents.invoke._agent_inactivity_timeout_error
"""AgentInactivityTimeoutError — raised when an agent stalls without producing output."""
from __future__ import annotations
from ralph.agents.idle_watchdog import WatchdogFireReason
from ralph.agents.invoke._agent_invocation_error import AgentInvocationError
from ralph.agents.invoke._inactivity_timeout_opts import InactivityTimeoutOpts
[docs]
class AgentInactivityTimeoutError(AgentInvocationError):
"""Raised when an agent stalls without producing output."""
def __init__(
self,
agent_name: str,
timeout_seconds: float,
parsed_output: list[str] | None = None,
opts: InactivityTimeoutOpts | None = None,
) -> None:
_opts = opts or InactivityTimeoutOpts()
self.timeout_seconds = timeout_seconds
self.reason = _opts.reason
self.session_resume_safe = _opts.session_resume_safe
self.resumable_session_id = _opts.resumable_session_id
if _opts.reason == WatchdogFireReason.CHILDREN_PERSIST_TOO_LONG:
duration = f"{timeout_seconds:.0f}s"
base_msg = f"Agent kept child agents alive without producing output for {duration}"
if _opts.diagnostic:
cum = _opts.diagnostic.get("cumulative", "?")
scoped = _opts.diagnostic.get("scoped_child_active", "?")
oldest = _opts.diagnostic.get("oldest_child_seconds", "?")
ws_delta = _opts.diagnostic.get("workspace_event_delta", "?")
lo = _opts.diagnostic.get("lifecycle_only_activity", "?")
stderr_msg = (
f"{base_msg} (cumulative={cum}s, scoped_child_active={scoped},"
f" oldest_child_seconds={oldest}s, workspace_event_delta={ws_delta},"
f" lifecycle_only_activity={lo})"
)
else:
stderr_msg = base_msg
elif _opts.reason == WatchdogFireReason.SESSION_CEILING_EXCEEDED:
duration = f"{timeout_seconds:.0f}s"
stderr_msg = f"Agent exceeded max session wall-clock of {duration}"
elif _opts.reason == WatchdogFireReason.PROCESS_EXIT_HANG:
duration = f"{timeout_seconds:.0f}s"
stderr_msg = f"Agent subprocess closed stdout but did not exit within {duration}"
elif _opts.reason == WatchdogFireReason.STALLED_AFTER_TOOL_RESULT:
duration = f"{timeout_seconds:.0f}s"
tool_name = (
_opts.diagnostic.get("last_tool_name", "tool") if _opts.diagnostic else "tool"
)
stderr_msg = (
f"Agent produced no follow-up output for {duration} after receiving a tool result"
f" (last_tool={tool_name})"
)
elif _opts.reason == WatchdogFireReason.REPEATED_ERROR_LOOP:
fingerprint = _opts.diagnostic.get("error_fingerprint") if _opts.diagnostic else None
detail = f" (last error: {fingerprint})" if fingerprint else ""
stderr_msg = (
"Agent repeated the same error without making forward progress"
f"{detail}; aborting the retry loop"
)
elif _opts.reason == WatchdogFireReason.REPEATED_IDENTICAL_TOOL_CALL:
tool_name = _opts.diagnostic.get("tool_name", "tool") if _opts.diagnostic else "tool"
args_preview = (
_opts.diagnostic.get("tool_args_preview") if _opts.diagnostic else None
)
detail = f" args={args_preview}" if args_preview else ""
stderr_msg = (
f"Agent repeated the same {tool_name} tool call{detail} without making"
" forward progress; aborting the identical-tool-call loop"
)
elif _opts.reason == WatchdogFireReason.NO_PROGRESS_QUIET:
duration = f"{timeout_seconds:.0f}s"
stderr_msg = (
"Agent produced no output and only stale-progress children "
f"for {duration} (no_progress_quiet trip)"
)
elif _opts.reason == WatchdogFireReason.NO_OUTPUT_AT_START:
duration = f"{timeout_seconds:.0f}s"
stderr_msg = (
f"Agent produced no output for {duration} (no_output_at_start trip"
" — invocation never recorded any activity)"
)
else:
stderr_msg = f"Agent produced no output for {timeout_seconds:.0f}s"
super().__init__(
agent_name,
-1,
stderr_msg,
list(parsed_output) if parsed_output is not None else [],
)
__all__ = ["AgentInactivityTimeoutError"]