Skip to main content

rskit_agent/types/
limit.rs

1use serde::{Deserialize, Serialize};
2
3/// Agent limit or cancellation failure with locked precedence.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5#[non_exhaustive]
6pub enum AgentLimitError {
7    /// Parent cancellation wins over all budget failures.
8    Cancelled,
9    /// Wall-clock deadline exceeded.
10    WallClockExceeded,
11    /// Maximum tool-call budget exceeded.
12    MaxToolCallsExceeded,
13    /// Maximum token budget exceeded.
14    MaxTokensExceeded,
15    /// Maximum turn budget exceeded.
16    MaxTurnsExceeded,
17}
18
19impl AgentLimitError {
20    /// Return precedence where larger values win.
21    pub const fn precedence(self) -> u8 {
22        match self {
23            Self::Cancelled => 5,
24            Self::WallClockExceeded => 4,
25            Self::MaxToolCallsExceeded => 3,
26            Self::MaxTokensExceeded => 2,
27            Self::MaxTurnsExceeded => 1,
28        }
29    }
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35
36    #[test]
37    fn cancellation_outranks_every_budget_failure() {
38        let budgets = [
39            AgentLimitError::WallClockExceeded,
40            AgentLimitError::MaxToolCallsExceeded,
41            AgentLimitError::MaxTokensExceeded,
42            AgentLimitError::MaxTurnsExceeded,
43        ];
44        for budget in budgets {
45            assert!(
46                AgentLimitError::Cancelled.precedence() > budget.precedence(),
47                "cancellation must win over {budget:?}",
48            );
49        }
50    }
51
52    #[test]
53    fn precedence_is_strict_total_order_with_locked_ranking() {
54        // Highest-to-lowest priority is a stable, documented contract.
55        let ranked = [
56            AgentLimitError::Cancelled,
57            AgentLimitError::WallClockExceeded,
58            AgentLimitError::MaxToolCallsExceeded,
59            AgentLimitError::MaxTokensExceeded,
60            AgentLimitError::MaxTurnsExceeded,
61        ];
62        for pair in ranked.windows(2) {
63            assert!(
64                pair[0].precedence() > pair[1].precedence(),
65                "{:?} must outrank {:?}",
66                pair[0],
67                pair[1],
68            );
69        }
70
71        // Selecting the winner by max precedence is order-independent.
72        let mut shuffled = ranked;
73        shuffled.reverse();
74        let winner = shuffled
75            .into_iter()
76            .max_by_key(|error| error.precedence())
77            .expect("non-empty");
78        assert_eq!(winner, AgentLimitError::Cancelled);
79    }
80}