1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//! Types for retry, fallback, and rate-limiting policies.
//!
//! These declarative policy structs ([`RetryPolicy`], [`FallbackPolicy`],
//! [`RateLimiter`]) are the durability knobs the recursive harness applies to
//! the model call that every level of the recursion ultimately reduces to, so
//! the same resilience settings govern parent and nested calls alike. Each is
//! deterministically testable: backoff and the limiter take an explicit
//! `rand01` / `now` rather than reading a global clock or RNG.
/// Configures how a harness call is retried on transient failure.
///
/// Backoff grows exponentially: `initial_backoff_ms * multiplier^attempt`, then
/// capped at `max_backoff_ms`. When `jitter` is `true` the caller should use
/// [`RetryPolicy::backoff_for_attempt_with`] and supply a `[0, 1)` random
/// value; this avoids thundering-herd without making the implementation
/// non-deterministic for tests.
///
/// # Examples
///
/// ```
/// use tinyagents::harness::retry::RetryPolicy;
///
/// let policy = RetryPolicy::default();
/// assert!(policy.should_retry(0));
/// assert!(!policy.should_retry(3));
/// ```
/// Ordered list of model identifiers to try in sequence when the current model
/// fails.
///
/// The harness will move to `next_after` the current model on non-retryable
/// errors (or after retries are exhausted).
///
/// # Examples
///
/// ```
/// use tinyagents::harness::retry::FallbackPolicy;
///
/// let policy = FallbackPolicy { models: vec!["claude-3-5-sonnet".into(), "claude-3-haiku".into()] };
/// assert_eq!(policy.next_after("claude-3-5-sonnet"), Some("claude-3-haiku"));
/// assert_eq!(policy.next_after("claude-3-haiku"), None);
/// ```
/// A simple token-bucket rate limiter.
///
/// State is kept inside the struct behind a [`std::sync::Mutex`] so the same
/// instance can be shared across threads without wrapping in an `Arc<Mutex<...>>`
/// by the caller.
///
/// The caller must supply the current time (`now: Instant`) to every method so
/// the limiter is fully testable without real sleeping or timer injection.
///
/// # Examples
///
/// ```
/// use std::time::Instant;
/// use tinyagents::harness::retry::RateLimiter;
///
/// let mut limiter = RateLimiter::new(10, 5.0);
/// let now = Instant::now();
/// assert!(limiter.try_acquire(1, now));
/// ```
/// Inner mutable state of a [`RateLimiter`].
pub