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