Skip to main content

nemo_flow_adaptive/
config.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Canonical adaptive config and diagnostics types.
5
6use nemo_flow::plugin::ConfigPolicy;
7use serde::{Deserialize, Serialize};
8use serde_json::{Map, Value as Json};
9
10/// Canonical config document for the adaptive plugin component.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct AdaptiveConfig {
13    /// Adaptive config schema version.
14    #[serde(default = "default_adaptive_config_version")]
15    pub version: u32,
16    /// Optional explicit agent identifier used by adaptive state.
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub agent_id: Option<String>,
19    /// Shared state backend configuration.
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub state: Option<StateConfig>,
22    /// Built-in adaptive telemetry settings.
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub telemetry: Option<TelemetryComponentConfig>,
25    /// Built-in LLM hint injection settings.
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub adaptive_hints: Option<AdaptiveHintsComponentConfig>,
28    /// Built-in tool scheduling settings.
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub tool_parallelism: Option<ToolParallelismComponentConfig>,
31    /// Adaptive Cache Governor settings.
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub acg: Option<AcgComponentConfig>,
34    /// Adaptive-local unsupported-config policy.
35    #[serde(default)]
36    pub policy: ConfigPolicy,
37}
38
39impl Default for AdaptiveConfig {
40    fn default() -> Self {
41        Self {
42            version: default_adaptive_config_version(),
43            agent_id: None,
44            state: None,
45            telemetry: None,
46            adaptive_hints: None,
47            tool_parallelism: None,
48            acg: None,
49            policy: ConfigPolicy::default(),
50        }
51    }
52}
53
54/// Shared state configuration consumed by adaptive features that need persistence.
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct StateConfig {
57    /// Backend selection for adaptive state.
58    pub backend: BackendSpec,
59}
60
61/// Dynamic backend selection. `config` is backend-specific.
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct BackendSpec {
64    /// Backend kind such as `in_memory` or `redis`.
65    pub kind: String,
66    /// Backend-specific JSON object.
67    #[serde(default)]
68    pub config: Map<String, Json>,
69}
70
71impl BackendSpec {
72    /// Creates an in-memory backend spec.
73    pub fn in_memory() -> Self {
74        Self {
75            kind: "in_memory".to_string(),
76            config: Map::new(),
77        }
78    }
79
80    #[cfg(feature = "redis-backend")]
81    /// Creates a Redis backend spec.
82    pub fn redis(url: impl Into<String>, key_prefix: impl Into<String>) -> Self {
83        let mut config = Map::new();
84        config.insert("url".to_string(), Json::String(url.into()));
85        config.insert("key_prefix".to_string(), Json::String(key_prefix.into()));
86        Self {
87            kind: "redis".to_string(),
88            config,
89        }
90    }
91}
92
93/// Typed helper for telemetry settings.
94#[derive(Debug, Clone, Default, Serialize, Deserialize)]
95pub struct TelemetryComponentConfig {
96    /// Optional subscriber registration name override.
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub subscriber_name: Option<String>,
99    /// Enabled learner identifiers.
100    #[serde(default)]
101    pub learners: Vec<String>,
102}
103
104/// Typed helper for adaptive hints settings.
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct AdaptiveHintsComponentConfig {
107    /// Intercept priority. Lower values run first.
108    #[serde(default = "default_priority")]
109    pub priority: i32,
110    /// Whether later request intercepts should be skipped after this one runs.
111    #[serde(default)]
112    pub break_chain: bool,
113    /// Whether to inject the adaptive hints header.
114    #[serde(default = "default_true")]
115    pub inject_header: bool,
116    /// JSON path used when injecting request-body hints.
117    #[serde(default = "default_adaptive_hints_path")]
118    pub inject_body_path: String,
119}
120
121impl Default for AdaptiveHintsComponentConfig {
122    fn default() -> Self {
123        Self {
124            priority: default_priority(),
125            break_chain: false,
126            inject_header: true,
127            inject_body_path: default_adaptive_hints_path(),
128        }
129    }
130}
131
132/// Typed helper for tool parallelism settings.
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct ToolParallelismComponentConfig {
135    /// Intercept priority. Lower values run first.
136    #[serde(default = "default_priority")]
137    pub priority: i32,
138    /// Scheduling mode such as `observe_only`, `inject_hints`, or `schedule`.
139    #[serde(default = "default_tool_parallelism_mode")]
140    pub mode: String,
141}
142
143impl Default for ToolParallelismComponentConfig {
144    fn default() -> Self {
145        Self {
146            priority: default_priority(),
147            mode: default_tool_parallelism_mode(),
148        }
149    }
150}
151
152/// Typed helper for the built-in Adaptive Cache Governor (ACG) component.
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct AcgComponentConfig {
155    /// Which provider plugin to activate (e.g. "anthropic", "openai", "passthrough").
156    #[serde(default = "default_acg_provider")]
157    pub provider: String,
158    /// Rolling observation window size. Default: 100.
159    #[serde(default = "default_acg_observation_window")]
160    pub observation_window: usize,
161    /// LLM execution intercept priority. Default: 50.
162    #[serde(default = "default_acg_priority")]
163    pub priority: i32,
164    /// Stability classification thresholds used by the learner.
165    #[serde(default)]
166    pub stability_thresholds: crate::acg::stability::StabilityThresholds,
167}
168
169impl Default for AcgComponentConfig {
170    fn default() -> Self {
171        Self {
172            provider: default_acg_provider(),
173            observation_window: default_acg_observation_window(),
174            priority: default_acg_priority(),
175            stability_thresholds: crate::acg::stability::StabilityThresholds::default(),
176        }
177    }
178}
179
180fn default_adaptive_config_version() -> u32 {
181    1
182}
183
184fn default_priority() -> i32 {
185    100
186}
187
188fn default_true() -> bool {
189    true
190}
191
192fn default_adaptive_hints_path() -> String {
193    "nvext.agent_hints".to_string()
194}
195
196fn default_tool_parallelism_mode() -> String {
197    "observe_only".to_string()
198}
199
200fn default_acg_provider() -> String {
201    "passthrough".to_string()
202}
203
204fn default_acg_observation_window() -> usize {
205    100
206}
207
208fn default_acg_priority() -> i32 {
209    50
210}
211
212#[cfg(test)]
213#[path = "../tests/unit/config_tests.rs"]
214mod tests;