Skip to main content

matrixcode_core/agent/core/
config.rs

1//! Agent configuration constants.
2//!
3//! This module defines configuration constants that were previously hardcoded
4//! throughout the agent module. By extracting these into a configuration struct,
5//! we enable:
6//! - Dynamic adjustment based on task complexity
7//! - User-configurable limits in config files
8//! - Better testability (can test edge cases)
9//! - Clearer documentation of defaults
10
11use crate::compress::CompressionConfig;
12
13/// Maximum number of iterations in the main loop.
14///
15/// This constant is used as the default for [`AgentConfig::max_iterations`].
16/// It is re-exported from the parent module for backward compatibility.
17///
18/// See [`AgentConfig::max_iterations`] for detailed documentation.
19pub const MAX_ITERATIONS: usize = 200;
20
21/// Agent runtime configuration.
22///
23/// Controls iteration limits, retry behavior, size thresholds, and compression settings.
24/// All defaults match the original hardcoded values for backward compatibility.
25#[derive(Debug, Clone)]
26pub struct AgentConfig {
27    /// Maximum number of iterations in the main loop.
28    ///
29    /// **Default**: 200
30    ///
31    /// **Why this value?**
32    /// - Sufficient for most common tasks (file edits, code review, simple builds)
33    /// - Prevents infinite loops and runaway operations
34    /// - Balances task completion with resource efficiency
35    ///
36    /// **What happens when limit is reached?**
37    /// - Agent stops execution gracefully
38    /// - User receives detailed warning message
39    /// - Can continue, break down task, or resume
40    ///
41    /// **Examples**:
42    /// - Simple task (edit file): ~5-10 iterations
43    /// - Medium task (refactor module): ~15-30 iterations
44    /// - Complex task (build system): ~40-50 iterations (may hit limit)
45    pub max_iterations: usize,
46
47    /// Maximum retry attempts for API calls.
48    ///
49    /// **Default**: 5
50    ///
51    /// **Why this value?**
52    /// - Handles transient network failures
53    /// - Balances resilience with response time
54    /// - Most failures resolve within 2-3 retries
55    pub max_retries: u32,
56
57    /// Delay between retry attempts (milliseconds).
58    ///
59    /// **Default**: 1000ms
60    ///
61    /// **Why this value?**
62    /// - Gives API server time to recover
63    /// - Not too long to frustrate users
64    /// - Often used with exponential backoff
65    pub retry_delay_ms: u64,
66
67    /// Maximum size for tool execution results (bytes).
68    ///
69    /// **Default**: 50,000 (50KB)
70    ///
71    /// **Why this value?**
72    /// - Prevents oversized responses from breaking API calls
73    /// - Fits within typical context window limits
74    /// - Most tool results are <10KB
75    ///
76    /// **Truncation behavior**:
77    /// - Results larger than this are truncated
78    /// - Truncation message added: "⚠️ Output truncated (X bytes total)"
79    /// - User can request specific portions if needed
80    pub max_tool_result_size: usize,
81
82    /// Maximum number of todo reminders per todo item.
83    ///
84    /// **Default**: 2
85    ///
86    /// **Why this value?**
87    /// - Prevents infinite loops when model doesn't update todo status
88    /// - Gives model multiple chances to notice pending work
89    /// - After limit, stops reminding (assumes model won't update)
90    pub max_todo_reminders: usize,
91
92    /// Iteration count threshold for warning user.
93    ///
94    /// **Default**: max_iterations - 10 (190)
95    ///
96    /// **Why this value?**
97    /// - Warns user 10 iterations before limit
98    /// - Gives time to decide: continue, break down task, or cancel
99    /// - Prevents surprising cutoff at exactly max_iterations
100    pub iteration_warning_threshold: usize,
101
102    /// Context compression configuration.
103    ///
104    /// Controls when and how to compress long conversations.
105    /// See [`CompressionConfig`] for details.
106    pub compression: CompressionConfig,
107
108    // === API Request Settings ===
109    /// Maximum tokens for API responses.
110    ///
111    /// **Default**: 4096 (from QUICK_ACTION_MAX_TOKENS)
112    pub max_tokens: u32,
113
114    /// Override for context window size.
115    ///
116    /// **Default**: None (use provider's default)
117    pub context_size_override: Option<u32>,
118
119    /// Enable thinking mode for extended reasoning.
120    ///
121    /// **Default**: false
122    pub think: bool,
123}
124
125impl Default for AgentConfig {
126    fn default() -> Self {
127        Self {
128            max_iterations: MAX_ITERATIONS,
129            max_retries: 5,
130            retry_delay_ms: 1000,
131            max_tool_result_size: 50_000,
132            max_todo_reminders: 2,
133            iteration_warning_threshold: MAX_ITERATIONS.saturating_sub(10),
134            compression: CompressionConfig::default(),
135            max_tokens: crate::constants::QUICK_ACTION_MAX_TOKENS,
136            context_size_override: None,
137            think: false,
138        }
139    }
140}
141
142impl AgentConfig {
143    /// Create a new config with all settings.
144    pub fn new(
145        max_tokens: u32,
146        context_size_override: Option<u32>,
147        think: bool,
148        compression: CompressionConfig,
149    ) -> Self {
150        Self {
151            max_tokens,
152            context_size_override,
153            think,
154            compression,
155            ..Self::default()
156        }
157    }
158
159    /// Create a new config with custom max_iterations.
160    ///
161    /// Automatically adjusts iteration_warning_threshold to max_iterations - 10.
162    pub fn with_max_iterations(max_iterations: usize) -> Self {
163        Self {
164            max_iterations,
165            iteration_warning_threshold: max_iterations.saturating_sub(10),
166            ..Self::default()
167        }
168    }
169
170    /// Create a new config with custom compression settings.
171    pub fn with_compression(compression: CompressionConfig) -> Self {
172        Self {
173            compression,
174            ..Self::default()
175        }
176    }
177
178    /// Create a config optimized for simple tasks.
179    ///
180    /// Use for: file edits, quick queries, simple tool calls.
181    /// Characteristics: lower iteration limit, smaller result size.
182    pub fn simple_task() -> Self {
183        Self {
184            max_iterations: 50,
185            iteration_warning_threshold: 40,
186            max_tool_result_size: 10_000,
187            ..Self::default()
188        }
189    }
190
191    /// Create a config optimized for complex tasks.
192    ///
193    /// Use for: refactoring, build systems, multi-file changes.
194    /// Characteristics: higher iteration limit, larger result size.
195    pub fn complex_task() -> Self {
196        Self {
197            max_iterations: 300,
198            iteration_warning_threshold: 290,
199            max_tool_result_size: 100_000,
200            max_todo_reminders: 3,
201            ..Self::default()
202        }
203    }
204
205    // === Accessor methods ===
206
207    /// Get max tokens for API responses.
208    pub fn max_tokens(&self) -> u32 {
209        self.max_tokens
210    }
211
212    /// Get context size override.
213    pub fn context_size_override(&self) -> Option<u32> {
214        self.context_size_override
215    }
216
217    /// Get thinking mode flag.
218    pub fn think(&self) -> bool {
219        self.think
220    }
221
222    /// Get compression config.
223    pub fn compression_config(&self) -> &CompressionConfig {
224        &self.compression
225    }
226
227    /// Get mutable compression config.
228    pub fn compression_config_mut(&mut self) -> &mut CompressionConfig {
229        &mut self.compression
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn test_default_config_matches_hardcoded_values() {
239        let config = AgentConfig::default();
240
241        // Verify all defaults match original hardcoded constants
242        assert_eq!(config.max_iterations, 200, "max_iterations default changed");
243        assert_eq!(config.max_retries, 5, "max_retries default changed");
244        assert_eq!(config.retry_delay_ms, 1000, "retry_delay_ms default changed");
245        assert_eq!(config.max_tool_result_size, 50_000, "max_tool_result_size default changed");
246        assert_eq!(config.max_todo_reminders, 2, "max_todo_reminders default changed");
247        assert_eq!(config.iteration_warning_threshold, 190, "iteration_warning_threshold default changed");
248    }
249
250    #[test]
251    fn test_with_max_iterations_adjusts_warning_threshold() {
252        let config = AgentConfig::with_max_iterations(100);
253
254        assert_eq!(config.max_iterations, 100);
255        assert_eq!(config.iteration_warning_threshold, 90, "warning threshold should be max - 10");
256    }
257
258    #[test]
259    fn test_with_max_iterations_handles_small_values() {
260        let config = AgentConfig::with_max_iterations(5);
261
262        // saturating_sub prevents negative threshold
263        assert_eq!(config.iteration_warning_threshold, 0, "threshold should use saturating_sub");
264    }
265
266    #[test]
267    fn test_simple_task_optimization() {
268        let config = AgentConfig::simple_task();
269
270        assert_eq!(config.max_iterations, 50, "simple tasks should have lower iteration limit");
271        assert_eq!(config.max_tool_result_size, 10_000, "simple tasks should have smaller result size");
272    }
273
274    #[test]
275    fn test_complex_task_optimization() {
276        let config = AgentConfig::complex_task();
277
278        assert_eq!(config.max_iterations, 300, "complex tasks should have higher iteration limit");
279        assert_eq!(config.max_tool_result_size, 100_000, "complex tasks should have larger result size");
280        assert_eq!(config.max_todo_reminders, 3, "complex tasks should allow more reminders");
281    }
282}