diff --git a/.github/workflows/run-eval.yml b/.github/workflows/run-eval.yml index 6705b5fe9d..b84047a747 100644 --- a/.github/workflows/run-eval.yml +++ b/.github/workflows/run-eval.yml @@ -29,6 +29,7 @@ on: type: choice options: - '1' + - '10' - '50' - '200' - '500' diff --git a/openhands-agent-server/openhands/agent_server/conversation_service.py b/openhands-agent-server/openhands/agent_server/conversation_service.py index b5ce248bba..e7201049c4 100644 --- a/openhands-agent-server/openhands/agent_server/conversation_service.py +++ b/openhands-agent-server/openhands/agent_server/conversation_service.py @@ -1,10 +1,12 @@ import asyncio import logging +import time from dataclasses import dataclass, field from pathlib import Path from uuid import UUID, uuid4 import httpx +from pydantic import ValidationError from openhands.agent_server.config import Config, WebhookSpec from openhands.agent_server.event_service import EventService @@ -33,13 +35,32 @@ def _compose_conversation_info( stored: StoredConversation, state: ConversationState ) -> ConversationInfo: - return ConversationInfo( - **state.model_dump(), - title=stored.title, - metrics=stored.metrics, - created_at=stored.created_at, - updated_at=stored.updated_at, - ) + # Retry with exponential backoff for transient race conditions where + # model_dump() can return incomplete data during concurrent state mutations. + # TODO: Implement proper locking in ConversationState.model_dump() to + # eliminate this race condition. See: + # https://github.com/OpenHands/software-agent-sdk/issues/1557 + last_error: ValidationError | None = None + for attempt in range(4): + try: + return ConversationInfo( + **state.model_dump(), + title=stored.title, + metrics=stored.metrics, + created_at=stored.created_at, + updated_at=stored.updated_at, + ) + except ValidationError as e: + last_error = e + delay = 0.1 * (2**attempt) # 0.1, 0.2, 0.4, 0.8 + logger.warning( + f"ConversationInfo validation failed (attempt {attempt + 1}/4), " + f"retrying after {delay}s: {e}" + ) + time.sleep(delay) + + assert last_error is not None + raise last_error @dataclass