use crate::constants::{defaults, execution, llm_generation, prompt_budget, tool_limits};
use crate::types::{
ReasoningEffortLevel, ShellPromptProfile, SystemPromptMode, ToolDocumentationMode, UiSurfacePreference,
VerbosityLevel,
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
const DEFAULT_CHECKPOINTS_ENABLED: bool = true;
const DEFAULT_MAX_SNAPSHOTS: usize = 50;
const DEFAULT_MAX_AGE_DAYS: u64 = 30;
mod approval;
mod circuit_breaker;
mod codex_app_server;
mod harness;
mod memory;
mod onboarding;
mod open_responses;
mod prompt_suggestions;
mod small_model;
mod vibe_coding;
pub use approval::{AsyncApprovalConfig, ConfidenceEscalationConfig, SkepticPanelConfig};
pub use circuit_breaker::CircuitBreakerConfig;
pub use codex_app_server::AgentCodexAppServerConfig;
pub use harness::{
AgentHarnessConfig, InspectionConfig, InspectionMode, ToolResultClearingConfig, TrackerContinuationConfig,
VerificationAutoRecoveryConfig,
};
pub use memory::{MemoriesConfig, PersistentMemoryConfig};
pub use onboarding::AgentOnboardingConfig;
pub use open_responses::OpenResponsesConfig;
pub use prompt_suggestions::AgentPromptSuggestionsConfig;
pub use small_model::AgentSmallModelConfig;
pub use vibe_coding::AgentVibeCodingConfig;
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentConfig {
#[serde(default = "default_provider")]
pub provider: String,
#[serde(default = "default_api_key_env")]
pub api_key_env: String,
#[serde(default = "default_model")]
pub default_model: String,
#[serde(default = "default_theme")]
pub theme: String,
#[serde(default)]
pub system_prompt_mode: SystemPromptMode,
#[serde(default = "default_max_system_prompt_tokens")]
pub max_system_prompt_tokens: u64,
#[serde(default = "default_system_prompt_budget_warning")]
pub system_prompt_budget_warning: bool,
#[serde(default = "default_trim_system_prompt")]
pub trim_system_prompt: bool,
#[serde(default)]
pub tool_documentation_mode: ToolDocumentationMode,
#[serde(default)]
pub shell_prompt_profile: ShellPromptProfile,
#[serde(default = "default_enable_split_tool_results")]
pub enable_split_tool_results: bool,
#[serde(default = "default_todo_planning_mode")]
pub todo_planning_mode: bool,
#[serde(default)]
pub ui_surface: UiSurfacePreference,
#[serde(default = "default_max_conversation_turns")]
pub max_conversation_turns: usize,
#[serde(default = "default_idle_turn_limit")]
pub idle_turn_limit: usize,
#[serde(default = "default_reasoning_effort")]
pub reasoning_effort: ReasoningEffortLevel,
#[serde(default)]
pub allow_reasoning_effort_downgrade: bool,
#[serde(default = "default_verbosity")]
pub verbosity: VerbosityLevel,
#[serde(default = "default_temperature")]
pub temperature: f32,
#[serde(default = "default_refine_temperature")]
pub refine_temperature: f32,
#[serde(default = "default_enable_self_review")]
pub enable_self_review: bool,
#[serde(default = "default_max_review_passes")]
pub max_review_passes: usize,
#[serde(default = "default_refine_prompts_enabled")]
pub refine_prompts_enabled: bool,
#[serde(default = "default_refine_max_passes")]
pub refine_prompts_max_passes: usize,
#[serde(default)]
pub refine_prompts_model: String,
#[serde(default)]
pub small_model: AgentSmallModelConfig,
#[serde(default)]
pub prompt_suggestions: AgentPromptSuggestionsConfig,
#[serde(default)]
pub onboarding: AgentOnboardingConfig,
#[serde(default = "default_project_doc_max_bytes")]
pub project_doc_max_bytes: usize,
#[serde(default)]
pub project_doc_fallback_filenames: Vec<String>,
#[serde(default = "default_instruction_max_bytes", alias = "rule_doc_max_bytes")]
pub instruction_max_bytes: usize,
#[serde(default, alias = "instruction_paths", alias = "instructions")]
pub instruction_files: Vec<String>,
#[serde(default)]
pub instruction_excludes: Vec<String>,
#[serde(default = "default_instruction_import_max_depth")]
pub instruction_import_max_depth: usize,
#[serde(default)]
pub persistent_memory: PersistentMemoryConfig,
#[serde(default, skip_serializing)]
pub custom_api_keys: BTreeMap<String, String>,
#[serde(default)]
pub credential_storage_mode: crate::auth::AuthCredentialsStoreMode,
#[serde(default)]
pub checkpointing: AgentCheckpointingConfig,
#[serde(default)]
pub vibe_coding: AgentVibeCodingConfig,
#[serde(default = "default_max_task_retries")]
pub max_task_retries: u32,
#[serde(default)]
pub harness: AgentHarnessConfig,
#[serde(default)]
pub codex_app_server: AgentCodexAppServerConfig,
#[serde(default = "default_include_temporal_context")]
pub include_temporal_context: bool,
#[serde(default)]
pub temporal_context_use_utc: bool,
#[serde(default = "default_include_working_directory")]
pub include_working_directory: bool,
#[serde(default)]
pub include_structured_reasoning_tags: Option<bool>,
#[serde(default)]
pub user_instructions: Option<String>,
#[serde(default = "default_require_plan_confirmation")]
pub require_plan_confirmation: bool,
#[serde(default)]
pub circuit_breaker: CircuitBreakerConfig,
#[serde(default)]
pub open_responses: OpenResponsesConfig,
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ContinuationPolicy {
Off,
ExecOnly,
#[default]
All,
}
impl ContinuationPolicy {
pub fn as_str(&self) -> &'static str {
match self {
Self::Off => "off",
Self::ExecOnly => "exec_only",
Self::All => "all",
}
}
fn parse(value: &str) -> Option<Self> {
let normalized = value.trim();
if normalized.eq_ignore_ascii_case("off") {
Some(Self::Off)
} else if normalized.eq_ignore_ascii_case("exec_only") || normalized.eq_ignore_ascii_case("exec-only") {
Some(Self::ExecOnly)
} else if normalized.eq_ignore_ascii_case("all") {
Some(Self::All)
} else {
None
}
}
}
impl<'de> Deserialize<'de> for ContinuationPolicy {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
Ok(Self::parse(&raw).unwrap_or_default())
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ContextResetMode {
#[default]
Off,
OnStall,
OnCompaction,
}
impl ContextResetMode {
pub fn as_str(&self) -> &'static str {
match self {
Self::Off => "off",
Self::OnStall => "on_stall",
Self::OnCompaction => "on_compaction",
}
}
fn parse(value: &str) -> Option<Self> {
let normalized = value.trim();
if normalized.eq_ignore_ascii_case("off") {
Some(Self::Off)
} else if normalized.eq_ignore_ascii_case("on_stall") || normalized.eq_ignore_ascii_case("on-stall") {
Some(Self::OnStall)
} else if normalized.eq_ignore_ascii_case("on_compaction") || normalized.eq_ignore_ascii_case("on-compaction") {
Some(Self::OnCompaction)
} else {
None
}
}
}
impl<'de> Deserialize<'de> for ContextResetMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
Ok(Self::parse(&raw).unwrap_or_default())
}
}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum HarnessOrchestrationMode {
#[default]
PlanBuildEvaluate,
Single,
}
impl HarnessOrchestrationMode {
pub fn as_str(&self) -> &'static str {
match self {
Self::Single => "single",
Self::PlanBuildEvaluate => "plan_build_evaluate",
}
}
fn parse(value: &str) -> Option<Self> {
let normalized = value.trim();
if normalized.eq_ignore_ascii_case("single") {
Some(Self::Single)
} else if normalized.eq_ignore_ascii_case("plan_build_evaluate")
|| normalized.eq_ignore_ascii_case("plan-build-evaluate")
|| normalized.eq_ignore_ascii_case("planner_generator_evaluator")
|| normalized.eq_ignore_ascii_case("planner-generator-evaluator")
{
Some(Self::PlanBuildEvaluate)
} else {
None
}
}
}
impl<'de> Deserialize<'de> for HarnessOrchestrationMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
Ok(Self::parse(&raw).unwrap_or_default())
}
}
impl Default for AgentConfig {
fn default() -> Self {
Self {
provider: default_provider(),
api_key_env: default_api_key_env(),
default_model: default_model(),
theme: default_theme(),
system_prompt_mode: SystemPromptMode::default(),
max_system_prompt_tokens: default_max_system_prompt_tokens(),
system_prompt_budget_warning: default_system_prompt_budget_warning(),
trim_system_prompt: default_trim_system_prompt(),
tool_documentation_mode: ToolDocumentationMode::default(),
shell_prompt_profile: ShellPromptProfile::default(),
enable_split_tool_results: default_enable_split_tool_results(),
todo_planning_mode: default_todo_planning_mode(),
ui_surface: UiSurfacePreference::default(),
max_conversation_turns: default_max_conversation_turns(),
idle_turn_limit: default_idle_turn_limit(),
reasoning_effort: default_reasoning_effort(),
allow_reasoning_effort_downgrade: false,
verbosity: default_verbosity(),
temperature: default_temperature(),
refine_temperature: default_refine_temperature(),
enable_self_review: default_enable_self_review(),
max_review_passes: default_max_review_passes(),
refine_prompts_enabled: default_refine_prompts_enabled(),
refine_prompts_max_passes: default_refine_max_passes(),
refine_prompts_model: String::new(),
small_model: AgentSmallModelConfig::default(),
prompt_suggestions: AgentPromptSuggestionsConfig::default(),
onboarding: AgentOnboardingConfig::default(),
project_doc_max_bytes: default_project_doc_max_bytes(),
project_doc_fallback_filenames: Vec::new(),
instruction_max_bytes: default_instruction_max_bytes(),
instruction_files: Vec::new(),
instruction_excludes: Vec::new(),
instruction_import_max_depth: default_instruction_import_max_depth(),
persistent_memory: PersistentMemoryConfig::default(),
custom_api_keys: BTreeMap::new(),
credential_storage_mode: crate::auth::AuthCredentialsStoreMode::default(),
checkpointing: AgentCheckpointingConfig::default(),
vibe_coding: AgentVibeCodingConfig::default(),
max_task_retries: default_max_task_retries(),
harness: AgentHarnessConfig::default(),
codex_app_server: AgentCodexAppServerConfig::default(),
include_temporal_context: default_include_temporal_context(),
temporal_context_use_utc: false, include_working_directory: default_include_working_directory(),
include_structured_reasoning_tags: None,
user_instructions: None,
require_plan_confirmation: default_require_plan_confirmation(),
circuit_breaker: CircuitBreakerConfig::default(),
open_responses: OpenResponsesConfig::default(),
}
}
}
impl AgentConfig {
pub fn should_include_structured_reasoning_tags(&self) -> bool {
self.include_structured_reasoning_tags.unwrap_or(false)
}
pub(crate) fn validate_llm_params(&self) -> Result<(), String> {
if !(0.0..=1.0).contains(&self.temperature) {
return Err(format!("temperature must be between 0.0 and 1.0, got {}", self.temperature));
}
if !(0.0..=1.0).contains(&self.refine_temperature) {
return Err(format!("refine_temperature must be between 0.0 and 1.0, got {}", self.refine_temperature));
}
if self.instruction_import_max_depth == 0 {
return Err("instruction_import_max_depth must be greater than 0".to_string());
}
if !(0.0..=1.0).contains(&self.harness.budget_warning_threshold) {
return Err(format!(
"harness.budget_warning_threshold must be between 0.0 and 1.0, got {}",
self.harness.budget_warning_threshold
));
}
self.persistent_memory.validate()?;
self.harness.tool_result_clearing.validate()?;
Ok(())
}
}
#[inline]
fn default_provider() -> String {
defaults::DEFAULT_PROVIDER.into()
}
#[inline]
fn default_api_key_env() -> String {
defaults::DEFAULT_API_KEY_ENV.into()
}
#[inline]
fn default_model() -> String {
defaults::DEFAULT_MODEL.into()
}
#[inline]
fn default_theme() -> String {
defaults::DEFAULT_THEME.into()
}
#[inline]
const fn default_todo_planning_mode() -> bool {
true
}
#[inline]
const fn default_enable_split_tool_results() -> bool {
true }
#[inline]
const fn default_max_conversation_turns() -> usize {
tool_limits::DEFAULT_MAX_CONVERSATION_TURNS
}
#[inline]
const fn default_idle_turn_limit() -> usize {
execution::IDLE_TURN_LIMIT
}
#[inline]
fn default_reasoning_effort() -> ReasoningEffortLevel {
ReasoningEffortLevel::None
}
#[inline]
fn default_verbosity() -> VerbosityLevel {
VerbosityLevel::default()
}
#[inline]
const fn default_temperature() -> f32 {
llm_generation::DEFAULT_TEMPERATURE
}
#[inline]
const fn default_refine_temperature() -> f32 {
llm_generation::DEFAULT_REFINE_TEMPERATURE
}
#[inline]
const fn default_enable_self_review() -> bool {
false
}
#[inline]
const fn default_max_review_passes() -> usize {
1
}
#[inline]
const fn default_refine_prompts_enabled() -> bool {
false
}
#[inline]
const fn default_refine_max_passes() -> usize {
1
}
#[inline]
const fn default_max_system_prompt_tokens() -> u64 {
prompt_budget::DEFAULT_MAX_SYSTEM_PROMPT_TOKENS
}
#[inline]
const fn default_system_prompt_budget_warning() -> bool {
true
}
#[inline]
const fn default_trim_system_prompt() -> bool {
true
}
#[inline]
const fn default_project_doc_max_bytes() -> usize {
prompt_budget::DEFAULT_MAX_BYTES
}
#[inline]
const fn default_instruction_max_bytes() -> usize {
prompt_budget::DEFAULT_MAX_BYTES
}
#[inline]
const fn default_instruction_import_max_depth() -> usize {
5
}
#[inline]
const fn default_max_task_retries() -> u32 {
2 }
#[inline]
const fn default_include_temporal_context() -> bool {
true }
#[inline]
const fn default_include_working_directory() -> bool {
true }
#[inline]
const fn default_require_plan_confirmation() -> bool {
true }
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentCheckpointingConfig {
#[serde(default = "default_checkpointing_enabled")]
pub enabled: bool,
#[serde(default)]
pub storage_dir: Option<String>,
#[serde(default = "default_checkpointing_max_snapshots")]
pub max_snapshots: usize,
#[serde(default = "default_checkpointing_max_age_days")]
pub max_age_days: Option<u64>,
}
impl Default for AgentCheckpointingConfig {
fn default() -> Self {
Self {
enabled: default_checkpointing_enabled(),
storage_dir: None,
max_snapshots: default_checkpointing_max_snapshots(),
max_age_days: default_checkpointing_max_age_days(),
}
}
}
#[inline]
const fn default_checkpointing_enabled() -> bool {
DEFAULT_CHECKPOINTS_ENABLED
}
#[inline]
const fn default_checkpointing_max_snapshots() -> usize {
DEFAULT_MAX_SNAPSHOTS
}
#[inline]
const fn default_checkpointing_max_age_days() -> Option<u64> {
Some(DEFAULT_MAX_AGE_DAYS)
}
#[cfg(test)]
mod tests;