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))]
238#[derive(Debug, Clone, Deserialize, Serialize)]
239pub struct VerificationAutoRecoveryConfig {
240 #[serde(default = "default_verification_auto_execute")]
244 pub auto_execute: bool,
245 #[serde(default = "default_verification_in_turn_attempts")]
250 pub in_turn_attempts: u8,
251 #[serde(default = "default_verification_cross_turn_turns")]
255 pub cross_turn_turns: u8,
256 #[serde(default)]
260 pub default_verifier_override: Option<String>,
261 #[serde(default = "default_verification_max_consecutive_failures")]
265 pub max_consecutive_failures: u8,
266}
267
268impl Default for VerificationAutoRecoveryConfig {
269 fn default() -> Self {
270 Self {
271 auto_execute: default_verification_auto_execute(),
272 in_turn_attempts: default_verification_in_turn_attempts(),
273 cross_turn_turns: default_verification_cross_turn_turns(),
274 default_verifier_override: None,
275 max_consecutive_failures: default_verification_max_consecutive_failures(),
276 }
277 }
278}
279#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
280#[derive(Debug, Clone, Deserialize, Serialize)]
281pub struct ToolResultClearingConfig {
282 #[serde(default = "default_tool_result_clearing_enabled")]
285 pub enabled: bool,
286 #[serde(default = "default_tool_result_clearing_trigger_tokens")]
292 pub trigger_tokens: u64,
293 #[serde(default = "default_tool_result_clearing_keep_tool_uses")]
297 pub keep_tool_uses: u32,
298 #[serde(default = "default_tool_result_clearing_keep_recent_tool_batches")]
303 pub keep_recent_tool_batches: u32,
304 #[serde(default = "default_tool_result_clearing_clear_at_least_tokens")]
305 pub clear_at_least_tokens: u64,
306 #[serde(default = "default_tool_result_clearing_clear_tool_inputs")]
312 pub clear_tool_inputs: bool,
313}
314
315impl Default for ToolResultClearingConfig {
316 fn default() -> Self {
317 Self {
318 enabled: default_tool_result_clearing_enabled(),
319 trigger_tokens: default_tool_result_clearing_trigger_tokens(),
320 keep_tool_uses: default_tool_result_clearing_keep_tool_uses(),
321 keep_recent_tool_batches: default_tool_result_clearing_keep_recent_tool_batches(),
322 clear_at_least_tokens: default_tool_result_clearing_clear_at_least_tokens(),
323 clear_tool_inputs: default_tool_result_clearing_clear_tool_inputs(),
324 }
325 }
326}
327
328impl ToolResultClearingConfig {
329 pub(crate) fn validate(&self) -> Result<(), String> {
330 if self.trigger_tokens == 0 {
331 return Err("tool_result_clearing.trigger_tokens must be greater than 0".to_string());
332 }
333 if self.keep_tool_uses == 0 {
334 return Err("tool_result_clearing.keep_tool_uses must be greater than 0".to_string());
335 }
336 if self.clear_at_least_tokens == 0 {
337 return Err("tool_result_clearing.clear_at_least_tokens must be greater than 0".to_string());
338 }
339 Ok(())
340 }
341}
342#[inline]
343const fn default_harness_max_tool_calls_per_turn() -> usize {
344 tool_limits::DEFAULT_MAX_TOOL_CALLS_PER_TURN
345}
346
347#[inline]
348const fn default_harness_max_tool_wall_clock_secs() -> u64 {
349 defaults::DEFAULT_MAX_TOOL_WALL_CLOCK_SECS
350}
351
352#[inline]
353const fn default_harness_max_tool_retries() -> u32 {
354 defaults::DEFAULT_MAX_TOOL_RETRIES
355}
356
357#[inline]
358const fn default_harness_max_parallel_tool_calls() -> usize {
359 4 }
361
362#[inline]
363const fn default_harness_auto_compaction_enabled() -> bool {
364 true
365}
366
367const fn default_harness_compact_on_model_switch() -> bool {
368 true
369}
370
371#[inline]
372const fn default_harness_context_reset_stall_threshold() -> u32 {
373 2
374}
375
376#[inline]
377const fn default_verification_auto_execute() -> bool {
378 true
379}
380
381#[inline]
382const fn default_verification_in_turn_attempts() -> u8 {
383 2
384}
385
386#[inline]
387const fn default_verification_cross_turn_turns() -> u8 {
388 2
389}
390
391#[inline]
392const fn default_verification_max_consecutive_failures() -> u8 {
393 3
394}
395
396#[inline]
397const fn default_tool_result_clearing_enabled() -> bool {
398 true
399}
400
401#[inline]
402const fn default_tool_result_clearing_trigger_tokens() -> u64 {
403 40_000
406}
407
408#[inline]
409const fn default_tool_result_clearing_keep_recent_tool_batches() -> u32 {
410 2
411}
412
413const fn default_tool_result_clearing_keep_tool_uses() -> u32 {
414 2
415}
416
417#[inline]
418const fn default_tool_result_clearing_clear_at_least_tokens() -> u64 {
419 30_000
420}
421
422#[inline]
423const fn default_tool_result_clearing_clear_tool_inputs() -> bool {
424 true
425}
426
427#[inline]
428const fn default_harness_max_revision_rounds() -> usize {
429 2
430}
431
432#[inline]
433const fn default_harness_budget_warning_threshold() -> f64 {
434 0.75
435}
436
437#[cfg(test)]
438mod inspection_tests {
439 use super::*;
440
441 #[test]
442 fn source_inspection_defaults_warns_and_parses_explicit_modes() {
443 assert_eq!(AgentHarnessConfig::default().inspection.mode, InspectionMode::Warn);
444 for text in ["", "[inspection]"] {
445 let config: AgentHarnessConfig = toml::from_str(text).unwrap();
446 assert_eq!(config.inspection.mode, InspectionMode::Warn);
447 }
448 for (text, expected) in [
449 ("enforce", InspectionMode::Enforce),
450 ("warn", InspectionMode::Warn),
451 ("off", InspectionMode::Off),
452 ] {
453 let config: AgentHarnessConfig = toml::from_str(&format!("[inspection]\nmode = '{text}'")).unwrap();
454 assert_eq!(config.inspection.mode, expected);
455 }
456 assert!(toml::from_str::<AgentHarnessConfig>("[inspection]\nmode = 'permissive'").is_err());
457 }
458}