1use acp_thread::{SUBAGENT_SESSION_INFO_META_KEY, SubagentSessionInfo};
2use agent_client_protocol as acp;
3use anyhow::Result;
4use gpui::{App, SharedString, Task};
5use language_model::LanguageModelToolResultContent;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use std::rc::Rc;
9use std::sync::Arc;
10
11use crate::{AgentTool, ThreadEnvironment, ToolCallEventStream, ToolInput};
12
13/// Spawn a sub-agent for a well-scoped task.
14///
15/// ### Designing delegated subtasks
16/// - An agent does not see your conversation history. Include all relevant context (file paths, requirements, constraints) in the message.
17/// - Subtasks must be concrete, well-defined, and self-contained.
18/// - Delegated subtasks must materially advance the main task.
19/// - Do not duplicate work between your work and delegated subtasks.
20/// - Do not use this tool for tasks you could accomplish directly with one or two tool calls.
21/// - When you delegate work, focus on coordinating and synthesizing results instead of duplicating the same work yourself.
22/// - Avoid issuing multiple delegate calls for the same unresolved subproblem unless the new delegated task is genuinely different and necessary.
23/// - Narrow the delegated ask to the concrete output you need next.
24/// - For code-edit subtasks, decompose work so each delegated task has a disjoint write set.
25/// - When sending a follow-up using an existing agent session_id, the agent already has the context from the previous turn. Send only a short, direct message. Do NOT repeat the original task or context.
26///
27/// ### Parallel delegation patterns
28/// - Run multiple independent information-seeking subtasks in parallel when you have distinct questions that can be answered independently.
29/// - Split implementation into disjoint codebase slices and spawn multiple agents for them in parallel when the write scopes do not overlap.
30/// - When a plan has multiple independent steps, prefer delegating those steps in parallel rather than serializing them unnecessarily.
31/// - Reuse the returned session_id when you want to follow up on the same delegated subproblem instead of creating a duplicate session.
32///
33/// ### Output
34/// - You will receive only the agent's final message as output.
35/// - Successful calls return a session_id that you can use for follow-up messages.
36/// - Error results may also include a session_id if a session was already created.
37#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
38#[serde(rename_all = "snake_case")]
39pub struct SpawnAgentToolInput {
40 /// Short label displayed in the UI while the agent runs (e.g., "Researching alternatives")
41 pub label: String,
42 /// The prompt for the agent. For new sessions, include full context needed for the task. For follow-ups (with session_id), you can rely on the agent already having the previous message.
43 pub message: String,
44 /// Session ID of an existing agent session to continue instead of creating a new one.
45 #[serde(default)]
46 pub session_id: Option<acp::SessionId>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(untagged)]
51#[serde(rename_all = "snake_case")]
52pub enum SpawnAgentToolOutput {
53 Success {
54 session_id: acp::SessionId,
55 output: String,
56 session_info: SubagentSessionInfo,
57 },
58 Error {
59 #[serde(skip_serializing_if = "Option::is_none")]
60 #[serde(default)]
61 session_id: Option<acp::SessionId>,
62 error: String,
63 session_info: Option<SubagentSessionInfo>,
64 },
65}
66
67impl From<SpawnAgentToolOutput> for LanguageModelToolResultContent {
68 fn from(output: SpawnAgentToolOutput) -> Self {
69 match output {
70 SpawnAgentToolOutput::Success {
71 session_id,
72 output,
73 session_info: _, // Don't show this to the model
74 } => serde_json::to_string(
75 &serde_json::json!({ "session_id": session_id, "output": output }),
76 )
77 .unwrap_or_else(|e| format!("Failed to serialize spawn_agent output: {e}"))
78 .into(),
79 SpawnAgentToolOutput::Error {
80 session_id,
81 error,
82 session_info: _, // Don't show this to the model
83 } => serde_json::to_string(
84 &serde_json::json!({ "session_id": session_id, "error": error }),
85 )
86 .unwrap_or_else(|e| format!("Failed to serialize spawn_agent output: {e}"))
87 .into(),
88 }
89 }
90}
91
92/// Tool that spawns an agent thread to work on a task.
93pub struct SpawnAgentTool {
94 environment: Rc<dyn ThreadEnvironment>,
95}
96
97impl SpawnAgentTool {
98 pub fn new(environment: Rc<dyn ThreadEnvironment>) -> Self {
99 Self { environment }
100 }
101}
102
103impl AgentTool for SpawnAgentTool {
104 type Input = SpawnAgentToolInput;
105 type Output = SpawnAgentToolOutput;
106
107 const NAME: &'static str = "spawn_agent";
108
109 fn kind() -> acp::ToolKind {
110 acp::ToolKind::Other
111 }
112
113 fn initial_title(
114 &self,
115 input: Result<Self::Input, serde_json::Value>,
116 _cx: &mut App,
117 ) -> SharedString {
118 match input {
119 Ok(i) => i.label.into(),
120 Err(value) => value
121 .get("label")
122 .and_then(|v| v.as_str())
123 .map(|s| SharedString::from(s.to_owned()))
124 .unwrap_or_else(|| "Spawning agent".into()),
125 }
126 }
127
128 fn run(
129 self: Arc<Self>,
130 input: ToolInput<Self::Input>,
131 event_stream: ToolCallEventStream,
132 cx: &mut App,
133 ) -> Task<Result<Self::Output, Self::Output>> {
134 cx.spawn(async move |cx| {
135 let input = input
136 .recv()
137 .await
138 .map_err(|e| SpawnAgentToolOutput::Error {
139 session_id: None,
140 error: format!("Failed to receive tool input: {e}"),
141 session_info: None,
142 })?;
143
144 let (subagent, mut session_info) = cx.update(|cx| {
145 let subagent = if let Some(session_id) = input.session_id {
146 self.environment.resume_subagent(session_id, cx)
147 } else {
148 self.environment.create_subagent(input.label, cx)
149 };
150 let subagent = subagent.map_err(|err| SpawnAgentToolOutput::Error {
151 session_id: None,
152 error: err.to_string(),
153 session_info: None,
154 })?;
155 let session_info = SubagentSessionInfo {
156 session_id: subagent.id(),
157 message_start_index: subagent.num_entries(cx),
158 message_end_index: None,
159 };
160
161 event_stream.subagent_spawned(subagent.id());
162 event_stream.update_fields_with_meta(
163 acp::ToolCallUpdateFields::new(),
164 Some(acp::Meta::from_iter([(
165 SUBAGENT_SESSION_INFO_META_KEY.into(),
166 serde_json::json!(&session_info),
167 )])),
168 );
169
170 Ok((subagent, session_info))
171 })?;
172
173 let send_result = subagent.send(input.message, cx).await;
174
175 let status = if send_result.is_ok() {
176 "completed"
177 } else {
178 "error"
179 };
180 telemetry::event!(
181 "Subagent Completed",
182 subagent_session = session_info.session_id.to_string(),
183 status,
184 );
185
186 session_info.message_end_index =
187 cx.update(|cx| Some(subagent.num_entries(cx).saturating_sub(1)));
188
189 let meta = Some(acp::Meta::from_iter([(
190 SUBAGENT_SESSION_INFO_META_KEY.into(),
191 serde_json::json!(&session_info),
192 )]));
193
194 let (output, result) = match send_result {
195 Ok(output) => (
196 output.clone(),
197 Ok(SpawnAgentToolOutput::Success {
198 session_id: session_info.session_id.clone(),
199 session_info,
200 output,
201 }),
202 ),
203 Err(e) => {
204 let error = e.to_string();
205 (
206 error.clone(),
207 Err(SpawnAgentToolOutput::Error {
208 session_id: Some(session_info.session_id.clone()),
209 error,
210 session_info: Some(session_info),
211 }),
212 )
213 }
214 };
215 event_stream.update_fields_with_meta(
216 acp::ToolCallUpdateFields::new().content(vec![output.into()]),
217 meta,
218 );
219 result
220 })
221 }
222
223 fn replay(
224 &self,
225 _input: Self::Input,
226 output: Self::Output,
227 event_stream: ToolCallEventStream,
228 _cx: &mut App,
229 ) -> Result<()> {
230 let (content, session_info) = match output {
231 SpawnAgentToolOutput::Success {
232 output,
233 session_info,
234 ..
235 } => (output.into(), Some(session_info)),
236 SpawnAgentToolOutput::Error {
237 error,
238 session_info,
239 ..
240 } => (error.into(), session_info),
241 };
242
243 let meta = session_info.map(|session_info| {
244 acp::Meta::from_iter([(
245 SUBAGENT_SESSION_INFO_META_KEY.into(),
246 serde_json::json!(&session_info),
247 )])
248 });
249 event_stream.update_fields_with_meta(
250 acp::ToolCallUpdateFields::new().content(vec![content]),
251 meta,
252 );
253
254 Ok(())
255 }
256}