use serde::{Deserialize, Serialize};
use ai_agents_core::{AgentError, Result};
use ai_agents_tools::ToolSchemaPromptMode;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct RuntimeConfig {
pub optimization: RuntimeOptimizationConfig,
pub tool_schema_prompt_mode: ToolSchemaPromptMode,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct RuntimeOptimizationConfig {
pub enabled: bool,
pub max_speculative_llm_calls_per_turn: u32,
pub pre_response_deterministic_transitions: bool,
pub pre_response_extractors: bool,
pub speculative_state_transitions: bool,
pub speculative_skill_routing: bool,
pub speculative_reasoning_auto: bool,
pub parallel_post_turn_memory: bool,
pub parallel_orchestration_vote_extraction: bool,
pub background_observability_export: bool,
pub streaming_policy: StreamingOptimizationPolicy,
pub max_parallel_runtime_tasks: usize,
pub post_turn: PostTurnOptimizationConfig,
}
impl Default for RuntimeOptimizationConfig {
fn default() -> Self {
Self {
enabled: false,
max_speculative_llm_calls_per_turn: 0,
pre_response_deterministic_transitions: false,
pre_response_extractors: false,
speculative_state_transitions: false,
speculative_skill_routing: false,
speculative_reasoning_auto: false,
parallel_post_turn_memory: false,
parallel_orchestration_vote_extraction: false,
background_observability_export: false,
streaming_policy: StreamingOptimizationPolicy::PreflightOnly,
max_parallel_runtime_tasks: 4,
post_turn: PostTurnOptimizationConfig::default(),
}
}
}
impl RuntimeOptimizationConfig {
pub fn validate(&self) -> Result<()> {
if self.max_parallel_runtime_tasks == 0 {
return Err(AgentError::InvalidSpec(
"runtime.optimization.max_parallel_runtime_tasks must be greater than 0".into(),
));
}
if self.post_turn.max_background_tasks == 0 && self.post_turn.any_background_tasks_enabled()
{
return Err(AgentError::InvalidSpec(
"runtime.optimization.post_turn.max_background_tasks must be greater than 0 when background maintenance is enabled".into(),
));
}
if self.background_observability_export {
return Err(AgentError::InvalidSpec(
"runtime.optimization.background_observability_export requires snapshot export support and is not enabled yet".into(),
));
}
let any_speculative = self.speculative_state_transitions
|| self.speculative_skill_routing
|| self.speculative_reasoning_auto;
if any_speculative {
if !self.enabled {
return Err(AgentError::InvalidSpec(
"runtime.optimization.enabled must be true when speculative branch settings are enabled".into(),
));
}
if self.max_speculative_llm_calls_per_turn == 0 {
return Err(AgentError::InvalidSpec(
"runtime.optimization.max_speculative_llm_calls_per_turn must be greater than 0 when speculative branch settings are enabled".into(),
));
}
}
if self.max_speculative_llm_calls_per_turn > self.max_parallel_runtime_tasks as u32 {
return Err(AgentError::InvalidSpec(
"runtime.optimization.max_speculative_llm_calls_per_turn must be less than or equal to max_parallel_runtime_tasks".into(),
));
}
if self.post_turn.sessions != MaintenanceTaskPolicy::default() {
return Err(AgentError::InvalidSpec(
"runtime.optimization.post_turn.sessions is reserved until session maintenance scheduling is enabled".into(),
));
}
if self.post_turn.memory_compression != MaintenanceTaskPolicy::default() {
return Err(AgentError::InvalidSpec(
"runtime.optimization.post_turn.memory_compression is reserved until compression scheduling is enabled".into(),
));
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct PostTurnOptimizationConfig {
pub facts: MaintenanceTaskPolicy,
pub relationships: MaintenanceTaskPolicy,
pub sessions: MaintenanceTaskPolicy,
pub memory_compression: MaintenanceTaskPolicy,
pub max_background_tasks: usize,
pub on_background_overflow: BackgroundOverflowPolicy,
}
impl Default for PostTurnOptimizationConfig {
fn default() -> Self {
Self {
facts: MaintenanceTaskPolicy {
mode: MaintenanceMode::InlineSerial,
await_before_next_turn: AwaitBeforeNextTurn::Always,
},
relationships: MaintenanceTaskPolicy {
mode: MaintenanceMode::InlineSerial,
await_before_next_turn: AwaitBeforeNextTurn::Always,
},
sessions: MaintenanceTaskPolicy::default(),
memory_compression: MaintenanceTaskPolicy::default(),
max_background_tasks: 16,
on_background_overflow: BackgroundOverflowPolicy::RunInline,
}
}
}
impl PostTurnOptimizationConfig {
pub fn any_background_tasks_enabled(&self) -> bool {
matches!(self.facts.mode, MaintenanceMode::Background)
|| matches!(self.relationships.mode, MaintenanceMode::Background)
|| matches!(self.sessions.mode, MaintenanceMode::Background)
|| matches!(self.memory_compression.mode, MaintenanceMode::Background)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct MaintenanceTaskPolicy {
pub mode: MaintenanceMode,
pub await_before_next_turn: AwaitBeforeNextTurn,
}
impl Default for MaintenanceTaskPolicy {
fn default() -> Self {
Self {
mode: MaintenanceMode::InlineSerial,
await_before_next_turn: AwaitBeforeNextTurn::Always,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum MaintenanceMode {
#[default]
InlineSerial,
InlineParallel,
Background,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum AwaitBeforeNextTurn {
Never,
SameActor,
#[default]
Always,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum BackgroundOverflowPolicy {
#[default]
RunInline,
Drop,
Error,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum StreamingOptimizationPolicy {
#[default]
PreflightOnly,
BufferUntilRoutingDone,
Disabled,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_buffered_streaming_policy() {
let config = RuntimeOptimizationConfig {
enabled: true,
streaming_policy: StreamingOptimizationPolicy::BufferUntilRoutingDone,
..Default::default()
};
assert!(config.validate().is_ok());
}
#[test]
fn rejects_reserved_session_maintenance_policy() {
let mut config = RuntimeOptimizationConfig {
enabled: true,
..Default::default()
};
config.post_turn.sessions.mode = MaintenanceMode::Background;
assert!(config.validate().is_err());
}
#[test]
fn rejects_reserved_compression_maintenance_policy() {
let mut config = RuntimeOptimizationConfig {
enabled: true,
..Default::default()
};
config.post_turn.memory_compression.mode = MaintenanceMode::Background;
assert!(config.validate().is_err());
}
#[test]
fn speculative_flags_require_positive_cap() {
let config = RuntimeOptimizationConfig {
enabled: true,
speculative_skill_routing: true,
max_speculative_llm_calls_per_turn: 0,
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn speculative_cap_must_fit_parallel_limit() {
let config = RuntimeOptimizationConfig {
enabled: true,
speculative_skill_routing: true,
max_speculative_llm_calls_per_turn: 5,
max_parallel_runtime_tasks: 4,
..Default::default()
};
assert!(config.validate().is_err());
}
}