zeph_config/subagent.rs
1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use serde::{Deserialize, Serialize};
5
6// ── PermissionMode ─────────────────────────────────────────────────────────
7
8/// Controls tool execution and prompt interactivity for a sub-agent.
9///
10/// For sub-agents (non-interactive), `Default`, `AcceptEdits`, `DontAsk`, and
11/// `BypassPermissions` are functionally equivalent — sub-agents never prompt the
12/// user. The meaningful differentiator is `Plan` mode, which suppresses all tool
13/// execution and returns only the plan text.
14#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
15#[serde(rename_all = "snake_case")]
16pub enum PermissionMode {
17 /// Standard behavior — prompt for each action (sub-agents auto-approve).
18 #[default]
19 Default,
20 /// Auto-accept file edits without prompting.
21 AcceptEdits,
22 /// Auto-approve all tool calls without prompting.
23 DontAsk,
24 /// Unrestricted tool access; emits a warning when loaded.
25 BypassPermissions,
26 /// Read-only planning: tools are visible in the catalog but execution is blocked.
27 Plan,
28}
29
30// ── MemoryScope ────────────────────────────────────────────────────────────
31
32/// Persistence scope for sub-agent memory files.
33///
34/// Determines where the agent's `MEMORY.md` and topic files are stored across sessions.
35#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
36#[serde(rename_all = "snake_case")]
37pub enum MemoryScope {
38 /// User-level: `~/.zeph/agent-memory/<name>/`.
39 User,
40 /// Project-level: `.zeph/agent-memory/<name>/`.
41 Project,
42 /// Local-only: `.zeph/agent-memory-local/<name>/`.
43 Local,
44}
45
46// ── ToolPolicy ─────────────────────────────────────────────────────────────
47
48/// Tool access policy for a sub-agent.
49///
50/// Controls which tools the sub-agent may call, independent of the global tool denylist.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53pub enum ToolPolicy {
54 /// Only the listed tool IDs are accessible.
55 AllowList(Vec<String>),
56 /// All tools except those in the list are accessible.
57 DenyList(Vec<String>),
58 /// Inherit the full tool set from the parent agent (no additional filtering).
59 InheritAll,
60}
61
62// ── SkillFilter ────────────────────────────────────────────────────────────
63
64/// Skill allow/deny filter for sub-agent definitions.
65///
66/// Skills named in `include` are the only ones loaded; `exclude` removes
67/// specific skills from the inherited set. When both are empty the sub-agent
68/// inherits all parent skills.
69#[derive(Debug, Clone, Default, Serialize, Deserialize)]
70pub struct SkillFilter {
71 /// Explicit skill names to include (empty = inherit all).
72 pub include: Vec<String>,
73 /// Skill names to remove from the inherited set.
74 pub exclude: Vec<String>,
75}
76
77impl SkillFilter {
78 /// Returns `true` when no filter is applied (all skills are inherited).
79 ///
80 /// # Examples
81 ///
82 /// ```
83 /// use zeph_config::SkillFilter;
84 ///
85 /// assert!(SkillFilter::default().is_empty());
86 /// ```
87 #[must_use]
88 pub fn is_empty(&self) -> bool {
89 self.include.is_empty() && self.exclude.is_empty()
90 }
91}
92
93// ── HookDef / HookType / HookMatcher / SubagentHooks ──────────────────────
94
95/// The type of hook — currently only shell command hooks are supported.
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
97#[serde(rename_all = "snake_case")]
98pub enum HookType {
99 Command,
100}
101
102fn default_hook_timeout() -> u64 {
103 30
104}
105
106/// A single hook definition.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct HookDef {
109 #[serde(rename = "type")]
110 pub hook_type: HookType,
111 pub command: String,
112 #[serde(default = "default_hook_timeout")]
113 pub timeout_secs: u64,
114 /// When `true`, a non-zero exit code or timeout causes the calling operation to fail.
115 /// When `false` (default), errors are logged but execution continues.
116 #[serde(default)]
117 pub fail_closed: bool,
118}
119
120/// Tool-name matcher with associated hooks.
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct HookMatcher {
123 pub matcher: String,
124 pub hooks: Vec<HookDef>,
125}
126
127/// Per-agent frontmatter hook collections (`PreToolUse` / `PostToolUse`).
128#[derive(Debug, Clone, Default, Serialize, Deserialize)]
129#[serde(rename_all = "PascalCase")]
130pub struct SubagentHooks {
131 #[serde(default)]
132 pub pre_tool_use: Vec<HookMatcher>,
133 #[serde(default)]
134 pub post_tool_use: Vec<HookMatcher>,
135}
136
137impl SubagentHooks {
138 /// Returns `true` when no pre- or post-tool-use hooks are configured.
139 #[must_use]
140 pub fn is_empty(&self) -> bool {
141 self.pre_tool_use.is_empty() && self.post_tool_use.is_empty()
142 }
143}