1use acp_thread::SUBAGENT_SESSION_ID_META_KEY;
2use agent_client_protocol as acp;
3use anyhow::Result;
4use gpui::{App, SharedString, Task, WeakEntity};
5use language_model::LanguageModelToolResultContent;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use std::rc::Rc;
9use std::sync::Arc;
10
11use crate::{AgentTool, Thread, ThreadEnvironment, ToolCallEventStream, ToolInput};
12
13/// Spawns an agent to perform a delegated task.
14///
15/// Use this tool when you want to:
16/// - Run multiple tasks in parallel.
17/// - Delegate a self-contained task where you only need the final outcome.
18///
19/// You will receive only the agent's final message as output.
20///
21/// **New session** (no session_id): Creates a new agent that does NOT see your conversation history. Include all relevant context (file paths, requirements, constraints) in the message.
22///
23/// **Follow-up** (with session_id): Sends a follow-up to an existing agent session. The agent already has full context, so send only a short, direct message — do NOT repeat the original task or context. Examples: "Also update the tests", "Fix the compile error in foo.rs", "Retry".
24///
25/// - If spawning multiple agents that might write to the filesystem, provide guidance on how to avoid conflicts (e.g. assign each to different directories).
26#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
27pub struct SpawnAgentToolInput {
28 /// Short label displayed in the UI while the agent runs (e.g., "Researching alternatives")
29 pub label: String,
30 /// 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.
31 pub message: String,
32 /// Session ID of an existing agent session to continue instead of creating a new one.
33 #[serde(default)]
34 pub session_id: Option<acp::SessionId>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
38#[serde(untagged)]
39pub enum SpawnAgentToolOutput {
40 Success {
41 session_id: acp::SessionId,
42 output: String,
43 },
44 Error {
45 #[serde(skip_serializing_if = "Option::is_none")]
46 #[serde(default)]
47 session_id: Option<acp::SessionId>,
48 error: String,
49 },
50}
51
52impl From<SpawnAgentToolOutput> for LanguageModelToolResultContent {
53 fn from(output: SpawnAgentToolOutput) -> Self {
54 serde_json::to_string(&output)
55 .unwrap_or_else(|e| format!("Failed to serialize spawn_agent output: {e}"))
56 .into()
57 }
58}
59
60/// Tool that spawns an agent thread to work on a task.
61pub struct SpawnAgentTool {
62 parent_thread: WeakEntity<Thread>,
63 environment: Rc<dyn ThreadEnvironment>,
64}
65
66impl SpawnAgentTool {
67 pub fn new(parent_thread: WeakEntity<Thread>, environment: Rc<dyn ThreadEnvironment>) -> Self {
68 Self {
69 parent_thread,
70 environment,
71 }
72 }
73}
74
75impl AgentTool for SpawnAgentTool {
76 type Input = SpawnAgentToolInput;
77 type Output = SpawnAgentToolOutput;
78
79 const NAME: &'static str = "spawn_agent";
80
81 fn kind() -> acp::ToolKind {
82 acp::ToolKind::Other
83 }
84
85 fn initial_title(
86 &self,
87 input: Result<Self::Input, serde_json::Value>,
88 _cx: &mut App,
89 ) -> SharedString {
90 input
91 .map(|i| i.label.into())
92 .unwrap_or_else(|_| "Spawning agent".into())
93 }
94
95 fn run(
96 self: Arc<Self>,
97 input: ToolInput<Self::Input>,
98 event_stream: ToolCallEventStream,
99 cx: &mut App,
100 ) -> Task<Result<Self::Output, Self::Output>> {
101 cx.spawn(async move |cx| {
102 let input = input
103 .recv()
104 .await
105 .map_err(|e| SpawnAgentToolOutput::Error {
106 session_id: None,
107 error: format!("Failed to receive tool input: {e}"),
108 })?;
109
110 let (subagent, subagent_session_id) = cx.update(|cx| {
111 let Some(parent_thread_entity) = self.parent_thread.upgrade() else {
112 return Err(SpawnAgentToolOutput::Error {
113 session_id: None,
114 error: "Parent thread no longer exists".to_string(),
115 });
116 };
117
118 let subagent = if let Some(session_id) = input.session_id {
119 self.environment.resume_subagent(
120 parent_thread_entity,
121 session_id,
122 input.message,
123 cx,
124 )
125 } else {
126 self.environment.create_subagent(
127 parent_thread_entity,
128 input.label,
129 input.message,
130 cx,
131 )
132 };
133 let subagent = subagent.map_err(|err| SpawnAgentToolOutput::Error {
134 session_id: None,
135 error: err.to_string(),
136 })?;
137 let subagent_session_id = subagent.id();
138
139 event_stream.subagent_spawned(subagent_session_id.clone());
140 let meta = acp::Meta::from_iter([(
141 SUBAGENT_SESSION_ID_META_KEY.into(),
142 subagent_session_id.to_string().into(),
143 )]);
144 event_stream.update_fields_with_meta(acp::ToolCallUpdateFields::new(), Some(meta));
145
146 Ok((subagent, subagent_session_id))
147 })?;
148
149 match subagent.wait_for_output(cx).await {
150 Ok(output) => {
151 event_stream.update_fields(
152 acp::ToolCallUpdateFields::new().content(vec![output.clone().into()]),
153 );
154 Ok(SpawnAgentToolOutput::Success {
155 session_id: subagent_session_id,
156 output,
157 })
158 }
159 Err(e) => {
160 let error = e.to_string();
161 event_stream.update_fields(
162 acp::ToolCallUpdateFields::new().content(vec![error.clone().into()]),
163 );
164 Err(SpawnAgentToolOutput::Error {
165 session_id: Some(subagent_session_id),
166 error,
167 })
168 }
169 }
170 })
171 }
172
173 fn replay(
174 &self,
175 _input: Self::Input,
176 output: Self::Output,
177 event_stream: ToolCallEventStream,
178 _cx: &mut App,
179 ) -> Result<()> {
180 let session_id = match &output {
181 SpawnAgentToolOutput::Success { session_id, .. } => Some(session_id),
182 SpawnAgentToolOutput::Error { session_id, .. } => session_id.as_ref(),
183 };
184
185 if let Some(session_id) = session_id {
186 event_stream.subagent_spawned(session_id.clone());
187 let meta = acp::Meta::from_iter([(
188 SUBAGENT_SESSION_ID_META_KEY.into(),
189 session_id.to_string().into(),
190 )]);
191 event_stream.update_fields_with_meta(acp::ToolCallUpdateFields::new(), Some(meta));
192 }
193
194 let content = match &output {
195 SpawnAgentToolOutput::Success { output, .. } => output.into(),
196 SpawnAgentToolOutput::Error { error, .. } => error.into(),
197 };
198 event_stream.update_fields(acp::ToolCallUpdateFields::new().content(vec![content]));
199
200 Ok(())
201 }
202}