Skip to main content

zeph_core/agent/
session_config.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::sync::Arc;
5
6use crate::config::{
7    Config, DebugConfig, DocumentConfig, GraphConfig, LearningConfig, OrchestrationConfig,
8    SecurityConfig, TimeoutConfig,
9};
10use crate::vault::Secret;
11
12/// Reserve ratio for `with_context_budget`: fraction of budget reserved for LLM reply.
13///
14/// Extracted from the hardcoded `0.20` literal used in both `spawn_acp_agent` and `runner.rs`.
15pub const CONTEXT_BUDGET_RESERVE_RATIO: f32 = 0.20;
16
17/// All config-derived fields needed to configure an `Agent` session.
18///
19/// This is the single source of truth for config → agent wiring.
20/// Adding a new config field requires exactly three changes:
21///
22/// 1. Add the field here.
23/// 2. Map it in [`AgentSessionConfig::from_config`].
24/// 3. Apply it in [`super::Agent::apply_session_config`] (destructure triggers a compile error if
25///    you forget step 3 — see the S4 note in the critic handoff).
26///
27/// ## What is NOT here
28///
29/// - **Shared runtime objects** (`provider`, `registry`, `memory`, `mcp_manager`, etc.) — these
30///   are expensive to create and shared across sessions; they stay in `SharedAgentDeps`.
31/// - **ACP-specific fields** (`acp_max_sessions`, bearer token, etc.) — transport-level, not
32///   agent-level.
33/// - **Optional runtime providers** (`summary_provider`, `judge_provider`,
34///   `quarantine_provider`) — these contain HTTP client pools (`AnyProvider`) that carry runtime
35///   state; callers wire them separately via `with_summary_provider` / `with_judge_provider` /
36///   `apply_quarantine_provider`.
37/// - **`mcp_config`** — passed alongside runtime MCP objects in `with_mcp()`; separating it
38///   from `mcp_tools` / `mcp_manager` would make the call site awkward.
39/// - **Runner-only fields** (`compression`, `routing`, `autosave`, `hybrid_search`, `trust_config`,
40///   `disambiguation_threshold`, `logging_config`, `subagent`, `experiment`, `instruction`,
41///   `lsp_hooks`, `response_cache`, `cost_tracker`) — not used in ACP sessions; keeping them out
42///   avoids unused-field noise and prevents inadvertent ACP behavior changes.
43/// - **Scheduler runtime objects** (`scheduler_executor`, broadcast senders) — runtime state,
44///   not config-derived values.
45#[derive(Clone)]
46#[allow(clippy::struct_excessive_bools)]
47pub struct AgentSessionConfig {
48    // Tool behavior
49    pub max_tool_iterations: usize,
50    pub max_tool_retries: usize,
51    pub max_retry_duration_secs: u64,
52    pub retry_base_ms: u64,
53    pub retry_max_ms: u64,
54    pub parameter_reformat_provider: String,
55    pub tool_repeat_threshold: usize,
56    pub tool_summarization: bool,
57    pub tool_call_cutoff: usize,
58    pub overflow_config: zeph_tools::OverflowConfig,
59    pub permission_policy: zeph_tools::PermissionPolicy,
60
61    // Model
62    pub model_name: String,
63    pub embed_model: String,
64
65    // Semantic cache
66    pub semantic_cache_enabled: bool,
67    pub semantic_cache_threshold: f32,
68    pub semantic_cache_max_candidates: u32,
69
70    // Memory / compaction
71    pub budget_tokens: usize,
72    pub soft_compaction_threshold: f32,
73    pub hard_compaction_threshold: f32,
74    pub compaction_preserve_tail: usize,
75    pub compaction_cooldown_turns: u8,
76    pub prune_protect_tokens: usize,
77    pub redact_credentials: bool,
78
79    // Security
80    pub security: SecurityConfig,
81    pub timeouts: TimeoutConfig,
82
83    // Feature configs
84    pub learning: LearningConfig,
85    pub document_config: DocumentConfig,
86    pub graph_config: GraphConfig,
87    pub anomaly_config: zeph_tools::AnomalyConfig,
88    pub result_cache_config: zeph_tools::ResultCacheConfig,
89    pub orchestration_config: OrchestrationConfig,
90    pub debug_config: DebugConfig,
91    pub server_compaction: bool,
92
93    /// Custom secrets from config.
94    ///
95    /// Stored as `Arc` because `Secret` intentionally does not implement `Clone` —
96    /// the wrapper prevents accidental duplication. Iteration produces new `Secret`
97    /// values via `Secret::new(v.expose())` on the consumption side.
98    pub secrets: Arc<[(String, Secret)]>,
99}
100
101impl AgentSessionConfig {
102    /// Build from a resolved [`Config`] snapshot and a pre-computed `budget_tokens`.
103    ///
104    /// `budget_tokens` is passed as a parameter because its computation (`auto_budget_tokens`)
105    /// depends on the active provider and must happen before `AgentSessionConfig` is constructed.
106    #[must_use]
107    pub fn from_config(config: &Config, budget_tokens: usize) -> Self {
108        Self {
109            max_tool_iterations: config.agent.max_tool_iterations,
110            max_tool_retries: config.tools.retry.max_attempts,
111            max_retry_duration_secs: config.tools.retry.budget_secs,
112            retry_base_ms: config.tools.retry.base_ms,
113            retry_max_ms: config.tools.retry.max_ms,
114            parameter_reformat_provider: config.tools.retry.parameter_reformat_provider.clone(),
115            tool_repeat_threshold: config.agent.tool_repeat_threshold,
116            tool_summarization: config.tools.summarize_output,
117            tool_call_cutoff: config.memory.tool_call_cutoff,
118            overflow_config: config.tools.overflow.clone(),
119            permission_policy: config
120                .tools
121                .permission_policy(config.security.autonomy_level),
122            model_name: config.llm.effective_model().to_owned(),
123            embed_model: crate::bootstrap::effective_embedding_model(config),
124            semantic_cache_enabled: config.llm.semantic_cache_enabled,
125            semantic_cache_threshold: config.llm.semantic_cache_threshold,
126            semantic_cache_max_candidates: config.llm.semantic_cache_max_candidates,
127            budget_tokens,
128            soft_compaction_threshold: config.memory.soft_compaction_threshold,
129            hard_compaction_threshold: config.memory.hard_compaction_threshold,
130            compaction_preserve_tail: config.memory.compaction_preserve_tail,
131            compaction_cooldown_turns: config.memory.compaction_cooldown_turns,
132            prune_protect_tokens: config.memory.prune_protect_tokens,
133            redact_credentials: config.memory.redact_credentials,
134            security: config.security.clone(),
135            timeouts: config.timeouts,
136            learning: config.skills.learning.clone(),
137            document_config: config.memory.documents.clone(),
138            graph_config: config.memory.graph.clone(),
139            anomaly_config: config.tools.anomaly.clone(),
140            result_cache_config: config.tools.result_cache.clone(),
141            orchestration_config: config.orchestration.clone(),
142            debug_config: config.debug.clone(),
143            server_compaction: config.llm.providers.iter().any(|e| e.server_compaction),
144            secrets: config
145                .secrets
146                .custom
147                .iter()
148                .map(|(k, v)| (k.clone(), Secret::new(v.expose().to_owned())))
149                .collect::<Vec<_>>()
150                .into(),
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn from_config_maps_all_fields() {
161        let config = Config::default();
162        let budget = 100_000;
163        let sc = AgentSessionConfig::from_config(&config, budget);
164
165        assert_eq!(sc.max_tool_iterations, config.agent.max_tool_iterations);
166        assert_eq!(sc.max_tool_retries, config.tools.retry.max_attempts);
167        assert_eq!(sc.max_retry_duration_secs, config.tools.retry.budget_secs);
168        assert_eq!(sc.retry_base_ms, config.tools.retry.base_ms);
169        assert_eq!(sc.retry_max_ms, config.tools.retry.max_ms);
170        assert_eq!(
171            sc.parameter_reformat_provider,
172            config.tools.retry.parameter_reformat_provider
173        );
174        assert_eq!(sc.tool_repeat_threshold, config.agent.tool_repeat_threshold);
175        assert_eq!(sc.tool_summarization, config.tools.summarize_output);
176        assert_eq!(sc.tool_call_cutoff, config.memory.tool_call_cutoff);
177        assert_eq!(sc.model_name, config.llm.effective_model());
178        assert_eq!(
179            sc.embed_model,
180            crate::bootstrap::effective_embedding_model(&config)
181        );
182        assert_eq!(sc.semantic_cache_enabled, config.llm.semantic_cache_enabled);
183        assert_eq!(
184            sc.semantic_cache_threshold,
185            config.llm.semantic_cache_threshold
186        );
187        assert_eq!(
188            sc.semantic_cache_max_candidates,
189            config.llm.semantic_cache_max_candidates
190        );
191        assert_eq!(sc.budget_tokens, budget);
192        assert_eq!(
193            sc.soft_compaction_threshold,
194            config.memory.soft_compaction_threshold
195        );
196        assert_eq!(
197            sc.hard_compaction_threshold,
198            config.memory.hard_compaction_threshold
199        );
200        assert_eq!(
201            sc.compaction_preserve_tail,
202            config.memory.compaction_preserve_tail
203        );
204        assert_eq!(
205            sc.compaction_cooldown_turns,
206            config.memory.compaction_cooldown_turns
207        );
208        assert_eq!(sc.prune_protect_tokens, config.memory.prune_protect_tokens);
209        assert_eq!(sc.redact_credentials, config.memory.redact_credentials);
210        assert_eq!(sc.graph_config.enabled, config.memory.graph.enabled);
211        assert_eq!(
212            sc.orchestration_config.enabled,
213            config.orchestration.enabled
214        );
215        assert_eq!(
216            sc.orchestration_config.max_tasks,
217            config.orchestration.max_tasks
218        );
219        assert_eq!(sc.anomaly_config.enabled, config.tools.anomaly.enabled);
220        assert_eq!(
221            sc.result_cache_config.enabled,
222            config.tools.result_cache.enabled
223        );
224        assert_eq!(
225            sc.result_cache_config.ttl_secs,
226            config.tools.result_cache.ttl_secs
227        );
228        assert_eq!(sc.debug_config.enabled, config.debug.enabled);
229        assert_eq!(
230            sc.document_config.rag_enabled,
231            config.memory.documents.rag_enabled
232        );
233        assert_eq!(
234            sc.overflow_config.threshold,
235            config.tools.overflow.threshold
236        );
237        assert_eq!(
238            sc.permission_policy.autonomy_level(),
239            config.security.autonomy_level
240        );
241        assert_eq!(sc.security.autonomy_level, config.security.autonomy_level);
242        assert_eq!(sc.timeouts.llm_seconds, config.timeouts.llm_seconds);
243        assert_eq!(sc.learning.enabled, config.skills.learning.enabled);
244        assert_eq!(
245            sc.server_compaction,
246            config.llm.providers.iter().any(|e| e.server_compaction)
247        );
248        assert_eq!(sc.secrets.len(), config.secrets.custom.len());
249    }
250}