Skip to main content

zeph_config/
agent.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::path::PathBuf;
5
6use serde::{Deserialize, Serialize};
7
8use crate::subagent::{HookDef, MemoryScope, PermissionMode};
9
10fn default_max_tool_iterations() -> usize {
11    10
12}
13
14fn default_auto_update_check() -> bool {
15    true
16}
17
18fn default_focus_compression_interval() -> usize {
19    12
20}
21
22fn default_focus_reminder_interval() -> usize {
23    15
24}
25
26fn default_focus_min_messages_per_focus() -> usize {
27    8
28}
29
30fn default_focus_max_knowledge_tokens() -> usize {
31    4096
32}
33
34fn default_max_tool_retries() -> usize {
35    2
36}
37
38fn default_max_retry_duration_secs() -> u64 {
39    30
40}
41
42fn default_tool_repeat_threshold() -> usize {
43    2
44}
45
46fn default_tool_filter_top_k() -> usize {
47    6
48}
49
50fn default_tool_filter_min_description_words() -> usize {
51    5
52}
53
54fn default_tool_filter_always_on() -> Vec<String> {
55    vec![
56        "memory_search".into(),
57        "memory_save".into(),
58        "load_skill".into(),
59        "bash".into(),
60        "read".into(),
61        "edit".into(),
62    ]
63}
64
65fn default_instruction_auto_detect() -> bool {
66    true
67}
68
69fn default_max_concurrent() -> usize {
70    5
71}
72
73fn default_transcript_enabled() -> bool {
74    true
75}
76
77fn default_transcript_max_files() -> usize {
78    50
79}
80
81/// Configuration for focus-based active context compression (#1850).
82#[derive(Debug, Clone, Deserialize, Serialize)]
83#[serde(default)]
84pub struct FocusConfig {
85    /// Enable focus tools (`start_focus` / `complete_focus`). Default: `false`.
86    pub enabled: bool,
87    /// Suggest focus after this many turns without one. Default: `12`.
88    #[serde(default = "default_focus_compression_interval")]
89    pub compression_interval: usize,
90    /// Remind the agent every N turns when focus is overdue. Default: `15`.
91    #[serde(default = "default_focus_reminder_interval")]
92    pub reminder_interval: usize,
93    /// Minimum messages required before suggesting a focus. Default: `8`.
94    #[serde(default = "default_focus_min_messages_per_focus")]
95    pub min_messages_per_focus: usize,
96    /// Maximum tokens the Knowledge block may grow to before old entries are trimmed.
97    /// Default: `4096`.
98    #[serde(default = "default_focus_max_knowledge_tokens")]
99    pub max_knowledge_tokens: usize,
100}
101
102impl Default for FocusConfig {
103    fn default() -> Self {
104        Self {
105            enabled: false,
106            compression_interval: default_focus_compression_interval(),
107            reminder_interval: default_focus_reminder_interval(),
108            min_messages_per_focus: default_focus_min_messages_per_focus(),
109            max_knowledge_tokens: default_focus_max_knowledge_tokens(),
110        }
111    }
112}
113
114/// Dynamic tool schema filtering configuration (#2020).
115///
116/// When enabled, only a subset of tool definitions is sent to the LLM on each turn,
117/// selected by embedding similarity between the user query and tool descriptions.
118#[derive(Debug, Clone, Deserialize, Serialize)]
119#[serde(default)]
120pub struct ToolFilterConfig {
121    /// Enable dynamic tool schema filtering. Default: `false` (opt-in).
122    pub enabled: bool,
123    /// Number of top-scoring filterable tools to include per turn.
124    /// Set to `0` to include all filterable tools.
125    #[serde(default = "default_tool_filter_top_k")]
126    pub top_k: usize,
127    /// Tool IDs that are never filtered out.
128    #[serde(default = "default_tool_filter_always_on")]
129    pub always_on: Vec<String>,
130    /// MCP tools with fewer description words than this are auto-included.
131    #[serde(default = "default_tool_filter_min_description_words")]
132    pub min_description_words: usize,
133}
134
135impl Default for ToolFilterConfig {
136    fn default() -> Self {
137        Self {
138            enabled: false,
139            top_k: default_tool_filter_top_k(),
140            always_on: default_tool_filter_always_on(),
141            min_description_words: default_tool_filter_min_description_words(),
142        }
143    }
144}
145
146#[derive(Debug, Deserialize, Serialize)]
147pub struct AgentConfig {
148    pub name: String,
149    #[serde(default = "default_max_tool_iterations")]
150    pub max_tool_iterations: usize,
151    #[serde(default = "default_auto_update_check")]
152    pub auto_update_check: bool,
153    /// Additional instruction files to always load, regardless of provider.
154    #[serde(default)]
155    pub instruction_files: Vec<std::path::PathBuf>,
156    /// When true, automatically detect provider-specific instruction files
157    /// (e.g. `CLAUDE.md` for Claude, `AGENTS.md` for `OpenAI`).
158    #[serde(default = "default_instruction_auto_detect")]
159    pub instruction_auto_detect: bool,
160    /// Maximum retry attempts for transient tool errors (0 to disable).
161    #[serde(default = "default_max_tool_retries")]
162    pub max_tool_retries: usize,
163    /// Number of identical tool+args calls within the recent window to trigger repeat-detection
164    /// abort (0 to disable).
165    #[serde(default = "default_tool_repeat_threshold")]
166    pub tool_repeat_threshold: usize,
167    /// Maximum total wall-clock time (seconds) to spend on retries for a single tool call.
168    #[serde(default = "default_max_retry_duration_secs")]
169    pub max_retry_duration_secs: u64,
170    /// Focus-based active context compression configuration (#1850).
171    #[serde(default)]
172    pub focus: FocusConfig,
173    /// Dynamic tool schema filtering configuration (#2020).
174    #[serde(default)]
175    pub tool_filter: ToolFilterConfig,
176}
177
178#[derive(Debug, Clone, Deserialize, Serialize)]
179#[serde(default)]
180pub struct SubAgentConfig {
181    pub enabled: bool,
182    /// Maximum number of sub-agents that can run concurrently.
183    #[serde(default = "default_max_concurrent")]
184    pub max_concurrent: usize,
185    pub extra_dirs: Vec<PathBuf>,
186    /// User-level agents directory.
187    #[serde(default)]
188    pub user_agents_dir: Option<PathBuf>,
189    /// Default permission mode applied to sub-agents that do not specify one.
190    pub default_permission_mode: Option<PermissionMode>,
191    /// Global denylist applied to all sub-agents in addition to per-agent `tools.except`.
192    #[serde(default)]
193    pub default_disallowed_tools: Vec<String>,
194    /// Allow sub-agents to use `bypass_permissions` mode.
195    #[serde(default)]
196    pub allow_bypass_permissions: bool,
197    /// Default memory scope applied to sub-agents that do not set `memory` in their definition.
198    #[serde(default)]
199    pub default_memory_scope: Option<MemoryScope>,
200    /// Lifecycle hooks executed when any sub-agent starts or stops.
201    #[serde(default)]
202    pub hooks: SubAgentLifecycleHooks,
203    /// Directory where transcript JSONL files and meta sidecars are stored.
204    #[serde(default)]
205    pub transcript_dir: Option<PathBuf>,
206    /// Enable writing JSONL transcripts for sub-agent sessions.
207    #[serde(default = "default_transcript_enabled")]
208    pub transcript_enabled: bool,
209    /// Maximum number of `.jsonl` transcript files to keep.
210    #[serde(default = "default_transcript_max_files")]
211    pub transcript_max_files: usize,
212}
213
214impl Default for SubAgentConfig {
215    fn default() -> Self {
216        Self {
217            enabled: false,
218            max_concurrent: default_max_concurrent(),
219            extra_dirs: Vec::new(),
220            user_agents_dir: None,
221            default_permission_mode: None,
222            default_disallowed_tools: Vec::new(),
223            allow_bypass_permissions: false,
224            default_memory_scope: None,
225            hooks: SubAgentLifecycleHooks::default(),
226            transcript_dir: None,
227            transcript_enabled: default_transcript_enabled(),
228            transcript_max_files: default_transcript_max_files(),
229        }
230    }
231}
232
233/// Config-level lifecycle hooks fired when any sub-agent starts or stops.
234#[derive(Debug, Clone, Default, Deserialize, Serialize)]
235#[serde(default)]
236pub struct SubAgentLifecycleHooks {
237    /// Hooks run after a sub-agent is spawned (fire-and-forget).
238    pub start: Vec<HookDef>,
239    /// Hooks run after a sub-agent finishes or is cancelled (fire-and-forget).
240    pub stop: Vec<HookDef>,
241}