1use crate::constants::{defaults, execution, llm_generation, prompt_budget};
2use crate::types::{
3 ReasoningEffortLevel, SystemPromptMode, ToolDocumentationMode, UiSurfacePreference,
4 VerbosityLevel,
5};
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8
9const DEFAULT_CHECKPOINTS_ENABLED: bool = true;
10const DEFAULT_MAX_SNAPSHOTS: usize = 50;
11const DEFAULT_MAX_AGE_DAYS: u64 = 30;
12
13#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
15#[derive(Debug, Clone, Deserialize, Serialize)]
16pub struct AgentConfig {
17 #[serde(default = "default_provider")]
19 pub provider: String,
20
21 #[serde(default = "default_api_key_env")]
23 pub api_key_env: String,
24
25 #[serde(default = "default_model")]
27 pub default_model: String,
28
29 #[serde(default = "default_theme")]
31 pub theme: String,
32
33 #[serde(default)]
37 pub system_prompt_mode: SystemPromptMode,
38
39 #[serde(default)]
45 pub tool_documentation_mode: ToolDocumentationMode,
46
47 #[serde(default = "default_enable_split_tool_results")]
54 pub enable_split_tool_results: bool,
55
56 #[serde(default = "default_todo_planning_mode")]
58 pub todo_planning_mode: bool,
59
60 #[serde(default)]
62 pub ui_surface: UiSurfacePreference,
63
64 #[serde(default = "default_max_conversation_turns")]
66 pub max_conversation_turns: usize,
67
68 #[serde(default = "default_idle_turn_limit")]
71 pub idle_turn_limit: usize,
72
73 #[serde(default = "default_reasoning_effort")]
76 pub reasoning_effort: ReasoningEffortLevel,
77
78 #[serde(default = "default_verbosity")]
81 pub verbosity: VerbosityLevel,
82
83 #[serde(default = "default_temperature")]
88 pub temperature: f32,
89
90 #[serde(default = "default_refine_temperature")]
94 pub refine_temperature: f32,
95
96 #[serde(default = "default_enable_self_review")]
98 pub enable_self_review: bool,
99
100 #[serde(default = "default_max_review_passes")]
102 pub max_review_passes: usize,
103
104 #[serde(default = "default_refine_prompts_enabled")]
106 pub refine_prompts_enabled: bool,
107
108 #[serde(default = "default_refine_max_passes")]
110 pub refine_prompts_max_passes: usize,
111
112 #[serde(default)]
114 pub refine_prompts_model: String,
115
116 #[serde(default)]
120 pub small_model: AgentSmallModelConfig,
121
122 #[serde(default)]
124 pub prompt_suggestions: AgentPromptSuggestionsConfig,
125
126 #[serde(default)]
128 pub onboarding: AgentOnboardingConfig,
129
130 #[serde(default = "default_project_doc_max_bytes")]
132 pub project_doc_max_bytes: usize,
133
134 #[serde(default)]
136 pub project_doc_fallback_filenames: Vec<String>,
137
138 #[serde(
140 default = "default_instruction_max_bytes",
141 alias = "rule_doc_max_bytes"
142 )]
143 pub instruction_max_bytes: usize,
144
145 #[serde(default, alias = "instruction_paths", alias = "instructions")]
147 pub instruction_files: Vec<String>,
148
149 #[serde(default)]
151 pub instruction_excludes: Vec<String>,
152
153 #[serde(default = "default_instruction_import_max_depth")]
155 pub instruction_import_max_depth: usize,
156
157 #[serde(default)]
159 pub persistent_memory: PersistentMemoryConfig,
160
161 #[serde(default, skip_serializing)]
167 pub custom_api_keys: BTreeMap<String, String>,
168
169 #[serde(default)]
176 pub credential_storage_mode: crate::auth::AuthCredentialsStoreMode,
177
178 #[serde(default)]
180 pub checkpointing: AgentCheckpointingConfig,
181
182 #[serde(default)]
184 pub vibe_coding: AgentVibeCodingConfig,
185
186 #[serde(default = "default_max_task_retries")]
190 pub max_task_retries: u32,
191
192 #[serde(default)]
194 pub harness: AgentHarnessConfig,
195
196 #[serde(default)]
198 pub codex_app_server: AgentCodexAppServerConfig,
199
200 #[serde(default = "default_include_temporal_context")]
203 pub include_temporal_context: bool,
204
205 #[serde(default)]
207 pub temporal_context_use_utc: bool,
208
209 #[serde(default = "default_include_working_directory")]
211 pub include_working_directory: bool,
212
213 #[serde(default)]
223 pub include_structured_reasoning_tags: Option<bool>,
224
225 #[serde(default)]
227 pub user_instructions: Option<String>,
228
229 #[serde(default = "default_require_plan_confirmation")]
233 pub require_plan_confirmation: bool,
234
235 #[serde(default)]
238 pub circuit_breaker: CircuitBreakerConfig,
239
240 #[serde(default)]
243 pub open_responses: OpenResponsesConfig,
244}
245
246#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
247#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
248#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
249#[serde(rename_all = "snake_case")]
250pub enum ContinuationPolicy {
251 Off,
252 ExecOnly,
253 #[default]
254 All,
255}
256
257impl ContinuationPolicy {
258 pub fn as_str(&self) -> &'static str {
259 match self {
260 Self::Off => "off",
261 Self::ExecOnly => "exec_only",
262 Self::All => "all",
263 }
264 }
265
266 pub fn parse(value: &str) -> Option<Self> {
267 let normalized = value.trim();
268 if normalized.eq_ignore_ascii_case("off") {
269 Some(Self::Off)
270 } else if normalized.eq_ignore_ascii_case("exec_only")
271 || normalized.eq_ignore_ascii_case("exec-only")
272 {
273 Some(Self::ExecOnly)
274 } else if normalized.eq_ignore_ascii_case("all") {
275 Some(Self::All)
276 } else {
277 None
278 }
279 }
280}
281
282impl<'de> Deserialize<'de> for ContinuationPolicy {
283 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
284 where
285 D: serde::Deserializer<'de>,
286 {
287 let raw = String::deserialize(deserializer)?;
288 Ok(Self::parse(&raw).unwrap_or_default())
289 }
290}
291
292#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
293#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
294#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
295#[serde(rename_all = "snake_case")]
296pub enum HarnessOrchestrationMode {
297 #[default]
298 Single,
299 PlanBuildEvaluate,
300}
301
302impl HarnessOrchestrationMode {
303 pub fn as_str(&self) -> &'static str {
304 match self {
305 Self::Single => "single",
306 Self::PlanBuildEvaluate => "plan_build_evaluate",
307 }
308 }
309
310 pub fn parse(value: &str) -> Option<Self> {
311 let normalized = value.trim();
312 if normalized.eq_ignore_ascii_case("single") {
313 Some(Self::Single)
314 } else if normalized.eq_ignore_ascii_case("plan_build_evaluate")
315 || normalized.eq_ignore_ascii_case("plan-build-evaluate")
316 || normalized.eq_ignore_ascii_case("planner_generator_evaluator")
317 || normalized.eq_ignore_ascii_case("planner-generator-evaluator")
318 {
319 Some(Self::PlanBuildEvaluate)
320 } else {
321 None
322 }
323 }
324}
325
326impl<'de> Deserialize<'de> for HarnessOrchestrationMode {
327 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
328 where
329 D: serde::Deserializer<'de>,
330 {
331 let raw = String::deserialize(deserializer)?;
332 Ok(Self::parse(&raw).unwrap_or_default())
333 }
334}
335
336#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
337#[derive(Debug, Clone, Deserialize, Serialize)]
338pub struct AgentHarnessConfig {
339 #[serde(default = "default_harness_max_tool_calls_per_turn")]
341 pub max_tool_calls_per_turn: usize,
342 #[serde(default = "default_harness_max_tool_wall_clock_secs")]
344 pub max_tool_wall_clock_secs: u64,
345 #[serde(default = "default_harness_max_tool_retries")]
347 pub max_tool_retries: u32,
348 #[serde(default = "default_harness_max_parallel_tool_calls")]
351 pub max_parallel_tool_calls: usize,
352 #[serde(default = "default_harness_auto_compaction_enabled")]
356 pub auto_compaction_enabled: bool,
357 #[serde(default)]
361 pub auto_compaction_threshold_tokens: Option<u64>,
362 #[serde(default)]
364 pub tool_result_clearing: ToolResultClearingConfig,
365 #[serde(default)]
367 pub max_budget_usd: Option<f64>,
368 #[serde(default)]
370 pub continuation_policy: ContinuationPolicy,
371 #[serde(default)]
374 pub event_log_path: Option<String>,
375 #[serde(default)]
377 pub orchestration_mode: HarnessOrchestrationMode,
378 #[serde(default = "default_harness_max_revision_rounds")]
380 pub max_revision_rounds: usize,
381}
382
383impl Default for AgentHarnessConfig {
384 fn default() -> Self {
385 Self {
386 max_tool_calls_per_turn: default_harness_max_tool_calls_per_turn(),
387 max_tool_wall_clock_secs: default_harness_max_tool_wall_clock_secs(),
388 max_tool_retries: default_harness_max_tool_retries(),
389 max_parallel_tool_calls: default_harness_max_parallel_tool_calls(),
390 auto_compaction_enabled: default_harness_auto_compaction_enabled(),
391 auto_compaction_threshold_tokens: None,
392 tool_result_clearing: ToolResultClearingConfig::default(),
393 max_budget_usd: None,
394 continuation_policy: ContinuationPolicy::default(),
395 event_log_path: None,
396 orchestration_mode: HarnessOrchestrationMode::default(),
397 max_revision_rounds: default_harness_max_revision_rounds(),
398 }
399 }
400}
401
402#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
403#[derive(Debug, Clone, Deserialize, Serialize)]
404pub struct ToolResultClearingConfig {
405 #[serde(default = "default_tool_result_clearing_enabled")]
406 pub enabled: bool,
407 #[serde(default = "default_tool_result_clearing_trigger_tokens")]
408 pub trigger_tokens: u64,
409 #[serde(default = "default_tool_result_clearing_keep_tool_uses")]
410 pub keep_tool_uses: u32,
411 #[serde(default = "default_tool_result_clearing_clear_at_least_tokens")]
412 pub clear_at_least_tokens: u64,
413 #[serde(default)]
414 pub clear_tool_inputs: bool,
415}
416
417impl Default for ToolResultClearingConfig {
418 fn default() -> Self {
419 Self {
420 enabled: default_tool_result_clearing_enabled(),
421 trigger_tokens: default_tool_result_clearing_trigger_tokens(),
422 keep_tool_uses: default_tool_result_clearing_keep_tool_uses(),
423 clear_at_least_tokens: default_tool_result_clearing_clear_at_least_tokens(),
424 clear_tool_inputs: false,
425 }
426 }
427}
428
429impl ToolResultClearingConfig {
430 pub fn validate(&self) -> Result<(), String> {
431 if self.trigger_tokens == 0 {
432 return Err("tool_result_clearing.trigger_tokens must be greater than 0".to_string());
433 }
434 if self.keep_tool_uses == 0 {
435 return Err("tool_result_clearing.keep_tool_uses must be greater than 0".to_string());
436 }
437 if self.clear_at_least_tokens == 0 {
438 return Err(
439 "tool_result_clearing.clear_at_least_tokens must be greater than 0".to_string(),
440 );
441 }
442 Ok(())
443 }
444}
445
446#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
447#[derive(Debug, Clone, Deserialize, Serialize)]
448pub struct AgentCodexAppServerConfig {
449 #[serde(default = "default_codex_app_server_command")]
451 pub command: String,
452 #[serde(default = "default_codex_app_server_args")]
454 pub args: Vec<String>,
455 #[serde(default = "default_codex_app_server_startup_timeout_secs")]
457 pub startup_timeout_secs: u64,
458 #[serde(default = "default_codex_app_server_experimental_features")]
461 pub experimental_features: bool,
462}
463
464impl Default for AgentCodexAppServerConfig {
465 fn default() -> Self {
466 Self {
467 command: default_codex_app_server_command(),
468 args: default_codex_app_server_args(),
469 startup_timeout_secs: default_codex_app_server_startup_timeout_secs(),
470 experimental_features: default_codex_app_server_experimental_features(),
471 }
472 }
473}
474
475#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
476#[derive(Debug, Clone, Deserialize, Serialize)]
477pub struct CircuitBreakerConfig {
478 #[serde(default = "default_circuit_breaker_enabled")]
480 pub enabled: bool,
481
482 #[serde(default = "default_failure_threshold")]
484 pub failure_threshold: u32,
485
486 #[serde(default = "default_pause_on_open")]
488 pub pause_on_open: bool,
489
490 #[serde(default = "default_max_open_circuits")]
492 pub max_open_circuits: usize,
493
494 #[serde(default = "default_recovery_cooldown")]
496 pub recovery_cooldown: u64,
497}
498
499impl Default for CircuitBreakerConfig {
500 fn default() -> Self {
501 Self {
502 enabled: default_circuit_breaker_enabled(),
503 failure_threshold: default_failure_threshold(),
504 pause_on_open: default_pause_on_open(),
505 max_open_circuits: default_max_open_circuits(),
506 recovery_cooldown: default_recovery_cooldown(),
507 }
508 }
509}
510
511#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
517#[derive(Debug, Clone, Deserialize, Serialize)]
518pub struct OpenResponsesConfig {
519 #[serde(default)]
523 pub enabled: bool,
524
525 #[serde(default = "default_open_responses_emit_events")]
529 pub emit_events: bool,
530
531 #[serde(default = "default_open_responses_include_extensions")]
534 pub include_extensions: bool,
535
536 #[serde(default = "default_open_responses_map_tool_calls")]
539 pub map_tool_calls: bool,
540
541 #[serde(default = "default_open_responses_include_reasoning")]
544 pub include_reasoning: bool,
545}
546
547impl Default for OpenResponsesConfig {
548 fn default() -> Self {
549 Self {
550 enabled: false, emit_events: default_open_responses_emit_events(),
552 include_extensions: default_open_responses_include_extensions(),
553 map_tool_calls: default_open_responses_map_tool_calls(),
554 include_reasoning: default_open_responses_include_reasoning(),
555 }
556 }
557}
558
559#[inline]
560const fn default_open_responses_emit_events() -> bool {
561 true }
563
564#[inline]
565const fn default_open_responses_include_extensions() -> bool {
566 true }
568
569#[inline]
570const fn default_open_responses_map_tool_calls() -> bool {
571 true }
573
574#[inline]
575const fn default_open_responses_include_reasoning() -> bool {
576 true }
578
579#[inline]
580fn default_codex_app_server_command() -> String {
581 "codex".to_string()
582}
583
584#[inline]
585fn default_codex_app_server_args() -> Vec<String> {
586 vec!["app-server".to_string()]
587}
588
589#[inline]
590const fn default_codex_app_server_startup_timeout_secs() -> u64 {
591 10
592}
593
594#[inline]
595const fn default_codex_app_server_experimental_features() -> bool {
596 false
597}
598
599impl Default for AgentConfig {
600 fn default() -> Self {
601 Self {
602 provider: default_provider(),
603 api_key_env: default_api_key_env(),
604 default_model: default_model(),
605 theme: default_theme(),
606 system_prompt_mode: SystemPromptMode::default(),
607 tool_documentation_mode: ToolDocumentationMode::default(),
608 enable_split_tool_results: default_enable_split_tool_results(),
609 todo_planning_mode: default_todo_planning_mode(),
610 ui_surface: UiSurfacePreference::default(),
611 max_conversation_turns: default_max_conversation_turns(),
612 idle_turn_limit: default_idle_turn_limit(),
613 reasoning_effort: default_reasoning_effort(),
614 verbosity: default_verbosity(),
615 temperature: default_temperature(),
616 refine_temperature: default_refine_temperature(),
617 enable_self_review: default_enable_self_review(),
618 max_review_passes: default_max_review_passes(),
619 refine_prompts_enabled: default_refine_prompts_enabled(),
620 refine_prompts_max_passes: default_refine_max_passes(),
621 refine_prompts_model: String::new(),
622 small_model: AgentSmallModelConfig::default(),
623 prompt_suggestions: AgentPromptSuggestionsConfig::default(),
624 onboarding: AgentOnboardingConfig::default(),
625 project_doc_max_bytes: default_project_doc_max_bytes(),
626 project_doc_fallback_filenames: Vec::new(),
627 instruction_max_bytes: default_instruction_max_bytes(),
628 instruction_files: Vec::new(),
629 instruction_excludes: Vec::new(),
630 instruction_import_max_depth: default_instruction_import_max_depth(),
631 persistent_memory: PersistentMemoryConfig::default(),
632 custom_api_keys: BTreeMap::new(),
633 credential_storage_mode: crate::auth::AuthCredentialsStoreMode::default(),
634 checkpointing: AgentCheckpointingConfig::default(),
635 vibe_coding: AgentVibeCodingConfig::default(),
636 max_task_retries: default_max_task_retries(),
637 harness: AgentHarnessConfig::default(),
638 codex_app_server: AgentCodexAppServerConfig::default(),
639 include_temporal_context: default_include_temporal_context(),
640 temporal_context_use_utc: false, include_working_directory: default_include_working_directory(),
642 include_structured_reasoning_tags: None,
643 user_instructions: None,
644 require_plan_confirmation: default_require_plan_confirmation(),
645 circuit_breaker: CircuitBreakerConfig::default(),
646 open_responses: OpenResponsesConfig::default(),
647 }
648 }
649}
650
651impl AgentConfig {
652 pub fn should_include_structured_reasoning_tags(&self) -> bool {
654 self.include_structured_reasoning_tags.unwrap_or(matches!(
655 self.system_prompt_mode,
656 SystemPromptMode::Specialized
657 ))
658 }
659
660 pub fn validate_llm_params(&self) -> Result<(), String> {
662 if !(0.0..=1.0).contains(&self.temperature) {
664 return Err(format!(
665 "temperature must be between 0.0 and 1.0, got {}",
666 self.temperature
667 ));
668 }
669
670 if !(0.0..=1.0).contains(&self.refine_temperature) {
671 return Err(format!(
672 "refine_temperature must be between 0.0 and 1.0, got {}",
673 self.refine_temperature
674 ));
675 }
676
677 if self.instruction_import_max_depth == 0 {
678 return Err("instruction_import_max_depth must be greater than 0".to_string());
679 }
680
681 self.persistent_memory.validate()?;
682 self.harness.tool_result_clearing.validate()?;
683
684 Ok(())
685 }
686}
687
688#[inline]
690fn default_provider() -> String {
691 defaults::DEFAULT_PROVIDER.into()
692}
693
694#[inline]
695fn default_api_key_env() -> String {
696 defaults::DEFAULT_API_KEY_ENV.into()
697}
698
699#[inline]
700fn default_model() -> String {
701 defaults::DEFAULT_MODEL.into()
702}
703
704#[inline]
705fn default_theme() -> String {
706 defaults::DEFAULT_THEME.into()
707}
708
709#[inline]
710const fn default_todo_planning_mode() -> bool {
711 true
712}
713
714#[inline]
715const fn default_enable_split_tool_results() -> bool {
716 true }
718
719#[inline]
720const fn default_max_conversation_turns() -> usize {
721 defaults::DEFAULT_MAX_CONVERSATION_TURNS
722}
723
724#[inline]
725const fn default_idle_turn_limit() -> usize {
726 execution::IDLE_TURN_LIMIT
727}
728
729#[inline]
730fn default_reasoning_effort() -> ReasoningEffortLevel {
731 ReasoningEffortLevel::None
732}
733
734#[inline]
735fn default_verbosity() -> VerbosityLevel {
736 VerbosityLevel::default()
737}
738
739#[inline]
740const fn default_temperature() -> f32 {
741 llm_generation::DEFAULT_TEMPERATURE
742}
743
744#[inline]
745const fn default_refine_temperature() -> f32 {
746 llm_generation::DEFAULT_REFINE_TEMPERATURE
747}
748
749#[inline]
750const fn default_enable_self_review() -> bool {
751 false
752}
753
754#[inline]
755const fn default_max_review_passes() -> usize {
756 1
757}
758
759#[inline]
760const fn default_refine_prompts_enabled() -> bool {
761 false
762}
763
764#[inline]
765const fn default_refine_max_passes() -> usize {
766 1
767}
768
769#[inline]
770const fn default_project_doc_max_bytes() -> usize {
771 prompt_budget::DEFAULT_MAX_BYTES
772}
773
774#[inline]
775const fn default_instruction_max_bytes() -> usize {
776 prompt_budget::DEFAULT_MAX_BYTES
777}
778
779#[inline]
780const fn default_instruction_import_max_depth() -> usize {
781 5
782}
783
784#[inline]
785const fn default_max_task_retries() -> u32 {
786 2 }
788
789#[inline]
790const fn default_harness_max_tool_calls_per_turn() -> usize {
791 defaults::DEFAULT_MAX_TOOL_CALLS_PER_TURN
792}
793
794#[inline]
795const fn default_harness_max_tool_wall_clock_secs() -> u64 {
796 defaults::DEFAULT_MAX_TOOL_WALL_CLOCK_SECS
797}
798
799#[inline]
800const fn default_harness_max_tool_retries() -> u32 {
801 defaults::DEFAULT_MAX_TOOL_RETRIES
802}
803
804#[inline]
805const fn default_harness_max_parallel_tool_calls() -> usize {
806 4 }
808
809#[inline]
810const fn default_harness_auto_compaction_enabled() -> bool {
811 false
812}
813
814#[inline]
815const fn default_tool_result_clearing_enabled() -> bool {
816 false
817}
818
819#[inline]
820const fn default_tool_result_clearing_trigger_tokens() -> u64 {
821 100_000
822}
823
824#[inline]
825const fn default_tool_result_clearing_keep_tool_uses() -> u32 {
826 3
827}
828
829#[inline]
830const fn default_tool_result_clearing_clear_at_least_tokens() -> u64 {
831 30_000
832}
833
834#[inline]
835const fn default_harness_max_revision_rounds() -> usize {
836 2
837}
838
839#[inline]
840const fn default_include_temporal_context() -> bool {
841 true }
843
844#[inline]
845const fn default_include_working_directory() -> bool {
846 true }
848
849#[inline]
850const fn default_require_plan_confirmation() -> bool {
851 true }
853
854#[inline]
855const fn default_circuit_breaker_enabled() -> bool {
856 true }
858
859#[inline]
860const fn default_failure_threshold() -> u32 {
861 7 }
863
864#[inline]
865const fn default_pause_on_open() -> bool {
866 true }
868
869#[inline]
870const fn default_max_open_circuits() -> usize {
871 3 }
873
874#[inline]
875const fn default_recovery_cooldown() -> u64 {
876 60 }
878
879#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
880#[derive(Debug, Clone, Deserialize, Serialize)]
881pub struct AgentCheckpointingConfig {
882 #[serde(default = "default_checkpointing_enabled")]
884 pub enabled: bool,
885
886 #[serde(default)]
888 pub storage_dir: Option<String>,
889
890 #[serde(default = "default_checkpointing_max_snapshots")]
892 pub max_snapshots: usize,
893
894 #[serde(default = "default_checkpointing_max_age_days")]
896 pub max_age_days: Option<u64>,
897}
898
899impl Default for AgentCheckpointingConfig {
900 fn default() -> Self {
901 Self {
902 enabled: default_checkpointing_enabled(),
903 storage_dir: None,
904 max_snapshots: default_checkpointing_max_snapshots(),
905 max_age_days: default_checkpointing_max_age_days(),
906 }
907 }
908}
909
910#[inline]
911const fn default_checkpointing_enabled() -> bool {
912 DEFAULT_CHECKPOINTS_ENABLED
913}
914
915#[inline]
916const fn default_checkpointing_max_snapshots() -> usize {
917 DEFAULT_MAX_SNAPSHOTS
918}
919
920#[inline]
921const fn default_checkpointing_max_age_days() -> Option<u64> {
922 Some(DEFAULT_MAX_AGE_DAYS)
923}
924
925#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
930#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
931pub struct MemoriesConfig {
932 #[serde(default = "default_memories_generate")]
935 pub generate_memories: bool,
936
937 #[serde(default = "default_memories_use")]
939 pub use_memories: bool,
940
941 #[serde(default)]
943 pub extract_model: Option<String>,
944
945 #[serde(default)]
947 pub consolidation_model: Option<String>,
948}
949
950impl Default for MemoriesConfig {
951 fn default() -> Self {
952 Self {
953 generate_memories: default_memories_generate(),
954 use_memories: default_memories_use(),
955 extract_model: None,
956 consolidation_model: None,
957 }
958 }
959}
960
961#[inline]
962const fn default_memories_generate() -> bool {
963 true
964}
965
966#[inline]
967const fn default_memories_use() -> bool {
968 true
969}
970
971#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
972#[derive(Debug, Clone, Deserialize, Serialize)]
973pub struct PersistentMemoryConfig {
974 #[serde(default = "default_persistent_memory_enabled")]
976 pub enabled: bool,
977
978 #[serde(default = "default_persistent_memory_auto_write")]
980 pub auto_write: bool,
981
982 #[serde(default)]
984 pub directory_override: Option<String>,
985
986 #[serde(default = "default_persistent_memory_startup_line_limit")]
988 pub startup_line_limit: usize,
989
990 #[serde(default = "default_persistent_memory_startup_byte_limit")]
992 pub startup_byte_limit: usize,
993
994 #[serde(default)]
996 pub memories: MemoriesConfig,
997}
998
999impl Default for PersistentMemoryConfig {
1000 fn default() -> Self {
1001 Self {
1002 enabled: default_persistent_memory_enabled(),
1003 auto_write: default_persistent_memory_auto_write(),
1004 directory_override: None,
1005 startup_line_limit: default_persistent_memory_startup_line_limit(),
1006 startup_byte_limit: default_persistent_memory_startup_byte_limit(),
1007 memories: MemoriesConfig::default(),
1008 }
1009 }
1010}
1011
1012impl PersistentMemoryConfig {
1013 pub fn validate(&self) -> Result<(), String> {
1014 if self.startup_line_limit == 0 {
1015 return Err("persistent_memory.startup_line_limit must be greater than 0".to_string());
1016 }
1017
1018 if self.startup_byte_limit == 0 {
1019 return Err("persistent_memory.startup_byte_limit must be greater than 0".to_string());
1020 }
1021
1022 Ok(())
1023 }
1024}
1025
1026#[inline]
1027const fn default_persistent_memory_enabled() -> bool {
1028 false
1029}
1030
1031#[inline]
1032const fn default_persistent_memory_auto_write() -> bool {
1033 true
1034}
1035
1036#[inline]
1037const fn default_persistent_memory_startup_line_limit() -> usize {
1038 200
1039}
1040
1041#[inline]
1042const fn default_persistent_memory_startup_byte_limit() -> usize {
1043 25 * 1024
1044}
1045
1046#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1047#[derive(Debug, Clone, Deserialize, Serialize)]
1048pub struct AgentOnboardingConfig {
1049 #[serde(default = "default_onboarding_enabled")]
1051 pub enabled: bool,
1052
1053 #[serde(default = "default_intro_text")]
1055 pub intro_text: String,
1056
1057 #[serde(default = "default_show_project_overview")]
1059 pub include_project_overview: bool,
1060
1061 #[serde(default = "default_show_language_summary")]
1063 pub include_language_summary: bool,
1064
1065 #[serde(default = "default_show_guideline_highlights")]
1067 pub include_guideline_highlights: bool,
1068
1069 #[serde(default = "default_show_usage_tips_in_welcome")]
1071 pub include_usage_tips_in_welcome: bool,
1072
1073 #[serde(default = "default_show_recommended_actions_in_welcome")]
1075 pub include_recommended_actions_in_welcome: bool,
1076
1077 #[serde(default = "default_guideline_highlight_limit")]
1079 pub guideline_highlight_limit: usize,
1080
1081 #[serde(default = "default_usage_tips")]
1083 pub usage_tips: Vec<String>,
1084
1085 #[serde(default = "default_recommended_actions")]
1087 pub recommended_actions: Vec<String>,
1088
1089 #[serde(default)]
1091 pub chat_placeholder: Option<String>,
1092}
1093
1094impl Default for AgentOnboardingConfig {
1095 fn default() -> Self {
1096 Self {
1097 enabled: default_onboarding_enabled(),
1098 intro_text: default_intro_text(),
1099 include_project_overview: default_show_project_overview(),
1100 include_language_summary: default_show_language_summary(),
1101 include_guideline_highlights: default_show_guideline_highlights(),
1102 include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
1103 include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
1104 guideline_highlight_limit: default_guideline_highlight_limit(),
1105 usage_tips: default_usage_tips(),
1106 recommended_actions: default_recommended_actions(),
1107 chat_placeholder: None,
1108 }
1109 }
1110}
1111
1112#[inline]
1113const fn default_onboarding_enabled() -> bool {
1114 true
1115}
1116
1117const DEFAULT_INTRO_TEXT: &str =
1118 "Let's get oriented. I preloaded workspace context so we can move fast.";
1119
1120#[inline]
1121fn default_intro_text() -> String {
1122 DEFAULT_INTRO_TEXT.into()
1123}
1124
1125#[inline]
1126const fn default_show_project_overview() -> bool {
1127 true
1128}
1129
1130#[inline]
1131const fn default_show_language_summary() -> bool {
1132 false
1133}
1134
1135#[inline]
1136const fn default_show_guideline_highlights() -> bool {
1137 true
1138}
1139
1140#[inline]
1141const fn default_show_usage_tips_in_welcome() -> bool {
1142 false
1143}
1144
1145#[inline]
1146const fn default_show_recommended_actions_in_welcome() -> bool {
1147 false
1148}
1149
1150#[inline]
1151const fn default_guideline_highlight_limit() -> usize {
1152 3
1153}
1154
1155const DEFAULT_USAGE_TIPS: &[&str] = &[
1156 "Describe your current coding goal or ask for a quick status overview.",
1157 "Reference AGENTS.md guidelines when proposing changes.",
1158 "Prefer asking for targeted file reads or diffs before editing.",
1159];
1160
1161const DEFAULT_RECOMMENDED_ACTIONS: &[&str] = &[
1162 "Review the highlighted guidelines and share the task you want to tackle.",
1163 "Ask for a workspace tour if you need more context.",
1164];
1165
1166fn default_usage_tips() -> Vec<String> {
1167 DEFAULT_USAGE_TIPS.iter().map(|s| (*s).into()).collect()
1168}
1169
1170fn default_recommended_actions() -> Vec<String> {
1171 DEFAULT_RECOMMENDED_ACTIONS
1172 .iter()
1173 .map(|s| (*s).into())
1174 .collect()
1175}
1176
1177#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1187#[derive(Debug, Clone, Deserialize, Serialize)]
1188pub struct AgentSmallModelConfig {
1189 #[serde(default = "default_small_model_enabled")]
1191 pub enabled: bool,
1192
1193 #[serde(default)]
1196 pub model: String,
1197
1198 #[serde(default = "default_small_model_temperature")]
1200 pub temperature: f32,
1201
1202 #[serde(default = "default_small_model_for_large_reads")]
1204 pub use_for_large_reads: bool,
1205
1206 #[serde(default = "default_small_model_for_web_summary")]
1208 pub use_for_web_summary: bool,
1209
1210 #[serde(default = "default_small_model_for_git_history")]
1212 pub use_for_git_history: bool,
1213
1214 #[serde(default = "default_small_model_for_memory")]
1216 pub use_for_memory: bool,
1217}
1218
1219impl Default for AgentSmallModelConfig {
1220 fn default() -> Self {
1221 Self {
1222 enabled: default_small_model_enabled(),
1223 model: String::new(),
1224 temperature: default_small_model_temperature(),
1225 use_for_large_reads: default_small_model_for_large_reads(),
1226 use_for_web_summary: default_small_model_for_web_summary(),
1227 use_for_git_history: default_small_model_for_git_history(),
1228 use_for_memory: default_small_model_for_memory(),
1229 }
1230 }
1231}
1232
1233#[inline]
1234const fn default_small_model_enabled() -> bool {
1235 true }
1237
1238#[inline]
1239const fn default_small_model_temperature() -> f32 {
1240 0.3 }
1242
1243#[inline]
1244const fn default_small_model_for_large_reads() -> bool {
1245 true
1246}
1247
1248#[inline]
1249const fn default_small_model_for_web_summary() -> bool {
1250 true
1251}
1252
1253#[inline]
1254const fn default_small_model_for_git_history() -> bool {
1255 true
1256}
1257
1258#[inline]
1259const fn default_small_model_for_memory() -> bool {
1260 true
1261}
1262
1263#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1265#[derive(Debug, Clone, Deserialize, Serialize)]
1266pub struct AgentPromptSuggestionsConfig {
1267 #[serde(default = "default_prompt_suggestions_enabled")]
1269 pub enabled: bool,
1270
1271 #[serde(default)]
1274 pub model: String,
1275
1276 #[serde(default = "default_prompt_suggestions_temperature")]
1278 pub temperature: f32,
1279
1280 #[serde(default = "default_prompt_suggestions_show_cost_notice")]
1282 pub show_cost_notice: bool,
1283}
1284
1285impl Default for AgentPromptSuggestionsConfig {
1286 fn default() -> Self {
1287 Self {
1288 enabled: default_prompt_suggestions_enabled(),
1289 model: String::new(),
1290 temperature: default_prompt_suggestions_temperature(),
1291 show_cost_notice: default_prompt_suggestions_show_cost_notice(),
1292 }
1293 }
1294}
1295
1296#[inline]
1297const fn default_prompt_suggestions_enabled() -> bool {
1298 true
1299}
1300
1301#[inline]
1302const fn default_prompt_suggestions_temperature() -> f32 {
1303 0.3
1304}
1305
1306#[inline]
1307const fn default_prompt_suggestions_show_cost_notice() -> bool {
1308 true
1309}
1310
1311#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1316#[derive(Debug, Clone, Deserialize, Serialize)]
1317pub struct AgentVibeCodingConfig {
1318 #[serde(default = "default_vibe_coding_enabled")]
1320 pub enabled: bool,
1321
1322 #[serde(default = "default_vibe_min_prompt_length")]
1324 pub min_prompt_length: usize,
1325
1326 #[serde(default = "default_vibe_min_prompt_words")]
1328 pub min_prompt_words: usize,
1329
1330 #[serde(default = "default_vibe_entity_resolution")]
1332 pub enable_entity_resolution: bool,
1333
1334 #[serde(default = "default_vibe_entity_cache")]
1336 pub entity_index_cache: String,
1337
1338 #[serde(default = "default_vibe_max_entity_matches")]
1340 pub max_entity_matches: usize,
1341
1342 #[serde(default = "default_vibe_track_workspace")]
1344 pub track_workspace_state: bool,
1345
1346 #[serde(default = "default_vibe_max_recent_files")]
1348 pub max_recent_files: usize,
1349
1350 #[serde(default = "default_vibe_track_values")]
1352 pub track_value_history: bool,
1353
1354 #[serde(default = "default_vibe_conversation_memory")]
1356 pub enable_conversation_memory: bool,
1357
1358 #[serde(default = "default_vibe_max_memory_turns")]
1360 pub max_memory_turns: usize,
1361
1362 #[serde(default = "default_vibe_pronoun_resolution")]
1364 pub enable_pronoun_resolution: bool,
1365
1366 #[serde(default = "default_vibe_proactive_context")]
1368 pub enable_proactive_context: bool,
1369
1370 #[serde(default = "default_vibe_max_context_files")]
1372 pub max_context_files: usize,
1373
1374 #[serde(default = "default_vibe_max_snippets_per_file")]
1376 pub max_context_snippets_per_file: usize,
1377
1378 #[serde(default = "default_vibe_max_search_results")]
1380 pub max_search_results: usize,
1381
1382 #[serde(default = "default_vibe_value_inference")]
1384 pub enable_relative_value_inference: bool,
1385}
1386
1387impl Default for AgentVibeCodingConfig {
1388 fn default() -> Self {
1389 Self {
1390 enabled: default_vibe_coding_enabled(),
1391 min_prompt_length: default_vibe_min_prompt_length(),
1392 min_prompt_words: default_vibe_min_prompt_words(),
1393 enable_entity_resolution: default_vibe_entity_resolution(),
1394 entity_index_cache: default_vibe_entity_cache(),
1395 max_entity_matches: default_vibe_max_entity_matches(),
1396 track_workspace_state: default_vibe_track_workspace(),
1397 max_recent_files: default_vibe_max_recent_files(),
1398 track_value_history: default_vibe_track_values(),
1399 enable_conversation_memory: default_vibe_conversation_memory(),
1400 max_memory_turns: default_vibe_max_memory_turns(),
1401 enable_pronoun_resolution: default_vibe_pronoun_resolution(),
1402 enable_proactive_context: default_vibe_proactive_context(),
1403 max_context_files: default_vibe_max_context_files(),
1404 max_context_snippets_per_file: default_vibe_max_snippets_per_file(),
1405 max_search_results: default_vibe_max_search_results(),
1406 enable_relative_value_inference: default_vibe_value_inference(),
1407 }
1408 }
1409}
1410
1411#[inline]
1413const fn default_vibe_coding_enabled() -> bool {
1414 false }
1416
1417#[inline]
1418const fn default_vibe_min_prompt_length() -> usize {
1419 5
1420}
1421
1422#[inline]
1423const fn default_vibe_min_prompt_words() -> usize {
1424 2
1425}
1426
1427#[inline]
1428const fn default_vibe_entity_resolution() -> bool {
1429 true
1430}
1431
1432#[inline]
1433fn default_vibe_entity_cache() -> String {
1434 ".vtcode/entity_index.json".into()
1435}
1436
1437#[inline]
1438const fn default_vibe_max_entity_matches() -> usize {
1439 5
1440}
1441
1442#[inline]
1443const fn default_vibe_track_workspace() -> bool {
1444 true
1445}
1446
1447#[inline]
1448const fn default_vibe_max_recent_files() -> usize {
1449 20
1450}
1451
1452#[inline]
1453const fn default_vibe_track_values() -> bool {
1454 true
1455}
1456
1457#[inline]
1458const fn default_vibe_conversation_memory() -> bool {
1459 true
1460}
1461
1462#[inline]
1463const fn default_vibe_max_memory_turns() -> usize {
1464 50
1465}
1466
1467#[inline]
1468const fn default_vibe_pronoun_resolution() -> bool {
1469 true
1470}
1471
1472#[inline]
1473const fn default_vibe_proactive_context() -> bool {
1474 true
1475}
1476
1477#[inline]
1478const fn default_vibe_max_context_files() -> usize {
1479 3
1480}
1481
1482#[inline]
1483const fn default_vibe_max_snippets_per_file() -> usize {
1484 20
1485}
1486
1487#[inline]
1488const fn default_vibe_max_search_results() -> usize {
1489 5
1490}
1491
1492#[inline]
1493const fn default_vibe_value_inference() -> bool {
1494 true
1495}
1496
1497#[cfg(test)]
1498mod tests {
1499 use super::*;
1500
1501 #[test]
1502 fn test_continuation_policy_defaults_and_parses() {
1503 assert_eq!(ContinuationPolicy::default(), ContinuationPolicy::All);
1504 assert_eq!(
1505 ContinuationPolicy::parse("off"),
1506 Some(ContinuationPolicy::Off)
1507 );
1508 assert_eq!(
1509 ContinuationPolicy::parse("exec-only"),
1510 Some(ContinuationPolicy::ExecOnly)
1511 );
1512 assert_eq!(
1513 ContinuationPolicy::parse("all"),
1514 Some(ContinuationPolicy::All)
1515 );
1516 assert_eq!(ContinuationPolicy::parse("invalid"), None);
1517 }
1518
1519 #[test]
1520 fn test_harness_config_continuation_policy_deserializes_with_fallback() {
1521 let parsed: AgentHarnessConfig =
1522 toml::from_str("continuation_policy = \"all\"").expect("valid harness config");
1523 assert_eq!(parsed.continuation_policy, ContinuationPolicy::All);
1524
1525 let fallback: AgentHarnessConfig =
1526 toml::from_str("continuation_policy = \"unexpected\"").expect("fallback config");
1527 assert_eq!(fallback.continuation_policy, ContinuationPolicy::All);
1528 }
1529
1530 #[test]
1531 fn test_harness_orchestration_mode_defaults_and_parses() {
1532 assert_eq!(
1533 HarnessOrchestrationMode::default(),
1534 HarnessOrchestrationMode::Single
1535 );
1536 assert_eq!(
1537 HarnessOrchestrationMode::parse("single"),
1538 Some(HarnessOrchestrationMode::Single)
1539 );
1540 assert_eq!(
1541 HarnessOrchestrationMode::parse("plan_build_evaluate"),
1542 Some(HarnessOrchestrationMode::PlanBuildEvaluate)
1543 );
1544 assert_eq!(
1545 HarnessOrchestrationMode::parse("planner-generator-evaluator"),
1546 Some(HarnessOrchestrationMode::PlanBuildEvaluate)
1547 );
1548 assert_eq!(HarnessOrchestrationMode::parse("unexpected"), None);
1549 }
1550
1551 #[test]
1552 fn test_harness_config_orchestration_deserializes_with_fallback() {
1553 let parsed: AgentHarnessConfig =
1554 toml::from_str("orchestration_mode = \"plan_build_evaluate\"")
1555 .expect("valid harness config");
1556 assert_eq!(
1557 parsed.orchestration_mode,
1558 HarnessOrchestrationMode::PlanBuildEvaluate
1559 );
1560 assert_eq!(parsed.max_revision_rounds, 2);
1561
1562 let fallback: AgentHarnessConfig =
1563 toml::from_str("orchestration_mode = \"unexpected\"").expect("fallback config");
1564 assert_eq!(
1565 fallback.orchestration_mode,
1566 HarnessOrchestrationMode::Single
1567 );
1568 }
1569
1570 #[test]
1571 fn test_plan_confirmation_config_default() {
1572 let config = AgentConfig::default();
1573 assert!(config.require_plan_confirmation);
1574 }
1575
1576 #[test]
1577 fn test_persistent_memory_is_disabled_by_default() {
1578 let config = AgentConfig::default();
1579 assert!(!config.persistent_memory.enabled);
1580 assert!(config.persistent_memory.auto_write);
1581 }
1582
1583 #[test]
1584 fn test_tool_result_clearing_defaults() {
1585 let config = AgentConfig::default();
1586 let clearing = config.harness.tool_result_clearing;
1587
1588 assert!(!clearing.enabled);
1589 assert_eq!(clearing.trigger_tokens, 100_000);
1590 assert_eq!(clearing.keep_tool_uses, 3);
1591 assert_eq!(clearing.clear_at_least_tokens, 30_000);
1592 assert!(!clearing.clear_tool_inputs);
1593 }
1594
1595 #[test]
1596 fn test_codex_app_server_experimental_features_default_to_disabled() {
1597 let config = AgentConfig::default();
1598
1599 assert!(!config.codex_app_server.experimental_features);
1600 }
1601
1602 #[test]
1603 fn test_codex_app_server_experimental_features_parse_from_toml() {
1604 let parsed: AgentCodexAppServerConfig = toml::from_str(
1605 r#"
1606 command = "codex"
1607 args = ["app-server"]
1608 startup_timeout_secs = 15
1609 experimental_features = true
1610 "#,
1611 )
1612 .expect("valid codex app-server config");
1613
1614 assert!(parsed.experimental_features);
1615 assert_eq!(parsed.startup_timeout_secs, 15);
1616 }
1617
1618 #[test]
1619 fn test_tool_result_clearing_parses_and_validates() {
1620 let parsed: AgentHarnessConfig = toml::from_str(
1621 r#"
1622 [tool_result_clearing]
1623 enabled = true
1624 trigger_tokens = 123456
1625 keep_tool_uses = 6
1626 clear_at_least_tokens = 4096
1627 clear_tool_inputs = true
1628 "#,
1629 )
1630 .expect("valid harness config");
1631
1632 assert!(parsed.tool_result_clearing.enabled);
1633 assert_eq!(parsed.tool_result_clearing.trigger_tokens, 123_456);
1634 assert_eq!(parsed.tool_result_clearing.keep_tool_uses, 6);
1635 assert_eq!(parsed.tool_result_clearing.clear_at_least_tokens, 4_096);
1636 assert!(parsed.tool_result_clearing.clear_tool_inputs);
1637 assert!(parsed.tool_result_clearing.validate().is_ok());
1638 }
1639
1640 #[test]
1641 fn test_tool_result_clearing_rejects_zero_values() {
1642 let clearing = ToolResultClearingConfig {
1643 trigger_tokens: 0,
1644 ..ToolResultClearingConfig::default()
1645 };
1646 assert!(clearing.validate().is_err());
1647
1648 let clearing = ToolResultClearingConfig {
1649 keep_tool_uses: 0,
1650 ..ToolResultClearingConfig::default()
1651 };
1652 assert!(clearing.validate().is_err());
1653
1654 let clearing = ToolResultClearingConfig {
1655 clear_at_least_tokens: 0,
1656 ..ToolResultClearingConfig::default()
1657 };
1658 assert!(clearing.validate().is_err());
1659 }
1660
1661 #[test]
1662 fn test_structured_reasoning_defaults_follow_prompt_mode() {
1663 let default_mode = AgentConfig {
1664 system_prompt_mode: SystemPromptMode::Default,
1665 ..Default::default()
1666 };
1667 assert!(!default_mode.should_include_structured_reasoning_tags());
1668
1669 let specialized_mode = AgentConfig {
1670 system_prompt_mode: SystemPromptMode::Specialized,
1671 ..Default::default()
1672 };
1673 assert!(specialized_mode.should_include_structured_reasoning_tags());
1674
1675 let minimal_mode = AgentConfig {
1676 system_prompt_mode: SystemPromptMode::Minimal,
1677 ..Default::default()
1678 };
1679 assert!(!minimal_mode.should_include_structured_reasoning_tags());
1680
1681 let lightweight_mode = AgentConfig {
1682 system_prompt_mode: SystemPromptMode::Lightweight,
1683 ..Default::default()
1684 };
1685 assert!(!lightweight_mode.should_include_structured_reasoning_tags());
1686 }
1687
1688 #[test]
1689 fn test_structured_reasoning_explicit_override() {
1690 let mut config = AgentConfig {
1691 system_prompt_mode: SystemPromptMode::Minimal,
1692 include_structured_reasoning_tags: Some(true),
1693 ..AgentConfig::default()
1694 };
1695 assert!(config.should_include_structured_reasoning_tags());
1696
1697 config.include_structured_reasoning_tags = Some(false);
1698 assert!(!config.should_include_structured_reasoning_tags());
1699 }
1700}