ai_agents_runtime/optimization/
config.rs1use serde::{Deserialize, Serialize};
2
3use ai_agents_core::{AgentError, Result};
4use ai_agents_tools::ToolSchemaPromptMode;
5
6#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8#[serde(default)]
9pub struct RuntimeConfig {
10 pub optimization: RuntimeOptimizationConfig,
12 pub tool_schema_prompt_mode: ToolSchemaPromptMode,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18#[serde(default)]
19pub struct RuntimeOptimizationConfig {
20 pub enabled: bool,
22 pub max_speculative_llm_calls_per_turn: u32,
24 pub pre_response_deterministic_transitions: bool,
26 pub pre_response_extractors: bool,
28 pub speculative_state_transitions: bool,
30 pub speculative_skill_routing: bool,
32 pub speculative_reasoning_auto: bool,
34 pub parallel_post_turn_memory: bool,
36 pub parallel_orchestration_vote_extraction: bool,
38 pub background_observability_export: bool,
40 pub streaming_policy: StreamingOptimizationPolicy,
42 pub max_parallel_runtime_tasks: usize,
44 pub post_turn: PostTurnOptimizationConfig,
46}
47
48impl Default for RuntimeOptimizationConfig {
49 fn default() -> Self {
50 Self {
51 enabled: false,
52 max_speculative_llm_calls_per_turn: 0,
53 pre_response_deterministic_transitions: false,
54 pre_response_extractors: false,
55 speculative_state_transitions: false,
56 speculative_skill_routing: false,
57 speculative_reasoning_auto: false,
58 parallel_post_turn_memory: false,
59 parallel_orchestration_vote_extraction: false,
60 background_observability_export: false,
61 streaming_policy: StreamingOptimizationPolicy::PreflightOnly,
62 max_parallel_runtime_tasks: 4,
63 post_turn: PostTurnOptimizationConfig::default(),
64 }
65 }
66}
67
68impl RuntimeOptimizationConfig {
69 pub fn validate(&self) -> Result<()> {
71 if self.max_parallel_runtime_tasks == 0 {
72 return Err(AgentError::InvalidSpec(
73 "runtime.optimization.max_parallel_runtime_tasks must be greater than 0".into(),
74 ));
75 }
76 if self.post_turn.max_background_tasks == 0 && self.post_turn.any_background_tasks_enabled()
77 {
78 return Err(AgentError::InvalidSpec(
79 "runtime.optimization.post_turn.max_background_tasks must be greater than 0 when background maintenance is enabled".into(),
80 ));
81 }
82 if self.background_observability_export {
83 return Err(AgentError::InvalidSpec(
84 "runtime.optimization.background_observability_export requires snapshot export support and is not enabled yet".into(),
85 ));
86 }
87 let any_speculative = self.speculative_state_transitions
88 || self.speculative_skill_routing
89 || self.speculative_reasoning_auto;
90 if any_speculative {
91 if !self.enabled {
92 return Err(AgentError::InvalidSpec(
93 "runtime.optimization.enabled must be true when speculative branch settings are enabled".into(),
94 ));
95 }
96 if self.max_speculative_llm_calls_per_turn == 0 {
97 return Err(AgentError::InvalidSpec(
98 "runtime.optimization.max_speculative_llm_calls_per_turn must be greater than 0 when speculative branch settings are enabled".into(),
99 ));
100 }
101 }
102 if self.max_speculative_llm_calls_per_turn > self.max_parallel_runtime_tasks as u32 {
103 return Err(AgentError::InvalidSpec(
104 "runtime.optimization.max_speculative_llm_calls_per_turn must be less than or equal to max_parallel_runtime_tasks".into(),
105 ));
106 }
107 if self.post_turn.sessions != MaintenanceTaskPolicy::default() {
108 return Err(AgentError::InvalidSpec(
109 "runtime.optimization.post_turn.sessions is reserved until session maintenance scheduling is enabled".into(),
110 ));
111 }
112 if self.post_turn.memory_compression != MaintenanceTaskPolicy::default() {
113 return Err(AgentError::InvalidSpec(
114 "runtime.optimization.post_turn.memory_compression is reserved until compression scheduling is enabled".into(),
115 ));
116 }
117 Ok(())
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123#[serde(default)]
124pub struct PostTurnOptimizationConfig {
125 pub facts: MaintenanceTaskPolicy,
127 pub relationships: MaintenanceTaskPolicy,
129 pub sessions: MaintenanceTaskPolicy,
131 pub memory_compression: MaintenanceTaskPolicy,
133 pub max_background_tasks: usize,
135 pub on_background_overflow: BackgroundOverflowPolicy,
137}
138
139impl Default for PostTurnOptimizationConfig {
140 fn default() -> Self {
141 Self {
142 facts: MaintenanceTaskPolicy {
143 mode: MaintenanceMode::InlineSerial,
144 await_before_next_turn: AwaitBeforeNextTurn::Always,
145 },
146 relationships: MaintenanceTaskPolicy {
147 mode: MaintenanceMode::InlineSerial,
148 await_before_next_turn: AwaitBeforeNextTurn::Always,
149 },
150 sessions: MaintenanceTaskPolicy::default(),
151 memory_compression: MaintenanceTaskPolicy::default(),
152 max_background_tasks: 16,
153 on_background_overflow: BackgroundOverflowPolicy::RunInline,
154 }
155 }
156}
157
158impl PostTurnOptimizationConfig {
159 pub fn any_background_tasks_enabled(&self) -> bool {
161 matches!(self.facts.mode, MaintenanceMode::Background)
162 || matches!(self.relationships.mode, MaintenanceMode::Background)
163 || matches!(self.sessions.mode, MaintenanceMode::Background)
164 || matches!(self.memory_compression.mode, MaintenanceMode::Background)
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
170#[serde(default)]
171pub struct MaintenanceTaskPolicy {
172 pub mode: MaintenanceMode,
174 pub await_before_next_turn: AwaitBeforeNextTurn,
176}
177
178impl Default for MaintenanceTaskPolicy {
179 fn default() -> Self {
180 Self {
181 mode: MaintenanceMode::InlineSerial,
182 await_before_next_turn: AwaitBeforeNextTurn::Always,
183 }
184 }
185}
186
187#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
189#[serde(rename_all = "snake_case")]
190pub enum MaintenanceMode {
191 #[default]
193 InlineSerial,
194 InlineParallel,
196 Background,
198}
199
200#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
202#[serde(rename_all = "snake_case")]
203pub enum AwaitBeforeNextTurn {
204 Never,
206 SameActor,
208 #[default]
210 Always,
211}
212
213#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
215#[serde(rename_all = "snake_case")]
216pub enum BackgroundOverflowPolicy {
217 #[default]
219 RunInline,
220 Drop,
222 Error,
224}
225
226#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
228#[serde(rename_all = "snake_case")]
229pub enum StreamingOptimizationPolicy {
230 #[default]
232 PreflightOnly,
233 BufferUntilRoutingDone,
235 Disabled,
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn accepts_buffered_streaming_policy() {
245 let config = RuntimeOptimizationConfig {
246 enabled: true,
247 streaming_policy: StreamingOptimizationPolicy::BufferUntilRoutingDone,
248 ..Default::default()
249 };
250 assert!(config.validate().is_ok());
251 }
252
253 #[test]
254 fn rejects_reserved_session_maintenance_policy() {
255 let mut config = RuntimeOptimizationConfig {
256 enabled: true,
257 ..Default::default()
258 };
259 config.post_turn.sessions.mode = MaintenanceMode::Background;
260 assert!(config.validate().is_err());
261 }
262
263 #[test]
264 fn rejects_reserved_compression_maintenance_policy() {
265 let mut config = RuntimeOptimizationConfig {
266 enabled: true,
267 ..Default::default()
268 };
269 config.post_turn.memory_compression.mode = MaintenanceMode::Background;
270 assert!(config.validate().is_err());
271 }
272
273 #[test]
274 fn speculative_flags_require_positive_cap() {
275 let config = RuntimeOptimizationConfig {
276 enabled: true,
277 speculative_skill_routing: true,
278 max_speculative_llm_calls_per_turn: 0,
279 ..Default::default()
280 };
281 assert!(config.validate().is_err());
282 }
283
284 #[test]
285 fn speculative_cap_must_fit_parallel_limit() {
286 let config = RuntimeOptimizationConfig {
287 enabled: true,
288 speculative_skill_routing: true,
289 max_speculative_llm_calls_per_turn: 5,
290 max_parallel_runtime_tasks: 4,
291 ..Default::default()
292 };
293 assert!(config.validate().is_err());
294 }
295}