vtcode_config/core/agent/
harness.rs1use serde::{Deserialize, Serialize};
4
5use crate::constants::defaults;
6use crate::constants::tool_limits;
7
8use super::approval::{AsyncApprovalConfig, ConfidenceEscalationConfig, SkepticPanelConfig};
9use super::{ContextResetMode, ContinuationPolicy, HarnessOrchestrationMode};
10
11#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
12#[derive(Debug, Clone, Deserialize, Serialize)]
13pub struct AgentHarnessConfig {
14 #[serde(default)]
16 pub inspection: InspectionConfig,
17 #[serde(default = "default_harness_max_tool_calls_per_turn")]
20 pub max_tool_calls_per_turn: usize,
21 #[serde(default = "default_harness_max_tool_wall_clock_secs")]
23 pub max_tool_wall_clock_secs: u64,
24 #[serde(default = "default_harness_max_tool_retries")]
26 pub max_tool_retries: u32,
27 #[serde(default = "default_harness_max_parallel_tool_calls")]
30 pub max_parallel_tool_calls: usize,
31 #[serde(default = "default_harness_auto_compaction_enabled")]
37 pub auto_compaction_enabled: bool,
38 #[serde(default)]
44 pub auto_compaction_threshold_tokens: Option<u64>,
45 #[serde(default)]
50 pub auto_compaction_instructions: Option<String>,
51 #[serde(default)]
57 pub auto_compaction_pause_after: bool,
58 #[serde(default = "default_harness_compact_on_model_switch")]
63 pub compact_on_model_switch: bool,
64 #[serde(default)]
70 pub tool_result_clearing: ToolResultClearingConfig,
71 #[serde(default)]
73 pub max_budget_usd: Option<f64>,
74 #[serde(default = "default_harness_budget_warning_threshold")]
77 pub budget_warning_threshold: f64,
78 #[serde(default)]
80 pub continuation_policy: ContinuationPolicy,
81 #[serde(default)]
86 pub context_reset_mode: ContextResetMode,
87 #[serde(default = "default_harness_context_reset_stall_threshold")]
91 pub context_reset_stall_threshold: u32,
92 #[serde(default)]
96 pub event_log_path: Option<String>,
97 #[serde(default)]
99 pub orchestration_mode: HarnessOrchestrationMode,
100 #[serde(default = "default_harness_max_revision_rounds")]
102 pub max_revision_rounds: usize,
103 #[serde(default)]
112 pub confidence_escalation: ConfidenceEscalationConfig,
113 #[serde(default)]
119 async_approval: AsyncApprovalConfig,
120 #[serde(default)]
125 pub skeptic_panel: SkepticPanelConfig,
126 #[serde(default)]
131 pub verification: VerificationAutoRecoveryConfig,
132 #[serde(default)]
136 pub continuation: TrackerContinuationConfig,
137}
138
139impl Default for AgentHarnessConfig {
140 fn default() -> Self {
141 Self {
142 inspection: InspectionConfig::default(),
143 max_tool_calls_per_turn: default_harness_max_tool_calls_per_turn(),
144 max_tool_wall_clock_secs: default_harness_max_tool_wall_clock_secs(),
145 max_tool_retries: default_harness_max_tool_retries(),
146 max_parallel_tool_calls: default_harness_max_parallel_tool_calls(),
147 auto_compaction_enabled: default_harness_auto_compaction_enabled(),
148 auto_compaction_threshold_tokens: None,
149 auto_compaction_instructions: None,
150 auto_compaction_pause_after: false,
151 compact_on_model_switch: default_harness_compact_on_model_switch(),
152 tool_result_clearing: ToolResultClearingConfig::default(),
153 max_budget_usd: None,
154 budget_warning_threshold: default_harness_budget_warning_threshold(),
155 continuation_policy: ContinuationPolicy::default(),
156 context_reset_mode: ContextResetMode::default(),
157 context_reset_stall_threshold: default_harness_context_reset_stall_threshold(),
158 event_log_path: None,
159 orchestration_mode: HarnessOrchestrationMode::default(),
160 max_revision_rounds: default_harness_max_revision_rounds(),
161 confidence_escalation: ConfidenceEscalationConfig::default(),
162 async_approval: AsyncApprovalConfig::default(),
163 skeptic_panel: SkepticPanelConfig::default(),
164 verification: VerificationAutoRecoveryConfig::default(),
165 continuation: TrackerContinuationConfig::default(),
166 }
167 }
168}
169#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
171#[derive(Debug, Clone, Default, Deserialize, Serialize)]
172pub struct InspectionConfig {
173 #[serde(default)]
174 pub mode: InspectionMode,
175}
176
177#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
179#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
180#[serde(rename_all = "snake_case")]
181pub enum InspectionMode {
182 Enforce,
183 #[default]
184 Warn,
185 Off,
186}
187#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
194#[derive(Debug, Clone, Deserialize, Serialize)]
195pub struct TrackerContinuationConfig {
196 #[serde(default = "default_tracker_auto_continue")]
199 pub auto_continue_tracker: bool,
200 #[serde(default = "default_tracker_cross_turn_turns")]
207 pub cross_turn_turns: u8,
208}
209
210impl Default for TrackerContinuationConfig {
211 fn default() -> Self {
212 Self {
213 auto_continue_tracker: default_tracker_auto_continue(),
214 cross_turn_turns: default_tracker_cross_turn_turns(),
215 }
216 }
217}
218
219#[inline]
220const fn default_tracker_auto_continue() -> bool {
221 true
222}
223
224#[inline]
225const fn default_tracker_cross_turn_turns() -> u8 {
226 32
227}
228#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
235#[derive(Debug, Clone, Deserialize, Serialize)]
236pub struct VerificationAutoRecoveryConfig {
237 #[serde(default = "default_verification_auto_execute")]
241 pub auto_execute: bool,
242 #[serde(default = "default_verification_in_turn_attempts")]
245 pub in_turn_attempts: u8,
246 #[serde(default = "default_verification_cross_turn_turns")]
249 pub cross_turn_turns: u8,
250 #[serde(default)]
254 pub default_verifier_override: Option<String>,
255 #[serde(default = "default_verification_max_consecutive_failures")]
259 pub max_consecutive_failures: u8,
260}
261
262impl Default for VerificationAutoRecoveryConfig {
263 fn default() -> Self {
264 Self {
265 auto_execute: default_verification_auto_execute(),
266 in_turn_attempts: default_verification_in_turn_attempts(),
267 cross_turn_turns: default_verification_cross_turn_turns(),
268 default_verifier_override: None,
269 max_consecutive_failures: default_verification_max_consecutive_failures(),
270 }
271 }
272}
273#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
274#[derive(Debug, Clone, Deserialize, Serialize)]
275pub struct ToolResultClearingConfig {
276 #[serde(default = "default_tool_result_clearing_enabled")]
279 pub enabled: bool,
280 #[serde(default = "default_tool_result_clearing_trigger_tokens")]
286 pub trigger_tokens: u64,
287 #[serde(default = "default_tool_result_clearing_keep_tool_uses")]
291 pub keep_tool_uses: u32,
292 #[serde(default = "default_tool_result_clearing_keep_recent_tool_batches")]
297 pub keep_recent_tool_batches: u32,
298 #[serde(default = "default_tool_result_clearing_clear_at_least_tokens")]
299 pub clear_at_least_tokens: u64,
300 #[serde(default = "default_tool_result_clearing_clear_tool_inputs")]
306 pub clear_tool_inputs: bool,
307}
308
309impl Default for ToolResultClearingConfig {
310 fn default() -> Self {
311 Self {
312 enabled: default_tool_result_clearing_enabled(),
313 trigger_tokens: default_tool_result_clearing_trigger_tokens(),
314 keep_tool_uses: default_tool_result_clearing_keep_tool_uses(),
315 keep_recent_tool_batches: default_tool_result_clearing_keep_recent_tool_batches(),
316 clear_at_least_tokens: default_tool_result_clearing_clear_at_least_tokens(),
317 clear_tool_inputs: default_tool_result_clearing_clear_tool_inputs(),
318 }
319 }
320}
321
322impl ToolResultClearingConfig {
323 pub(crate) fn validate(&self) -> Result<(), String> {
324 if self.trigger_tokens == 0 {
325 return Err("tool_result_clearing.trigger_tokens must be greater than 0".to_string());
326 }
327 if self.keep_tool_uses == 0 {
328 return Err("tool_result_clearing.keep_tool_uses must be greater than 0".to_string());
329 }
330 if self.clear_at_least_tokens == 0 {
331 return Err("tool_result_clearing.clear_at_least_tokens must be greater than 0".to_string());
332 }
333 Ok(())
334 }
335}
336#[inline]
337const fn default_harness_max_tool_calls_per_turn() -> usize {
338 tool_limits::DEFAULT_MAX_TOOL_CALLS_PER_TURN
339}
340
341#[inline]
342const fn default_harness_max_tool_wall_clock_secs() -> u64 {
343 defaults::DEFAULT_MAX_TOOL_WALL_CLOCK_SECS
344}
345
346#[inline]
347const fn default_harness_max_tool_retries() -> u32 {
348 defaults::DEFAULT_MAX_TOOL_RETRIES
349}
350
351#[inline]
352const fn default_harness_max_parallel_tool_calls() -> usize {
353 4 }
355
356#[inline]
357const fn default_harness_auto_compaction_enabled() -> bool {
358 true
359}
360
361const fn default_harness_compact_on_model_switch() -> bool {
362 true
363}
364
365#[inline]
366const fn default_harness_context_reset_stall_threshold() -> u32 {
367 2
368}
369
370#[inline]
371const fn default_verification_auto_execute() -> bool {
372 true
373}
374
375#[inline]
376const fn default_verification_in_turn_attempts() -> u8 {
377 2
378}
379
380#[inline]
381const fn default_verification_cross_turn_turns() -> u8 {
382 2
383}
384
385#[inline]
386const fn default_verification_max_consecutive_failures() -> u8 {
387 3
388}
389
390#[inline]
391const fn default_tool_result_clearing_enabled() -> bool {
392 true
393}
394
395#[inline]
396const fn default_tool_result_clearing_trigger_tokens() -> u64 {
397 40_000
400}
401
402#[inline]
403const fn default_tool_result_clearing_keep_recent_tool_batches() -> u32 {
404 2
405}
406
407const fn default_tool_result_clearing_keep_tool_uses() -> u32 {
408 2
409}
410
411#[inline]
412const fn default_tool_result_clearing_clear_at_least_tokens() -> u64 {
413 30_000
414}
415
416#[inline]
417const fn default_tool_result_clearing_clear_tool_inputs() -> bool {
418 true
419}
420
421#[inline]
422const fn default_harness_max_revision_rounds() -> usize {
423 2
424}
425
426#[inline]
427const fn default_harness_budget_warning_threshold() -> f64 {
428 0.75
429}
430
431#[cfg(test)]
432mod inspection_tests {
433 use super::*;
434
435 #[test]
436 fn source_inspection_defaults_warns_and_parses_explicit_modes() {
437 assert_eq!(AgentHarnessConfig::default().inspection.mode, InspectionMode::Warn);
438 for text in ["", "[inspection]"] {
439 let config: AgentHarnessConfig = toml::from_str(text).unwrap();
440 assert_eq!(config.inspection.mode, InspectionMode::Warn);
441 }
442 for (text, expected) in [
443 ("enforce", InspectionMode::Enforce),
444 ("warn", InspectionMode::Warn),
445 ("off", InspectionMode::Off),
446 ] {
447 let config: AgentHarnessConfig = toml::from_str(&format!("[inspection]\nmode = '{text}'")).unwrap();
448 assert_eq!(config.inspection.mode, expected);
449 }
450 assert!(toml::from_str::<AgentHarnessConfig>("[inspection]\nmode = 'permissive'").is_err());
451 }
452}