Skip to main content

zeph_config/
features.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use serde::{Deserialize, Serialize};
5
6use crate::defaults::{default_skill_paths, default_true};
7use crate::learning::LearningConfig;
8use crate::security::TrustConfig;
9
10fn default_disambiguation_threshold() -> f32 {
11    0.05
12}
13
14fn default_cosine_weight() -> f32 {
15    0.7
16}
17
18fn default_hybrid_search() -> bool {
19    true
20}
21
22fn default_max_active_skills() -> usize {
23    5
24}
25
26fn default_index_watch() -> bool {
27    true
28}
29
30fn default_index_search_enabled() -> bool {
31    true
32}
33
34fn default_index_max_chunks() -> usize {
35    12
36}
37
38fn default_index_score_threshold() -> f32 {
39    0.25
40}
41
42fn default_index_budget_ratio() -> f32 {
43    0.40
44}
45
46fn default_index_repo_map_tokens() -> usize {
47    500
48}
49
50fn default_repo_map_ttl_secs() -> u64 {
51    300
52}
53
54fn default_vault_backend() -> String {
55    "env".into()
56}
57
58fn default_max_daily_cents() -> u32 {
59    0
60}
61
62fn default_otlp_endpoint() -> String {
63    "http://localhost:4317".into()
64}
65
66fn default_pid_file() -> String {
67    "~/.zeph/zeph.pid".into()
68}
69
70fn default_health_interval() -> u64 {
71    30
72}
73
74fn default_max_restart_backoff() -> u64 {
75    60
76}
77
78fn default_scheduler_tick_interval() -> u64 {
79    60
80}
81
82fn default_scheduler_max_tasks() -> usize {
83    100
84}
85
86fn default_gateway_bind() -> String {
87    "127.0.0.1".into()
88}
89
90fn default_gateway_port() -> u16 {
91    8090
92}
93
94fn default_gateway_rate_limit() -> u32 {
95    120
96}
97
98fn default_gateway_max_body() -> usize {
99    1_048_576
100}
101
102/// Controls how skills are formatted in the system prompt.
103#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
104#[serde(rename_all = "lowercase")]
105pub enum SkillPromptMode {
106    Full,
107    Compact,
108    #[default]
109    Auto,
110}
111
112#[derive(Debug, Deserialize, Serialize)]
113pub struct SkillsConfig {
114    #[serde(default = "default_skill_paths")]
115    pub paths: Vec<String>,
116    #[serde(default = "default_max_active_skills")]
117    pub max_active_skills: usize,
118    #[serde(default = "default_disambiguation_threshold")]
119    pub disambiguation_threshold: f32,
120    #[serde(default = "default_cosine_weight")]
121    pub cosine_weight: f32,
122    #[serde(default = "default_hybrid_search")]
123    pub hybrid_search: bool,
124    #[serde(default)]
125    pub learning: LearningConfig,
126    #[serde(default)]
127    pub trust: TrustConfig,
128    #[serde(default)]
129    pub prompt_mode: SkillPromptMode,
130}
131
132#[derive(Debug, Deserialize, Serialize)]
133pub struct IndexConfig {
134    #[serde(default)]
135    pub enabled: bool,
136    #[serde(default = "default_index_search_enabled")]
137    pub search_enabled: bool,
138    #[serde(default = "default_index_watch")]
139    pub watch: bool,
140    #[serde(default = "default_index_max_chunks")]
141    pub max_chunks: usize,
142    #[serde(default = "default_index_score_threshold")]
143    pub score_threshold: f32,
144    #[serde(default = "default_index_budget_ratio")]
145    pub budget_ratio: f32,
146    #[serde(default = "default_index_repo_map_tokens")]
147    pub repo_map_tokens: usize,
148    #[serde(default = "default_repo_map_ttl_secs")]
149    pub repo_map_ttl_secs: u64,
150}
151
152impl Default for IndexConfig {
153    fn default() -> Self {
154        Self {
155            enabled: false,
156            search_enabled: default_index_search_enabled(),
157            watch: default_index_watch(),
158            max_chunks: default_index_max_chunks(),
159            score_threshold: default_index_score_threshold(),
160            budget_ratio: default_index_budget_ratio(),
161            repo_map_tokens: default_index_repo_map_tokens(),
162            repo_map_ttl_secs: default_repo_map_ttl_secs(),
163        }
164    }
165}
166
167#[derive(Debug, Deserialize, Serialize)]
168pub struct VaultConfig {
169    #[serde(default = "default_vault_backend")]
170    pub backend: String,
171}
172
173impl Default for VaultConfig {
174    fn default() -> Self {
175        Self {
176            backend: default_vault_backend(),
177        }
178    }
179}
180
181#[derive(Debug, Deserialize, Serialize)]
182pub struct CostConfig {
183    #[serde(default = "default_true")]
184    pub enabled: bool,
185    #[serde(default = "default_max_daily_cents")]
186    pub max_daily_cents: u32,
187}
188
189impl Default for CostConfig {
190    fn default() -> Self {
191        Self {
192            enabled: true,
193            max_daily_cents: default_max_daily_cents(),
194        }
195    }
196}
197
198#[derive(Debug, Deserialize, Serialize)]
199pub struct ObservabilityConfig {
200    #[serde(default)]
201    pub exporter: String,
202    #[serde(default = "default_otlp_endpoint")]
203    pub endpoint: String,
204}
205
206impl Default for ObservabilityConfig {
207    fn default() -> Self {
208        Self {
209            exporter: String::new(),
210            endpoint: default_otlp_endpoint(),
211        }
212    }
213}
214
215#[derive(Debug, Clone, Deserialize, Serialize)]
216pub struct GatewayConfig {
217    #[serde(default)]
218    pub enabled: bool,
219    #[serde(default = "default_gateway_bind")]
220    pub bind: String,
221    #[serde(default = "default_gateway_port")]
222    pub port: u16,
223    #[serde(default)]
224    pub auth_token: Option<String>,
225    #[serde(default = "default_gateway_rate_limit")]
226    pub rate_limit: u32,
227    #[serde(default = "default_gateway_max_body")]
228    pub max_body_size: usize,
229}
230
231impl Default for GatewayConfig {
232    fn default() -> Self {
233        Self {
234            enabled: false,
235            bind: default_gateway_bind(),
236            port: default_gateway_port(),
237            auth_token: None,
238            rate_limit: default_gateway_rate_limit(),
239            max_body_size: default_gateway_max_body(),
240        }
241    }
242}
243
244#[derive(Debug, Clone, Deserialize, Serialize)]
245pub struct DaemonConfig {
246    #[serde(default)]
247    pub enabled: bool,
248    #[serde(default = "default_pid_file")]
249    pub pid_file: String,
250    #[serde(default = "default_health_interval")]
251    pub health_interval_secs: u64,
252    #[serde(default = "default_max_restart_backoff")]
253    pub max_restart_backoff_secs: u64,
254}
255
256impl Default for DaemonConfig {
257    fn default() -> Self {
258        Self {
259            enabled: false,
260            pid_file: default_pid_file(),
261            health_interval_secs: default_health_interval(),
262            max_restart_backoff_secs: default_max_restart_backoff(),
263        }
264    }
265}
266
267#[derive(Debug, Clone, Deserialize, Serialize)]
268pub struct SchedulerConfig {
269    #[serde(default)]
270    pub enabled: bool,
271    #[serde(default = "default_scheduler_tick_interval")]
272    pub tick_interval_secs: u64,
273    #[serde(default = "default_scheduler_max_tasks")]
274    pub max_tasks: usize,
275    #[serde(default)]
276    pub tasks: Vec<ScheduledTaskConfig>,
277}
278
279impl Default for SchedulerConfig {
280    fn default() -> Self {
281        Self {
282            enabled: true,
283            tick_interval_secs: default_scheduler_tick_interval(),
284            max_tasks: default_scheduler_max_tasks(),
285            tasks: Vec::new(),
286        }
287    }
288}
289
290/// Task kind for scheduled tasks.
291///
292/// Known variants map to built-in handlers; `Custom` accommodates user-defined task types.
293#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
294#[serde(rename_all = "snake_case")]
295pub enum ScheduledTaskKind {
296    MemoryCleanup,
297    SkillRefresh,
298    HealthCheck,
299    UpdateCheck,
300    Experiment,
301    Custom(String),
302}
303
304#[derive(Debug, Clone, Deserialize, Serialize)]
305pub struct ScheduledTaskConfig {
306    pub name: String,
307    #[serde(default, skip_serializing_if = "Option::is_none")]
308    pub cron: Option<String>,
309    #[serde(default, skip_serializing_if = "Option::is_none")]
310    pub run_at: Option<String>,
311    pub kind: ScheduledTaskKind,
312    #[serde(default)]
313    pub config: serde_json::Value,
314}
315
316fn default_trace_service_name() -> String {
317    "zeph".into()
318}
319
320/// Configuration for OTel-compatible trace dumps (`format = "trace"`).
321///
322/// When `format = "trace"`, the `TracingCollector` writes a `trace.json` file in OTLP JSON
323/// format at session end. Legacy numbered dump files are NOT written by default (C-03).
324/// When the `otel` feature is enabled and `otlp_endpoint` is set, spans are also exported
325/// via OTLP gRPC.
326#[derive(Debug, Clone, Deserialize, Serialize)]
327#[serde(default)]
328pub struct TraceConfig {
329    /// OTLP gRPC endpoint (only used when `otel` feature is enabled).
330    /// Defaults to `observability.endpoint` if unset (I-01).
331    #[serde(default = "default_otlp_endpoint")]
332    pub otlp_endpoint: String,
333    /// Service name reported to the `OTel` collector.
334    #[serde(default = "default_trace_service_name")]
335    pub service_name: String,
336    /// Redact sensitive data in span attributes (default: `true`) (C-01).
337    #[serde(default = "default_true")]
338    pub redact: bool,
339}
340
341impl Default for TraceConfig {
342    fn default() -> Self {
343        Self {
344            otlp_endpoint: default_otlp_endpoint(),
345            service_name: default_trace_service_name(),
346            redact: true,
347        }
348    }
349}
350
351#[derive(Debug, Clone, Deserialize, Serialize)]
352#[serde(default)]
353pub struct DebugConfig {
354    /// Enable debug dump on startup (CLI `--debug-dump` takes priority).
355    pub enabled: bool,
356    /// Directory where per-session debug dump subdirectories are created.
357    #[serde(default = "crate::defaults::default_debug_output_dir")]
358    pub output_dir: std::path::PathBuf,
359    /// Output format: `"json"` (default), `"raw"` (API payload), or `"trace"` (OTLP spans).
360    pub format: crate::dump_format::DumpFormat,
361    /// `OTel` trace configuration (only used when `format = "trace"`).
362    pub traces: TraceConfig,
363}
364
365impl Default for DebugConfig {
366    fn default() -> Self {
367        Self {
368            enabled: false,
369            output_dir: super::defaults::default_debug_output_dir(),
370            format: crate::dump_format::DumpFormat::default(),
371            traces: TraceConfig::default(),
372        }
373    }
374}