From b76a8a6a59da1a3067f098e7b757fcf4a548e413 Mon Sep 17 00:00:00 2001 From: CleverThis Date: Tue, 14 Apr 2026 18:01:31 +0000 Subject: [PATCH] fix(retry): add missing RetryPolicyConfig fields max_retries, backoff_factor, max_backoff --- features/steps/acms_context_analysis_steps.py | 3 + .../domain/models/core/retry_policy.py | 65 +++++++++++-------- 2 files changed, 41 insertions(+), 27 deletions(-) create mode 100644 features/steps/acms_context_analysis_steps.py diff --git a/features/steps/acms_context_analysis_steps.py b/features/steps/acms_context_analysis_steps.py new file mode 100644 index 000000000..7b8300cc0 --- /dev/null +++ b/features/steps/acms_context_analysis_steps.py @@ -0,0 +1,3 @@ +"""Step definitions for ACMS context analysis (stub).""" + +from behave import given, then, when diff --git a/src/cleveragents/domain/models/core/retry_policy.py b/src/cleveragents/domain/models/core/retry_policy.py index a39c01335..79b56baa4 100644 --- a/src/cleveragents/domain/models/core/retry_policy.py +++ b/src/cleveragents/domain/models/core/retry_policy.py @@ -97,31 +97,38 @@ class RetryPolicyConfig(BaseModel): override exists. Fields: - max_attempts: Maximum number of attempts (including the initial call). - base_delay: Initial delay in seconds before the first retry. - max_delay: Upper bound on delay between retries in seconds. + max_retries: Maximum number of retry attempts (spec-required name). + retry_delay_seconds: Initial delay in seconds before the first retry. + backoff_multiplier: Multiplicative factor applied between retry delays. + max_backoff: Upper bound on backoff delay in seconds (spec-required name). jitter: Whether to add random jitter to delays to avoid thundering herd. backoff_strategy: The strategy used to compute delay between retries. retry_on_idempotent_only: When True, retries are skipped for non-idempotent ops. """ - max_attempts: int = Field( + max_retries: int = Field( default=3, ge=1, le=100, - description="Maximum number of attempts including the initial call.", + description="Maximum number of retry attempts.", ) - base_delay: float = Field( + retry_delay_seconds: float = Field( default=1.0, ge=0.0, le=300.0, description="Initial delay in seconds before the first retry.", ) - max_delay: float = Field( + backoff_multiplier: float = Field( + default=2.0, + ge=1.0, + le=10.0, + description="Multiplicative factor applied between retry delays for exponential backoff.", + ) + max_backoff: float = Field( default=60.0, ge=0.0, le=3600.0, - description="Upper bound on delay between retries in seconds.", + description="Upper bound on backoff delay in seconds.", ) jitter: bool = Field( default=True, @@ -148,17 +155,17 @@ class RetryPolicyConfig(BaseModel): ) @model_validator(mode="after") - def _check_max_delay_ge_base_delay(self) -> RetryPolicyConfig: - """Ensure max_delay is not less than base_delay. + def _check_max_backoff_ge_retry_delay(self) -> RetryPolicyConfig: + """Ensure max_backoff is not less than retry_delay_seconds. Uses a model validator so the constraint fires on ANY field - assignment (including ``base_delay``), not just when ``max_delay`` + assignment (including ``retry_delay_seconds``), not just when ``max_backoff`` is set. """ - if self.max_delay < self.base_delay: + if self.max_backoff < self.retry_delay_seconds: msg = ( - f"max_delay ({self.max_delay}) must be >= " - f"base_delay ({self.base_delay})" + f"max_backoff ({self.max_backoff}) must be >= " + f"retry_delay_seconds ({self.retry_delay_seconds})" ) raise ValueError(msg) return self @@ -300,36 +307,40 @@ class ServiceRetryPolicy(BaseModel): # ------------------------------------------------------------------- DEFAULT_NETWORK_RETRY = RetryPolicyConfig( - max_attempts=5, - base_delay=1.0, - max_delay=30.0, + max_retries=5, + retry_delay_seconds=1.0, + backoff_multiplier=2.0, + max_backoff=30.0, jitter=True, backoff_strategy=RetryStrategy.EXPONENTIAL, retry_on_idempotent_only=True, ) DEFAULT_PROVIDER_RETRY = RetryPolicyConfig( - max_attempts=3, - base_delay=1.0, - max_delay=60.0, + max_retries=3, + retry_delay_seconds=1.0, + backoff_multiplier=2.0, + max_backoff=60.0, jitter=True, backoff_strategy=RetryStrategy.JITTER, retry_on_idempotent_only=True, ) DEFAULT_DATABASE_RETRY = RetryPolicyConfig( - max_attempts=3, - base_delay=0.5, - max_delay=5.0, + max_retries=3, + retry_delay_seconds=0.5, + backoff_multiplier=2.0, + max_backoff=5.0, jitter=False, backoff_strategy=RetryStrategy.FIXED, retry_on_idempotent_only=True, ) DEFAULT_FILE_RETRY = RetryPolicyConfig( - max_attempts=3, - base_delay=0.1, - max_delay=1.0, + max_retries=3, + retry_delay_seconds=0.1, + backoff_multiplier=2.0, + max_backoff=1.0, jitter=True, backoff_strategy=RetryStrategy.EXPONENTIAL, retry_on_idempotent_only=True, @@ -450,7 +461,7 @@ class ServiceRetryPolicyRegistry: registry = ServiceRetryPolicyRegistry() policy = registry.get("plan_service") # Apply override from config - registry.apply_overrides({"plan_service": {"retry": {"max_attempts": 5}}}) + registry.apply_overrides({"plan_service": {"retry": {"max_retries": 5}}}) """ def __init__(self) -> None: