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 PlanBuildEvaluate,
299 Single,
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)]
367 pub auto_compaction_instructions: Option<String>,
368 #[serde(default)]
374 pub auto_compaction_pause_after: bool,
375 #[serde(default)]
377 pub tool_result_clearing: ToolResultClearingConfig,
378 #[serde(default)]
380 pub max_budget_usd: Option<f64>,
381 #[serde(default)]
383 pub continuation_policy: ContinuationPolicy,
384 #[serde(default)]
387 pub event_log_path: Option<String>,
388 #[serde(default)]
390 pub orchestration_mode: HarnessOrchestrationMode,
391 #[serde(default = "default_harness_max_revision_rounds")]
393 pub max_revision_rounds: usize,
394}
395
396impl Default for AgentHarnessConfig {
397 fn default() -> Self {
398 Self {
399 max_tool_calls_per_turn: default_harness_max_tool_calls_per_turn(),
400 max_tool_wall_clock_secs: default_harness_max_tool_wall_clock_secs(),
401 max_tool_retries: default_harness_max_tool_retries(),
402 max_parallel_tool_calls: default_harness_max_parallel_tool_calls(),
403 auto_compaction_enabled: default_harness_auto_compaction_enabled(),
404 auto_compaction_threshold_tokens: None,
405 auto_compaction_instructions: None,
406 auto_compaction_pause_after: false,
407 tool_result_clearing: ToolResultClearingConfig::default(),
408 max_budget_usd: None,
409 continuation_policy: ContinuationPolicy::default(),
410 event_log_path: None,
411 orchestration_mode: HarnessOrchestrationMode::default(),
412 max_revision_rounds: default_harness_max_revision_rounds(),
413 }
414 }
415}
416
417#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
418#[derive(Debug, Clone, Deserialize, Serialize)]
419pub struct ToolResultClearingConfig {
420 #[serde(default = "default_tool_result_clearing_enabled")]
421 pub enabled: bool,
422 #[serde(default = "default_tool_result_clearing_trigger_tokens")]
423 pub trigger_tokens: u64,
424 #[serde(default = "default_tool_result_clearing_keep_tool_uses")]
425 pub keep_tool_uses: u32,
426 #[serde(default = "default_tool_result_clearing_clear_at_least_tokens")]
427 pub clear_at_least_tokens: u64,
428 #[serde(default)]
429 pub clear_tool_inputs: bool,
430}
431
432impl Default for ToolResultClearingConfig {
433 fn default() -> Self {
434 Self {
435 enabled: default_tool_result_clearing_enabled(),
436 trigger_tokens: default_tool_result_clearing_trigger_tokens(),
437 keep_tool_uses: default_tool_result_clearing_keep_tool_uses(),
438 clear_at_least_tokens: default_tool_result_clearing_clear_at_least_tokens(),
439 clear_tool_inputs: false,
440 }
441 }
442}
443
444impl ToolResultClearingConfig {
445 pub fn validate(&self) -> Result<(), String> {
446 if self.trigger_tokens == 0 {
447 return Err("tool_result_clearing.trigger_tokens must be greater than 0".to_string());
448 }
449 if self.keep_tool_uses == 0 {
450 return Err("tool_result_clearing.keep_tool_uses must be greater than 0".to_string());
451 }
452 if self.clear_at_least_tokens == 0 {
453 return Err(
454 "tool_result_clearing.clear_at_least_tokens must be greater than 0".to_string(),
455 );
456 }
457 Ok(())
458 }
459}
460
461#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
462#[derive(Debug, Clone, Deserialize, Serialize)]
463pub struct AgentCodexAppServerConfig {
464 #[serde(default = "default_codex_app_server_command")]
466 pub command: String,
467 #[serde(default = "default_codex_app_server_args")]
469 pub args: Vec<String>,
470 #[serde(default = "default_codex_app_server_startup_timeout_secs")]
472 pub startup_timeout_secs: u64,
473 #[serde(default = "default_codex_app_server_experimental_features")]
476 pub experimental_features: bool,
477}
478
479impl Default for AgentCodexAppServerConfig {
480 fn default() -> Self {
481 Self {
482 command: default_codex_app_server_command(),
483 args: default_codex_app_server_args(),
484 startup_timeout_secs: default_codex_app_server_startup_timeout_secs(),
485 experimental_features: default_codex_app_server_experimental_features(),
486 }
487 }
488}
489
490#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
491#[derive(Debug, Clone, Deserialize, Serialize)]
492pub struct CircuitBreakerConfig {
493 #[serde(default = "default_circuit_breaker_enabled")]
495 pub enabled: bool,
496
497 #[serde(default = "default_failure_threshold")]
499 pub failure_threshold: u32,
500
501 #[serde(default = "default_pause_on_open")]
503 pub pause_on_open: bool,
504
505 #[serde(default = "default_max_open_circuits")]
507 pub max_open_circuits: usize,
508
509 #[serde(default = "default_recovery_cooldown")]
511 pub recovery_cooldown: u64,
512}
513
514impl Default for CircuitBreakerConfig {
515 fn default() -> Self {
516 Self {
517 enabled: default_circuit_breaker_enabled(),
518 failure_threshold: default_failure_threshold(),
519 pause_on_open: default_pause_on_open(),
520 max_open_circuits: default_max_open_circuits(),
521 recovery_cooldown: default_recovery_cooldown(),
522 }
523 }
524}
525
526#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
532#[derive(Debug, Clone, Deserialize, Serialize)]
533pub struct OpenResponsesConfig {
534 #[serde(default)]
538 pub enabled: bool,
539
540 #[serde(default = "default_open_responses_emit_events")]
544 pub emit_events: bool,
545
546 #[serde(default = "default_open_responses_include_extensions")]
549 pub include_extensions: bool,
550
551 #[serde(default = "default_open_responses_map_tool_calls")]
554 pub map_tool_calls: bool,
555
556 #[serde(default = "default_open_responses_include_reasoning")]
559 pub include_reasoning: bool,
560}
561
562impl Default for OpenResponsesConfig {
563 fn default() -> Self {
564 Self {
565 enabled: false, emit_events: default_open_responses_emit_events(),
567 include_extensions: default_open_responses_include_extensions(),
568 map_tool_calls: default_open_responses_map_tool_calls(),
569 include_reasoning: default_open_responses_include_reasoning(),
570 }
571 }
572}
573
574#[inline]
575const fn default_open_responses_emit_events() -> bool {
576 true }
578
579#[inline]
580const fn default_open_responses_include_extensions() -> bool {
581 true }
583
584#[inline]
585const fn default_open_responses_map_tool_calls() -> bool {
586 true }
588
589#[inline]
590const fn default_open_responses_include_reasoning() -> bool {
591 true }
593
594#[inline]
595fn default_codex_app_server_command() -> String {
596 "codex".to_string()
597}
598
599#[inline]
600fn default_codex_app_server_args() -> Vec<String> {
601 vec!["app-server".to_string()]
602}
603
604#[inline]
605const fn default_codex_app_server_startup_timeout_secs() -> u64 {
606 10
607}
608
609#[inline]
610const fn default_codex_app_server_experimental_features() -> bool {
611 false
612}
613
614impl Default for AgentConfig {
615 fn default() -> Self {
616 Self {
617 provider: default_provider(),
618 api_key_env: default_api_key_env(),
619 default_model: default_model(),
620 theme: default_theme(),
621 system_prompt_mode: SystemPromptMode::default(),
622 tool_documentation_mode: ToolDocumentationMode::default(),
623 enable_split_tool_results: default_enable_split_tool_results(),
624 todo_planning_mode: default_todo_planning_mode(),
625 ui_surface: UiSurfacePreference::default(),
626 max_conversation_turns: default_max_conversation_turns(),
627 idle_turn_limit: default_idle_turn_limit(),
628 reasoning_effort: default_reasoning_effort(),
629 verbosity: default_verbosity(),
630 temperature: default_temperature(),
631 refine_temperature: default_refine_temperature(),
632 enable_self_review: default_enable_self_review(),
633 max_review_passes: default_max_review_passes(),
634 refine_prompts_enabled: default_refine_prompts_enabled(),
635 refine_prompts_max_passes: default_refine_max_passes(),
636 refine_prompts_model: String::new(),
637 small_model: AgentSmallModelConfig::default(),
638 prompt_suggestions: AgentPromptSuggestionsConfig::default(),
639 onboarding: AgentOnboardingConfig::default(),
640 project_doc_max_bytes: default_project_doc_max_bytes(),
641 project_doc_fallback_filenames: Vec::new(),
642 instruction_max_bytes: default_instruction_max_bytes(),
643 instruction_files: Vec::new(),
644 instruction_excludes: Vec::new(),
645 instruction_import_max_depth: default_instruction_import_max_depth(),
646 persistent_memory: PersistentMemoryConfig::default(),
647 custom_api_keys: BTreeMap::new(),
648 credential_storage_mode: crate::auth::AuthCredentialsStoreMode::default(),
649 checkpointing: AgentCheckpointingConfig::default(),
650 vibe_coding: AgentVibeCodingConfig::default(),
651 max_task_retries: default_max_task_retries(),
652 harness: AgentHarnessConfig::default(),
653 codex_app_server: AgentCodexAppServerConfig::default(),
654 include_temporal_context: default_include_temporal_context(),
655 temporal_context_use_utc: false, include_working_directory: default_include_working_directory(),
657 include_structured_reasoning_tags: None,
658 user_instructions: None,
659 require_plan_confirmation: default_require_plan_confirmation(),
660 circuit_breaker: CircuitBreakerConfig::default(),
661 open_responses: OpenResponsesConfig::default(),
662 }
663 }
664}
665
666impl AgentConfig {
667 pub fn should_include_structured_reasoning_tags(&self) -> bool {
669 self.include_structured_reasoning_tags.unwrap_or(matches!(
670 self.system_prompt_mode,
671 SystemPromptMode::Specialized
672 ))
673 }
674
675 pub fn validate_llm_params(&self) -> Result<(), String> {
677 if !(0.0..=1.0).contains(&self.temperature) {
679 return Err(format!(
680 "temperature must be between 0.0 and 1.0, got {}",
681 self.temperature
682 ));
683 }
684
685 if !(0.0..=1.0).contains(&self.refine_temperature) {
686 return Err(format!(
687 "refine_temperature must be between 0.0 and 1.0, got {}",
688 self.refine_temperature
689 ));
690 }
691
692 if self.instruction_import_max_depth == 0 {
693 return Err("instruction_import_max_depth must be greater than 0".to_string());
694 }
695
696 self.persistent_memory.validate()?;
697 self.harness.tool_result_clearing.validate()?;
698
699 Ok(())
700 }
701}
702
703#[inline]
705fn default_provider() -> String {
706 defaults::DEFAULT_PROVIDER.into()
707}
708
709#[inline]
710fn default_api_key_env() -> String {
711 defaults::DEFAULT_API_KEY_ENV.into()
712}
713
714#[inline]
715fn default_model() -> String {
716 defaults::DEFAULT_MODEL.into()
717}
718
719#[inline]
720fn default_theme() -> String {
721 defaults::DEFAULT_THEME.into()
722}
723
724#[inline]
725const fn default_todo_planning_mode() -> bool {
726 true
727}
728
729#[inline]
730const fn default_enable_split_tool_results() -> bool {
731 true }
733
734#[inline]
735const fn default_max_conversation_turns() -> usize {
736 defaults::DEFAULT_MAX_CONVERSATION_TURNS
737}
738
739#[inline]
740const fn default_idle_turn_limit() -> usize {
741 execution::IDLE_TURN_LIMIT
742}
743
744#[inline]
745fn default_reasoning_effort() -> ReasoningEffortLevel {
746 ReasoningEffortLevel::None
747}
748
749#[inline]
750fn default_verbosity() -> VerbosityLevel {
751 VerbosityLevel::default()
752}
753
754#[inline]
755const fn default_temperature() -> f32 {
756 llm_generation::DEFAULT_TEMPERATURE
757}
758
759#[inline]
760const fn default_refine_temperature() -> f32 {
761 llm_generation::DEFAULT_REFINE_TEMPERATURE
762}
763
764#[inline]
765const fn default_enable_self_review() -> bool {
766 false
767}
768
769#[inline]
770const fn default_max_review_passes() -> usize {
771 1
772}
773
774#[inline]
775const fn default_refine_prompts_enabled() -> bool {
776 false
777}
778
779#[inline]
780const fn default_refine_max_passes() -> usize {
781 1
782}
783
784#[inline]
785const fn default_project_doc_max_bytes() -> usize {
786 prompt_budget::DEFAULT_MAX_BYTES
787}
788
789#[inline]
790const fn default_instruction_max_bytes() -> usize {
791 prompt_budget::DEFAULT_MAX_BYTES
792}
793
794#[inline]
795const fn default_instruction_import_max_depth() -> usize {
796 5
797}
798
799#[inline]
800const fn default_max_task_retries() -> u32 {
801 2 }
803
804#[inline]
805const fn default_harness_max_tool_calls_per_turn() -> usize {
806 defaults::DEFAULT_MAX_TOOL_CALLS_PER_TURN
807}
808
809#[inline]
810const fn default_harness_max_tool_wall_clock_secs() -> u64 {
811 defaults::DEFAULT_MAX_TOOL_WALL_CLOCK_SECS
812}
813
814#[inline]
815const fn default_harness_max_tool_retries() -> u32 {
816 defaults::DEFAULT_MAX_TOOL_RETRIES
817}
818
819#[inline]
820const fn default_harness_max_parallel_tool_calls() -> usize {
821 4 }
823
824#[inline]
825const fn default_harness_auto_compaction_enabled() -> bool {
826 false
827}
828
829#[inline]
830const fn default_tool_result_clearing_enabled() -> bool {
831 false
832}
833
834#[inline]
835const fn default_tool_result_clearing_trigger_tokens() -> u64 {
836 100_000
837}
838
839#[inline]
840const fn default_tool_result_clearing_keep_tool_uses() -> u32 {
841 3
842}
843
844#[inline]
845const fn default_tool_result_clearing_clear_at_least_tokens() -> u64 {
846 30_000
847}
848
849#[inline]
850const fn default_harness_max_revision_rounds() -> usize {
851 2
852}
853
854#[inline]
855const fn default_include_temporal_context() -> bool {
856 true }
858
859#[inline]
860const fn default_include_working_directory() -> bool {
861 true }
863
864#[inline]
865const fn default_require_plan_confirmation() -> bool {
866 true }
868
869#[inline]
870const fn default_circuit_breaker_enabled() -> bool {
871 true }
873
874#[inline]
875const fn default_failure_threshold() -> u32 {
876 7 }
878
879#[inline]
880const fn default_pause_on_open() -> bool {
881 true }
883
884#[inline]
885const fn default_max_open_circuits() -> usize {
886 3 }
888
889#[inline]
890const fn default_recovery_cooldown() -> u64 {
891 60 }
893
894#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
895#[derive(Debug, Clone, Deserialize, Serialize)]
896pub struct AgentCheckpointingConfig {
897 #[serde(default = "default_checkpointing_enabled")]
899 pub enabled: bool,
900
901 #[serde(default)]
903 pub storage_dir: Option<String>,
904
905 #[serde(default = "default_checkpointing_max_snapshots")]
907 pub max_snapshots: usize,
908
909 #[serde(default = "default_checkpointing_max_age_days")]
911 pub max_age_days: Option<u64>,
912}
913
914impl Default for AgentCheckpointingConfig {
915 fn default() -> Self {
916 Self {
917 enabled: default_checkpointing_enabled(),
918 storage_dir: None,
919 max_snapshots: default_checkpointing_max_snapshots(),
920 max_age_days: default_checkpointing_max_age_days(),
921 }
922 }
923}
924
925#[inline]
926const fn default_checkpointing_enabled() -> bool {
927 DEFAULT_CHECKPOINTS_ENABLED
928}
929
930#[inline]
931const fn default_checkpointing_max_snapshots() -> usize {
932 DEFAULT_MAX_SNAPSHOTS
933}
934
935#[inline]
936const fn default_checkpointing_max_age_days() -> Option<u64> {
937 Some(DEFAULT_MAX_AGE_DAYS)
938}
939
940#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
945#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
946pub struct MemoriesConfig {
947 #[serde(default = "default_memories_generate")]
950 pub generate_memories: bool,
951
952 #[serde(default = "default_memories_use")]
954 pub use_memories: bool,
955
956 #[serde(default)]
958 pub extract_model: Option<String>,
959
960 #[serde(default)]
962 pub consolidation_model: Option<String>,
963}
964
965impl Default for MemoriesConfig {
966 fn default() -> Self {
967 Self {
968 generate_memories: default_memories_generate(),
969 use_memories: default_memories_use(),
970 extract_model: None,
971 consolidation_model: None,
972 }
973 }
974}
975
976#[inline]
977const fn default_memories_generate() -> bool {
978 true
979}
980
981#[inline]
982const fn default_memories_use() -> bool {
983 true
984}
985
986#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
987#[derive(Debug, Clone, Deserialize, Serialize)]
988pub struct PersistentMemoryConfig {
989 #[serde(default = "default_persistent_memory_enabled")]
991 pub enabled: bool,
992
993 #[serde(default = "default_persistent_memory_auto_write")]
995 pub auto_write: bool,
996
997 #[serde(default)]
999 pub directory_override: Option<String>,
1000
1001 #[serde(default = "default_persistent_memory_startup_line_limit")]
1003 pub startup_line_limit: usize,
1004
1005 #[serde(default = "default_persistent_memory_startup_byte_limit")]
1007 pub startup_byte_limit: usize,
1008
1009 #[serde(default)]
1011 pub memories: MemoriesConfig,
1012}
1013
1014impl Default for PersistentMemoryConfig {
1015 fn default() -> Self {
1016 Self {
1017 enabled: default_persistent_memory_enabled(),
1018 auto_write: default_persistent_memory_auto_write(),
1019 directory_override: None,
1020 startup_line_limit: default_persistent_memory_startup_line_limit(),
1021 startup_byte_limit: default_persistent_memory_startup_byte_limit(),
1022 memories: MemoriesConfig::default(),
1023 }
1024 }
1025}
1026
1027impl PersistentMemoryConfig {
1028 pub fn validate(&self) -> Result<(), String> {
1029 if self.startup_line_limit == 0 {
1030 return Err("persistent_memory.startup_line_limit must be greater than 0".to_string());
1031 }
1032
1033 if self.startup_byte_limit == 0 {
1034 return Err("persistent_memory.startup_byte_limit must be greater than 0".to_string());
1035 }
1036
1037 Ok(())
1038 }
1039}
1040
1041#[inline]
1042const fn default_persistent_memory_enabled() -> bool {
1043 false
1044}
1045
1046#[inline]
1047const fn default_persistent_memory_auto_write() -> bool {
1048 true
1049}
1050
1051#[inline]
1052const fn default_persistent_memory_startup_line_limit() -> usize {
1053 200
1054}
1055
1056#[inline]
1057const fn default_persistent_memory_startup_byte_limit() -> usize {
1058 25 * 1024
1059}
1060
1061#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1062#[derive(Debug, Clone, Deserialize, Serialize)]
1063pub struct AgentOnboardingConfig {
1064 #[serde(default = "default_onboarding_enabled")]
1066 pub enabled: bool,
1067
1068 #[serde(default = "default_intro_text")]
1070 pub intro_text: String,
1071
1072 #[serde(default = "default_show_project_overview")]
1074 pub include_project_overview: bool,
1075
1076 #[serde(default = "default_show_language_summary")]
1078 pub include_language_summary: bool,
1079
1080 #[serde(default = "default_show_guideline_highlights")]
1082 pub include_guideline_highlights: bool,
1083
1084 #[serde(default = "default_show_usage_tips_in_welcome")]
1086 pub include_usage_tips_in_welcome: bool,
1087
1088 #[serde(default = "default_show_recommended_actions_in_welcome")]
1090 pub include_recommended_actions_in_welcome: bool,
1091
1092 #[serde(default = "default_guideline_highlight_limit")]
1094 pub guideline_highlight_limit: usize,
1095
1096 #[serde(default = "default_usage_tips")]
1098 pub usage_tips: Vec<String>,
1099
1100 #[serde(default = "default_recommended_actions")]
1102 pub recommended_actions: Vec<String>,
1103
1104 #[serde(default)]
1106 pub chat_placeholder: Option<String>,
1107}
1108
1109impl Default for AgentOnboardingConfig {
1110 fn default() -> Self {
1111 Self {
1112 enabled: default_onboarding_enabled(),
1113 intro_text: default_intro_text(),
1114 include_project_overview: default_show_project_overview(),
1115 include_language_summary: default_show_language_summary(),
1116 include_guideline_highlights: default_show_guideline_highlights(),
1117 include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
1118 include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
1119 guideline_highlight_limit: default_guideline_highlight_limit(),
1120 usage_tips: default_usage_tips(),
1121 recommended_actions: default_recommended_actions(),
1122 chat_placeholder: None,
1123 }
1124 }
1125}
1126
1127#[inline]
1128const fn default_onboarding_enabled() -> bool {
1129 true
1130}
1131
1132const DEFAULT_INTRO_TEXT: &str =
1133 "Let's get oriented. I preloaded workspace context so we can move fast.";
1134
1135#[inline]
1136fn default_intro_text() -> String {
1137 DEFAULT_INTRO_TEXT.into()
1138}
1139
1140#[inline]
1141const fn default_show_project_overview() -> bool {
1142 true
1143}
1144
1145#[inline]
1146const fn default_show_language_summary() -> bool {
1147 false
1148}
1149
1150#[inline]
1151const fn default_show_guideline_highlights() -> bool {
1152 true
1153}
1154
1155#[inline]
1156const fn default_show_usage_tips_in_welcome() -> bool {
1157 false
1158}
1159
1160#[inline]
1161const fn default_show_recommended_actions_in_welcome() -> bool {
1162 false
1163}
1164
1165#[inline]
1166const fn default_guideline_highlight_limit() -> usize {
1167 3
1168}
1169
1170const DEFAULT_USAGE_TIPS: &[&str] = &[
1171 "Describe your current coding goal or ask for a quick status overview.",
1172 "Reference AGENTS.md guidelines when proposing changes.",
1173 "Prefer asking for targeted file reads or diffs before editing.",
1174];
1175
1176const DEFAULT_RECOMMENDED_ACTIONS: &[&str] = &[
1177 "Review the highlighted guidelines and share the task you want to tackle.",
1178 "Ask for a workspace tour if you need more context.",
1179];
1180
1181fn default_usage_tips() -> Vec<String> {
1182 DEFAULT_USAGE_TIPS.iter().map(|s| (*s).into()).collect()
1183}
1184
1185fn default_recommended_actions() -> Vec<String> {
1186 DEFAULT_RECOMMENDED_ACTIONS
1187 .iter()
1188 .map(|s| (*s).into())
1189 .collect()
1190}
1191
1192#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1202#[derive(Debug, Clone, Deserialize, Serialize)]
1203pub struct AgentSmallModelConfig {
1204 #[serde(default = "default_small_model_enabled")]
1206 pub enabled: bool,
1207
1208 #[serde(default)]
1211 pub model: String,
1212
1213 #[serde(default = "default_small_model_temperature")]
1215 pub temperature: f32,
1216
1217 #[serde(default = "default_small_model_for_large_reads")]
1219 pub use_for_large_reads: bool,
1220
1221 #[serde(default = "default_small_model_for_web_summary")]
1223 pub use_for_web_summary: bool,
1224
1225 #[serde(default = "default_small_model_for_git_history")]
1227 pub use_for_git_history: bool,
1228
1229 #[serde(default = "default_small_model_for_memory")]
1231 pub use_for_memory: bool,
1232}
1233
1234impl Default for AgentSmallModelConfig {
1235 fn default() -> Self {
1236 Self {
1237 enabled: default_small_model_enabled(),
1238 model: String::new(),
1239 temperature: default_small_model_temperature(),
1240 use_for_large_reads: default_small_model_for_large_reads(),
1241 use_for_web_summary: default_small_model_for_web_summary(),
1242 use_for_git_history: default_small_model_for_git_history(),
1243 use_for_memory: default_small_model_for_memory(),
1244 }
1245 }
1246}
1247
1248#[inline]
1249const fn default_small_model_enabled() -> bool {
1250 true }
1252
1253#[inline]
1254const fn default_small_model_temperature() -> f32 {
1255 0.3 }
1257
1258#[inline]
1259const fn default_small_model_for_large_reads() -> bool {
1260 true
1261}
1262
1263#[inline]
1264const fn default_small_model_for_web_summary() -> bool {
1265 true
1266}
1267
1268#[inline]
1269const fn default_small_model_for_git_history() -> bool {
1270 true
1271}
1272
1273#[inline]
1274const fn default_small_model_for_memory() -> bool {
1275 true
1276}
1277
1278#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1280#[derive(Debug, Clone, Deserialize, Serialize)]
1281pub struct AgentPromptSuggestionsConfig {
1282 #[serde(default = "default_prompt_suggestions_enabled")]
1284 pub enabled: bool,
1285
1286 #[serde(default)]
1289 pub model: String,
1290
1291 #[serde(default = "default_prompt_suggestions_temperature")]
1293 pub temperature: f32,
1294
1295 #[serde(default = "default_prompt_suggestions_show_cost_notice")]
1297 pub show_cost_notice: bool,
1298}
1299
1300impl Default for AgentPromptSuggestionsConfig {
1301 fn default() -> Self {
1302 Self {
1303 enabled: default_prompt_suggestions_enabled(),
1304 model: String::new(),
1305 temperature: default_prompt_suggestions_temperature(),
1306 show_cost_notice: default_prompt_suggestions_show_cost_notice(),
1307 }
1308 }
1309}
1310
1311#[inline]
1312const fn default_prompt_suggestions_enabled() -> bool {
1313 true
1314}
1315
1316#[inline]
1317const fn default_prompt_suggestions_temperature() -> f32 {
1318 0.3
1319}
1320
1321#[inline]
1322const fn default_prompt_suggestions_show_cost_notice() -> bool {
1323 true
1324}
1325
1326#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1331#[derive(Debug, Clone, Deserialize, Serialize)]
1332pub struct AgentVibeCodingConfig {
1333 #[serde(default = "default_vibe_coding_enabled")]
1335 pub enabled: bool,
1336
1337 #[serde(default = "default_vibe_min_prompt_length")]
1339 pub min_prompt_length: usize,
1340
1341 #[serde(default = "default_vibe_min_prompt_words")]
1343 pub min_prompt_words: usize,
1344
1345 #[serde(default = "default_vibe_entity_resolution")]
1347 pub enable_entity_resolution: bool,
1348
1349 #[serde(default = "default_vibe_entity_cache")]
1351 pub entity_index_cache: String,
1352
1353 #[serde(default = "default_vibe_max_entity_matches")]
1355 pub max_entity_matches: usize,
1356
1357 #[serde(default = "default_vibe_track_workspace")]
1359 pub track_workspace_state: bool,
1360
1361 #[serde(default = "default_vibe_max_recent_files")]
1363 pub max_recent_files: usize,
1364
1365 #[serde(default = "default_vibe_track_values")]
1367 pub track_value_history: bool,
1368
1369 #[serde(default = "default_vibe_conversation_memory")]
1371 pub enable_conversation_memory: bool,
1372
1373 #[serde(default = "default_vibe_max_memory_turns")]
1375 pub max_memory_turns: usize,
1376
1377 #[serde(default = "default_vibe_pronoun_resolution")]
1379 pub enable_pronoun_resolution: bool,
1380
1381 #[serde(default = "default_vibe_proactive_context")]
1383 pub enable_proactive_context: bool,
1384
1385 #[serde(default = "default_vibe_max_context_files")]
1387 pub max_context_files: usize,
1388
1389 #[serde(default = "default_vibe_max_snippets_per_file")]
1391 pub max_context_snippets_per_file: usize,
1392
1393 #[serde(default = "default_vibe_max_search_results")]
1395 pub max_search_results: usize,
1396
1397 #[serde(default = "default_vibe_value_inference")]
1399 pub enable_relative_value_inference: bool,
1400}
1401
1402impl Default for AgentVibeCodingConfig {
1403 fn default() -> Self {
1404 Self {
1405 enabled: default_vibe_coding_enabled(),
1406 min_prompt_length: default_vibe_min_prompt_length(),
1407 min_prompt_words: default_vibe_min_prompt_words(),
1408 enable_entity_resolution: default_vibe_entity_resolution(),
1409 entity_index_cache: default_vibe_entity_cache(),
1410 max_entity_matches: default_vibe_max_entity_matches(),
1411 track_workspace_state: default_vibe_track_workspace(),
1412 max_recent_files: default_vibe_max_recent_files(),
1413 track_value_history: default_vibe_track_values(),
1414 enable_conversation_memory: default_vibe_conversation_memory(),
1415 max_memory_turns: default_vibe_max_memory_turns(),
1416 enable_pronoun_resolution: default_vibe_pronoun_resolution(),
1417 enable_proactive_context: default_vibe_proactive_context(),
1418 max_context_files: default_vibe_max_context_files(),
1419 max_context_snippets_per_file: default_vibe_max_snippets_per_file(),
1420 max_search_results: default_vibe_max_search_results(),
1421 enable_relative_value_inference: default_vibe_value_inference(),
1422 }
1423 }
1424}
1425
1426#[inline]
1428const fn default_vibe_coding_enabled() -> bool {
1429 false }
1431
1432#[inline]
1433const fn default_vibe_min_prompt_length() -> usize {
1434 5
1435}
1436
1437#[inline]
1438const fn default_vibe_min_prompt_words() -> usize {
1439 2
1440}
1441
1442#[inline]
1443const fn default_vibe_entity_resolution() -> bool {
1444 true
1445}
1446
1447#[inline]
1448fn default_vibe_entity_cache() -> String {
1449 ".vtcode/entity_index.json".into()
1450}
1451
1452#[inline]
1453const fn default_vibe_max_entity_matches() -> usize {
1454 5
1455}
1456
1457#[inline]
1458const fn default_vibe_track_workspace() -> bool {
1459 true
1460}
1461
1462#[inline]
1463const fn default_vibe_max_recent_files() -> usize {
1464 20
1465}
1466
1467#[inline]
1468const fn default_vibe_track_values() -> bool {
1469 true
1470}
1471
1472#[inline]
1473const fn default_vibe_conversation_memory() -> bool {
1474 true
1475}
1476
1477#[inline]
1478const fn default_vibe_max_memory_turns() -> usize {
1479 50
1480}
1481
1482#[inline]
1483const fn default_vibe_pronoun_resolution() -> bool {
1484 true
1485}
1486
1487#[inline]
1488const fn default_vibe_proactive_context() -> bool {
1489 true
1490}
1491
1492#[inline]
1493const fn default_vibe_max_context_files() -> usize {
1494 3
1495}
1496
1497#[inline]
1498const fn default_vibe_max_snippets_per_file() -> usize {
1499 20
1500}
1501
1502#[inline]
1503const fn default_vibe_max_search_results() -> usize {
1504 5
1505}
1506
1507#[inline]
1508const fn default_vibe_value_inference() -> bool {
1509 true
1510}
1511
1512#[cfg(test)]
1513mod tests {
1514 use super::*;
1515
1516 #[test]
1517 fn test_continuation_policy_defaults_and_parses() {
1518 assert_eq!(ContinuationPolicy::default(), ContinuationPolicy::All);
1519 assert_eq!(
1520 ContinuationPolicy::parse("off"),
1521 Some(ContinuationPolicy::Off)
1522 );
1523 assert_eq!(
1524 ContinuationPolicy::parse("exec-only"),
1525 Some(ContinuationPolicy::ExecOnly)
1526 );
1527 assert_eq!(
1528 ContinuationPolicy::parse("all"),
1529 Some(ContinuationPolicy::All)
1530 );
1531 assert_eq!(ContinuationPolicy::parse("invalid"), None);
1532 }
1533
1534 #[test]
1535 fn test_harness_config_continuation_policy_deserializes_with_fallback() {
1536 let parsed: AgentHarnessConfig =
1537 toml::from_str("continuation_policy = \"all\"").expect("valid harness config");
1538 assert_eq!(parsed.continuation_policy, ContinuationPolicy::All);
1539
1540 let fallback: AgentHarnessConfig =
1541 toml::from_str("continuation_policy = \"unexpected\"").expect("fallback config");
1542 assert_eq!(fallback.continuation_policy, ContinuationPolicy::All);
1543 }
1544
1545 #[test]
1546 fn test_harness_orchestration_mode_defaults_and_parses() {
1547 assert_eq!(
1548 HarnessOrchestrationMode::default(),
1549 HarnessOrchestrationMode::PlanBuildEvaluate
1550 );
1551 assert_eq!(
1552 HarnessOrchestrationMode::parse("single"),
1553 Some(HarnessOrchestrationMode::Single)
1554 );
1555 assert_eq!(
1556 HarnessOrchestrationMode::parse("plan_build_evaluate"),
1557 Some(HarnessOrchestrationMode::PlanBuildEvaluate)
1558 );
1559 assert_eq!(
1560 HarnessOrchestrationMode::parse("planner-generator-evaluator"),
1561 Some(HarnessOrchestrationMode::PlanBuildEvaluate)
1562 );
1563 assert_eq!(HarnessOrchestrationMode::parse("unexpected"), None);
1564 }
1565
1566 #[test]
1567 fn test_harness_config_orchestration_deserializes_with_fallback() {
1568 let parsed: AgentHarnessConfig =
1569 toml::from_str("orchestration_mode = \"plan_build_evaluate\"")
1570 .expect("valid harness config");
1571 assert_eq!(
1572 parsed.orchestration_mode,
1573 HarnessOrchestrationMode::PlanBuildEvaluate
1574 );
1575 assert_eq!(parsed.max_revision_rounds, 2);
1576
1577 let fallback: AgentHarnessConfig =
1578 toml::from_str("orchestration_mode = \"unexpected\"").expect("fallback config");
1579 assert_eq!(
1580 fallback.orchestration_mode,
1581 HarnessOrchestrationMode::PlanBuildEvaluate
1582 );
1583 }
1584
1585 #[test]
1586 fn test_plan_confirmation_config_default() {
1587 let config = AgentConfig::default();
1588 assert!(config.require_plan_confirmation);
1589 }
1590
1591 #[test]
1592 fn test_persistent_memory_is_disabled_by_default() {
1593 let config = AgentConfig::default();
1594 assert!(!config.persistent_memory.enabled);
1595 assert!(config.persistent_memory.auto_write);
1596 }
1597
1598 #[test]
1599 fn test_tool_result_clearing_defaults() {
1600 let config = AgentConfig::default();
1601 let clearing = config.harness.tool_result_clearing;
1602
1603 assert!(!clearing.enabled);
1604 assert_eq!(clearing.trigger_tokens, 100_000);
1605 assert_eq!(clearing.keep_tool_uses, 3);
1606 assert_eq!(clearing.clear_at_least_tokens, 30_000);
1607 assert!(!clearing.clear_tool_inputs);
1608 }
1609
1610 #[test]
1611 fn test_codex_app_server_experimental_features_default_to_disabled() {
1612 let config = AgentConfig::default();
1613
1614 assert!(!config.codex_app_server.experimental_features);
1615 }
1616
1617 #[test]
1618 fn test_codex_app_server_experimental_features_parse_from_toml() {
1619 let parsed: AgentCodexAppServerConfig = toml::from_str(
1620 r#"
1621 command = "codex"
1622 args = ["app-server"]
1623 startup_timeout_secs = 15
1624 experimental_features = true
1625 "#,
1626 )
1627 .expect("valid codex app-server config");
1628
1629 assert!(parsed.experimental_features);
1630 assert_eq!(parsed.startup_timeout_secs, 15);
1631 }
1632
1633 #[test]
1634 fn test_tool_result_clearing_parses_and_validates() {
1635 let parsed: AgentHarnessConfig = toml::from_str(
1636 r#"
1637 [tool_result_clearing]
1638 enabled = true
1639 trigger_tokens = 123456
1640 keep_tool_uses = 6
1641 clear_at_least_tokens = 4096
1642 clear_tool_inputs = true
1643 "#,
1644 )
1645 .expect("valid harness config");
1646
1647 assert!(parsed.tool_result_clearing.enabled);
1648 assert_eq!(parsed.tool_result_clearing.trigger_tokens, 123_456);
1649 assert_eq!(parsed.tool_result_clearing.keep_tool_uses, 6);
1650 assert_eq!(parsed.tool_result_clearing.clear_at_least_tokens, 4_096);
1651 assert!(parsed.tool_result_clearing.clear_tool_inputs);
1652 assert!(parsed.tool_result_clearing.validate().is_ok());
1653 }
1654
1655 #[test]
1656 fn test_tool_result_clearing_rejects_zero_values() {
1657 let clearing = ToolResultClearingConfig {
1658 trigger_tokens: 0,
1659 ..ToolResultClearingConfig::default()
1660 };
1661 assert!(clearing.validate().is_err());
1662
1663 let clearing = ToolResultClearingConfig {
1664 keep_tool_uses: 0,
1665 ..ToolResultClearingConfig::default()
1666 };
1667 assert!(clearing.validate().is_err());
1668
1669 let clearing = ToolResultClearingConfig {
1670 clear_at_least_tokens: 0,
1671 ..ToolResultClearingConfig::default()
1672 };
1673 assert!(clearing.validate().is_err());
1674 }
1675
1676 #[test]
1677 fn test_structured_reasoning_defaults_follow_prompt_mode() {
1678 let default_mode = AgentConfig {
1679 system_prompt_mode: SystemPromptMode::Default,
1680 ..Default::default()
1681 };
1682 assert!(!default_mode.should_include_structured_reasoning_tags());
1683
1684 let specialized_mode = AgentConfig {
1685 system_prompt_mode: SystemPromptMode::Specialized,
1686 ..Default::default()
1687 };
1688 assert!(specialized_mode.should_include_structured_reasoning_tags());
1689
1690 let minimal_mode = AgentConfig {
1691 system_prompt_mode: SystemPromptMode::Minimal,
1692 ..Default::default()
1693 };
1694 assert!(!minimal_mode.should_include_structured_reasoning_tags());
1695
1696 let lightweight_mode = AgentConfig {
1697 system_prompt_mode: SystemPromptMode::Lightweight,
1698 ..Default::default()
1699 };
1700 assert!(!lightweight_mode.should_include_structured_reasoning_tags());
1701 }
1702
1703 #[test]
1704 fn test_structured_reasoning_explicit_override() {
1705 let mut config = AgentConfig {
1706 system_prompt_mode: SystemPromptMode::Minimal,
1707 include_structured_reasoning_tags: Some(true),
1708 ..AgentConfig::default()
1709 };
1710 assert!(config.should_include_structured_reasoning_tags());
1711
1712 config.include_structured_reasoning_tags = Some(false);
1713 assert!(!config.should_include_structured_reasoning_tags());
1714 }
1715}