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#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(rename_all = "snake_case")]
50pub enum ToolPolicy {
51 AllowList(Vec<String>),
52 DenyList(Vec<String>),
53 InheritAll,
54}
55
56// ── SkillFilter ────────────────────────────────────────────────────────────
57
58#[derive(Debug, Clone, Default, Serialize, Deserialize)]
59pub struct SkillFilter {
60 pub include: Vec<String>,
61 pub exclude: Vec<String>,
62}
63
64impl SkillFilter {
65 #[must_use]
66 pub fn is_empty(&self) -> bool {
67 self.include.is_empty() && self.exclude.is_empty()
68 }
69}
70
71// ── HookDef / HookType / HookMatcher / SubagentHooks ──────────────────────
72
73/// The type of hook — currently only shell command hooks are supported.
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
75#[serde(rename_all = "snake_case")]
76pub enum HookType {
77 Command,
78}
79
80fn default_hook_timeout() -> u64 {
81 30
82}
83
84/// A single hook definition.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct HookDef {
87 #[serde(rename = "type")]
88 pub hook_type: HookType,
89 pub command: String,
90 #[serde(default = "default_hook_timeout")]
91 pub timeout_secs: u64,
92 /// When `true`, a non-zero exit code or timeout causes the calling operation to fail.
93 /// When `false` (default), errors are logged but execution continues.
94 #[serde(default)]
95 pub fail_closed: bool,
96}
97
98/// Tool-name matcher with associated hooks.
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct HookMatcher {
101 pub matcher: String,
102 pub hooks: Vec<HookDef>,
103}
104
105/// Per-agent frontmatter hook collections (`PreToolUse` / `PostToolUse`).
106#[derive(Debug, Clone, Default, Serialize, Deserialize)]
107#[serde(rename_all = "PascalCase")]
108pub struct SubagentHooks {
109 #[serde(default)]
110 pub pre_tool_use: Vec<HookMatcher>,
111 #[serde(default)]
112 pub post_tool_use: Vec<HookMatcher>,
113}
114
115impl SubagentHooks {
116 #[must_use]
117 pub fn is_empty(&self) -> bool {
118 self.pre_tool_use.is_empty() && self.post_tool_use.is_empty()
119 }
120}