agent_works/multi_agent/config.rs
1//! Multi-agent configuration.
2//!
3//! Controls whether multi-agent capabilities are enabled and sets resource limits.
4//! Multi-agent is enabled by default — users who don't need it can disable via
5//! `.without_multi_agent()` or feature gate.
6
7use agent_base::ReasoningEffort;
8
9/// Permission mode for spawned child agents.
10///
11/// Controls whether sub-agents run with full tool access or are restricted by
12/// the parent's [`ToolPolicy`](agent_base::ToolPolicy). The mode is resolved at
13/// the runtime layer on every spawn, so it cannot be bypassed by the LLM.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15pub enum ChildPermissionMode {
16 /// Child agents have full permission — every tool auto-approves.
17 ///
18 /// This matches the historical behaviour (children carried no tool policy),
19 /// so it is the default to avoid surprising existing users.
20 #[default]
21 Full,
22 /// Child agents have no permission — the parent's "dangerous tools" are
23 /// denied when the parent has a [`ToolPolicy`](agent_base::ToolPolicy); if
24 /// the parent has none, every tool is denied via [`DenyAllToolPolicy`](agent_base::DenyAllToolPolicy).
25 None,
26 /// The parent agent decides per-spawn via `SpawnAgentArgs.full_permission`.
27 PerSpawn,
28}
29
30/// Configuration for the multi-agent subsystem.
31///
32/// # Default
33///
34/// Multi-agent is **enabled** by default with limits of 8 sub-agents and depth 1.
35/// Disable it with:
36///
37/// ```rust,ignore
38/// use agent_works::AgentBuilder;
39///
40/// let agent = AgentBuilder::new(client)
41/// .without_multi_agent()
42/// .build()?;
43/// ```
44///
45/// # Limits
46///
47/// Limits only take effect when `enabled` is `true`.
48#[derive(Clone, Debug)]
49pub struct MultiAgentConfig {
50 /// Enable multi-agent capabilities.
51 ///
52 /// When `true` (default): all 6 multi-agent tools are registered, and the main
53 /// agent's system prompt is augmented with usage guidance.
54 ///
55 /// When `false`: no multi-agent tools are registered, and the system
56 /// prompt does not mention multi-agent capabilities.
57 pub enabled: bool,
58
59 /// Maximum number of concurrent sub-agents.
60 ///
61 /// Default: 8. Only enforced when `enabled` is `true`.
62 pub max_sub_agents: usize,
63
64 /// Maximum nesting depth for sub-agents.
65 ///
66 /// Default: 1 (only direct children of root can spawn). A value of 0 means
67 /// no sub-agents can be spawned at all. Only enforced when `enabled` is `true`.
68 pub max_agent_depth: i32,
69
70 /// Permission mode for spawned child agents.
71 ///
72 /// Default: [`ChildPermissionMode::Full`].
73 pub child_permission_mode: ChildPermissionMode,
74
75 /// Business tool names to EXCLUDE from child agents.
76 ///
77 /// By default children inherit every business tool registered on the parent.
78 /// Some tools are inherently root-level (e.g. `decompose`/`merge` orchestration):
79 /// a leaf agent that lacks `spawn_agent` should not be handed a "split this into
80 /// parallel sub-agents" tool — it would plan work it cannot execute. Mark such
81 /// tools here so `build_child_runtime` skips them, and children do the work
82 /// inline instead of trying to orchestrate further.
83 pub child_excluded_tools: Vec<String>,
84
85 /// Optional reasoning-effort override applied to every child agent.
86 ///
87 /// Child agents do narrow, focused slices, so they rarely need the parent's
88 /// full reasoning depth — and on reasoning-heavy models (deepseek-v4-pro) an
89 /// unbounded child can "think" itself into a runaway (30KB+ of reasoning with
90 /// no tool call or answer). Setting e.g. [`ReasoningEffort::Low`] caps that
91 /// cost. `None` (default) leaves the child on the framework default.
92 pub child_reasoning_effort: Option<ReasoningEffort>,
93
94 /// Whether child agents are *recommended* to be read-only.
95 ///
96 /// The framework is domain-agnostic: it cannot classify which business tools
97 /// mutate state and which merely read, so this is a **prompt-level suggestion
98 /// only** — it does not remove or gate any tool. When `true` (default),
99 /// [`MultiAgentRuntime::build_child_runtime`] appends a read-only nudge to
100 /// every child's system prompt, telling it to investigate and report rather
101 /// than mutate.
102 ///
103 /// For a *hard* guarantee, exclude mutating tools at the business layer via
104 /// [`MultiAgentConfig::child_excluded_tools`]; the framework only *suggests*
105 /// read-only, it cannot enforce it. Set this to `false` when children are
106 /// meant to write (the codex-style symmetric model).
107 pub child_read_only: bool,
108}
109
110impl Default for MultiAgentConfig {
111 fn default() -> Self {
112 Self {
113 enabled: true,
114 max_sub_agents: 8,
115 max_agent_depth: 1,
116 child_permission_mode: ChildPermissionMode::Full,
117 child_excluded_tools: Vec::new(),
118 child_reasoning_effort: None,
119 child_read_only: true,
120 }
121 }
122}
123
124impl MultiAgentConfig {
125 /// Create a config with multi-agent enabled and default limits.
126 pub fn enabled() -> Self {
127 Self {
128 enabled: true,
129 ..Default::default()
130 }
131 }
132
133 /// Create a config with multi-agent enabled and custom limits.
134 pub fn with_limits(max_sub_agents: usize, max_agent_depth: i32) -> Self {
135 Self {
136 enabled: true,
137 max_sub_agents,
138 max_agent_depth,
139 child_permission_mode: ChildPermissionMode::Full,
140 child_excluded_tools: Vec::new(),
141 child_reasoning_effort: None,
142 child_read_only: true,
143 }
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn default_is_enabled() {
153 let config = MultiAgentConfig::default();
154 assert!(config.enabled);
155 assert_eq!(config.max_sub_agents, 8);
156 assert_eq!(config.max_agent_depth, 1);
157 }
158
159 #[test]
160 fn default_children_are_read_only() {
161 assert!(MultiAgentConfig::default().child_read_only);
162 assert!(MultiAgentConfig::enabled().child_read_only);
163 assert!(MultiAgentConfig::with_limits(4, 2).child_read_only);
164 }
165
166 #[test]
167 fn default_permission_mode_is_full() {
168 assert_eq!(
169 MultiAgentConfig::default().child_permission_mode,
170 ChildPermissionMode::Full
171 );
172 assert_eq!(
173 MultiAgentConfig::enabled().child_permission_mode,
174 ChildPermissionMode::Full
175 );
176 assert_eq!(
177 MultiAgentConfig::with_limits(4, 2).child_permission_mode,
178 ChildPermissionMode::Full
179 );
180 }
181
182 #[test]
183 fn enabled_shortcut() {
184 let config = MultiAgentConfig::enabled();
185 assert!(config.enabled);
186 assert_eq!(config.max_sub_agents, 8);
187 assert_eq!(config.max_agent_depth, 1);
188 }
189
190 #[test]
191 fn with_limits() {
192 let config = MultiAgentConfig::with_limits(4, 2);
193 assert!(config.enabled);
194 assert_eq!(config.max_sub_agents, 4);
195 assert_eq!(config.max_agent_depth, 2);
196 }
197}