Skip to main content

ralph/contracts/config/
retry.rs

1//! Runner retry/backoff configuration for transient failure handling.
2//!
3//! Responsibilities:
4//! - Define retry config struct and merge behavior.
5//!
6//! Not handled here:
7//! - Actual retry logic (see runner invocation modules).
8
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12/// Runner retry/backoff configuration for transient failure handling.
13///
14/// Controls automatic retry behavior when runner invocations fail with
15/// transient errors (rate limits, temporary unavailability, network issues).
16/// Distinct from webhook retry settings (`agent.webhook.retry_*`).
17#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
18#[serde(default, deny_unknown_fields)]
19pub struct RunnerRetryConfig {
20    /// Total attempts (including initial). Default: 3.
21    #[schemars(range(min = 1, max = 20))]
22    pub max_attempts: Option<u32>,
23
24    /// Base backoff in milliseconds. Default: 1000.
25    #[schemars(range(min = 0, max = 600_000))]
26    pub base_backoff_ms: Option<u32>,
27
28    /// Exponential multiplier. Default: 2.0.
29    #[schemars(range(min = 1.0, max = 10.0))]
30    pub multiplier: Option<f64>,
31
32    /// Max backoff cap in milliseconds. Default: 30000.
33    #[schemars(range(min = 0, max = 600_000))]
34    pub max_backoff_ms: Option<u32>,
35
36    /// Jitter ratio in \[0,1\]. Default: 0.2 (20% variance).
37    #[schemars(range(min = 0.0, max = 1.0))]
38    pub jitter_ratio: Option<f64>,
39}
40
41impl RunnerRetryConfig {
42    /// Leaf-wise merge: other.Some overrides self, other.None preserves self
43    pub fn merge_from(&mut self, other: Self) {
44        if other.max_attempts.is_some() {
45            self.max_attempts = other.max_attempts;
46        }
47        if other.base_backoff_ms.is_some() {
48            self.base_backoff_ms = other.base_backoff_ms;
49        }
50        if other.multiplier.is_some() {
51            self.multiplier = other.multiplier;
52        }
53        if other.max_backoff_ms.is_some() {
54            self.max_backoff_ms = other.max_backoff_ms;
55        }
56        if other.jitter_ratio.is_some() {
57            self.jitter_ratio = other.jitter_ratio;
58        }
59    }
60}