agent: Change tool name to spawn_agent (#49741)

Ben Brandt created

After looking around a bit, I felt like subagent wasn't the right way to
introduce this tool to the model. And with some back and forth
verification + market research, we both agreed spawn_agent was a better
name for what this tool is doing.

It still calls the right tool if you ask for a subagent, but especially
if we move these to be running in the background, spawn will be a better
name anyway.

Release Notes:

- N/A

Change summary

assets/settings/default.json                                     |  4 
crates/acp_thread/src/acp_thread.rs                              |  2 
crates/agent/src/tests/mod.rs                                    | 20 
crates/agent/src/thread.rs                                       |  4 
crates/agent/src/tools.rs                                        |  6 
crates/agent/src/tools/spawn_agent_tool.rs                       | 42 +-
crates/agent_ui/src/acp/thread_view/active_thread.rs             |  2 
crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs |  4 
crates/settings_ui/src/pages/tool_permissions_setup.rs           |  2 
9 files changed, 43 insertions(+), 43 deletions(-)

Detailed changes

assets/settings/default.json πŸ”—

@@ -1040,7 +1040,7 @@
           "save_file": true,
           "open": true,
           "grep": true,
-          "subagent": true,
+          "spawn_agent": true,
           "terminal": true,
           "thinking": true,
           "web_search": true,
@@ -1060,7 +1060,7 @@
           "read_file": true,
           "open": true,
           "grep": true,
-          "subagent": true,
+          "spawn_agent": true,
           "thinking": true,
           "web_search": true,
         },

crates/acp_thread/src/acp_thread.rs πŸ”—

@@ -400,7 +400,7 @@ impl ToolCall {
     }
 
     pub fn is_subagent(&self) -> bool {
-        self.tool_name.as_ref().is_some_and(|s| s == "subagent")
+        self.tool_name.as_ref().is_some_and(|s| s == "spawn_agent")
             || self.subagent_session_id.is_some()
     }
 

crates/agent/src/tests/mod.rs πŸ”—

@@ -4230,7 +4230,7 @@ async fn test_subagent_tool_call_end_to_end(cx: &mut TestAppContext) {
     let send = acp_thread.update(cx, |thread, cx| thread.send_raw("Prompt", cx));
     cx.run_until_parked();
     model.send_last_completion_stream_text_chunk("spawning subagent");
-    let subagent_tool_input = SubagentToolInput {
+    let subagent_tool_input = SpawnAgentToolInput {
         label: "label".to_string(),
         task: "subagent task prompt".to_string(),
         session_id: None,
@@ -4238,7 +4238,7 @@ async fn test_subagent_tool_call_end_to_end(cx: &mut TestAppContext) {
     };
     let subagent_tool_use = LanguageModelToolUse {
         id: "subagent_1".into(),
-        name: SubagentTool::NAME.into(),
+        name: SpawnAgentTool::NAME.into(),
         raw_input: serde_json::to_string(&subagent_tool_input).unwrap(),
         input: serde_json::to_value(&subagent_tool_input).unwrap(),
         is_input_complete: true,
@@ -4381,7 +4381,7 @@ async fn test_subagent_tool_call_cancellation_during_task_prompt(cx: &mut TestAp
     let send = acp_thread.update(cx, |thread, cx| thread.send_raw("Prompt", cx));
     cx.run_until_parked();
     model.send_last_completion_stream_text_chunk("spawning subagent");
-    let subagent_tool_input = SubagentToolInput {
+    let subagent_tool_input = SpawnAgentToolInput {
         label: "label".to_string(),
         task: "subagent task prompt".to_string(),
         session_id: None,
@@ -4389,7 +4389,7 @@ async fn test_subagent_tool_call_cancellation_during_task_prompt(cx: &mut TestAp
     };
     let subagent_tool_use = LanguageModelToolUse {
         id: "subagent_1".into(),
-        name: SubagentTool::NAME.into(),
+        name: SpawnAgentTool::NAME.into(),
         raw_input: serde_json::to_string(&subagent_tool_input).unwrap(),
         input: serde_json::to_value(&subagent_tool_input).unwrap(),
         is_input_complete: true,
@@ -4519,7 +4519,7 @@ async fn test_subagent_tool_resume_session(cx: &mut TestAppContext) {
     let send = acp_thread.update(cx, |thread, cx| thread.send_raw("First prompt", cx));
     cx.run_until_parked();
     model.send_last_completion_stream_text_chunk("spawning subagent");
-    let subagent_tool_input = SubagentToolInput {
+    let subagent_tool_input = SpawnAgentToolInput {
         label: "initial task".to_string(),
         task: "do the first task".to_string(),
         session_id: None,
@@ -4527,7 +4527,7 @@ async fn test_subagent_tool_resume_session(cx: &mut TestAppContext) {
     };
     let subagent_tool_use = LanguageModelToolUse {
         id: "subagent_1".into(),
-        name: SubagentTool::NAME.into(),
+        name: SpawnAgentTool::NAME.into(),
         raw_input: serde_json::to_string(&subagent_tool_input).unwrap(),
         input: serde_json::to_value(&subagent_tool_input).unwrap(),
         is_input_complete: true,
@@ -4581,7 +4581,7 @@ async fn test_subagent_tool_resume_session(cx: &mut TestAppContext) {
     let send2 = acp_thread.update(cx, |thread, cx| thread.send_raw("Follow up", cx));
     cx.run_until_parked();
     model.send_last_completion_stream_text_chunk("resuming subagent");
-    let resume_tool_input = SubagentToolInput {
+    let resume_tool_input = SpawnAgentToolInput {
         label: "follow-up task".to_string(),
         task: "do the follow-up task".to_string(),
         session_id: Some(subagent_session_id.clone()),
@@ -4589,7 +4589,7 @@ async fn test_subagent_tool_resume_session(cx: &mut TestAppContext) {
     };
     let resume_tool_use = LanguageModelToolUse {
         id: "subagent_2".into(),
-        name: SubagentTool::NAME.into(),
+        name: SpawnAgentTool::NAME.into(),
         raw_input: serde_json::to_string(&resume_tool_input).unwrap(),
         input: serde_json::to_value(&resume_tool_input).unwrap(),
         is_input_complete: true,
@@ -4687,7 +4687,7 @@ async fn test_subagent_tool_is_present_when_feature_flag_enabled(cx: &mut TestAp
 
     thread.read_with(cx, |thread, _| {
         assert!(
-            thread.has_registered_tool(SubagentTool::NAME),
+            thread.has_registered_tool(SpawnAgentTool::NAME),
             "subagent tool should be present when feature flag is enabled"
         );
     });
@@ -4780,7 +4780,7 @@ async fn test_max_subagent_depth_prevents_tool_registration(cx: &mut TestAppCont
     deep_subagent_thread.read_with(cx, |thread, _| {
         assert_eq!(thread.depth(), MAX_SUBAGENT_DEPTH);
         assert!(
-            !thread.has_registered_tool(SubagentTool::NAME),
+            !thread.has_registered_tool(SpawnAgentTool::NAME),
             "subagent tool should not be present at max depth"
         );
     });

crates/agent/src/thread.rs πŸ”—

@@ -2,7 +2,7 @@ use crate::{
     AgentGitWorktreeInfo, ContextServerRegistry, CopyPathTool, CreateDirectoryTool,
     DbLanguageModel, DbThread, DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool,
     FindPathTool, GrepTool, ListDirectoryTool, MovePathTool, NowTool, OpenTool, ProjectSnapshot,
-    ReadFileTool, RestoreFileFromDiskTool, SaveFileTool, StreamingEditFileTool, SubagentTool,
+    ReadFileTool, RestoreFileFromDiskTool, SaveFileTool, SpawnAgentTool, StreamingEditFileTool,
     SystemPromptTemplate, Template, Templates, TerminalTool, ToolPermissionDecision, WebSearchTool,
     decide_permission_from_settings,
 };
@@ -1380,7 +1380,7 @@ impl Thread {
         self.add_tool(WebSearchTool);
 
         if cx.has_flag::<SubagentsFeatureFlag>() && self.depth() < MAX_SUBAGENT_DEPTH {
-            self.add_tool(SubagentTool::new(cx.weak_entity(), environment));
+            self.add_tool(SpawnAgentTool::new(cx.weak_entity(), environment));
         }
     }
 

crates/agent/src/tools.rs πŸ”—

@@ -14,8 +14,8 @@ mod open_tool;
 mod read_file_tool;
 mod restore_file_from_disk_tool;
 mod save_file_tool;
+mod spawn_agent_tool;
 mod streaming_edit_file_tool;
-mod subagent_tool;
 mod terminal_tool;
 mod tool_permissions;
 mod web_search_tool;
@@ -39,8 +39,8 @@ pub use open_tool::*;
 pub use read_file_tool::*;
 pub use restore_file_from_disk_tool::*;
 pub use save_file_tool::*;
+pub use spawn_agent_tool::*;
 pub use streaming_edit_file_tool::*;
-pub use subagent_tool::*;
 pub use terminal_tool::*;
 pub use tool_permissions::*;
 pub use web_search_tool::*;
@@ -128,7 +128,7 @@ tools! {
     ReadFileTool,
     RestoreFileFromDiskTool,
     SaveFileTool,
-    SubagentTool,
+    SpawnAgentTool,
     TerminalTool,
     WebSearchTool,
 }

crates/agent/src/tools/subagent_tool.rs β†’ crates/agent/src/tools/spawn_agent_tool.rs πŸ”—

@@ -23,18 +23,18 @@ use crate::{AgentTool, Thread, ThreadEnvironment, ToolCallEventStream};
 ///
 /// You will receive only the agent's final message as output.
 ///
-/// If a response (success or error) includes a session_id, you can send a follow-up message to that session by passing the session_id back. This is useful for multi-turn conversations with a subagent, asking clarifying questions about its output, or retrying after timeouts or transient failures.
+/// If a response (success or error) includes a session_id, you can send a follow-up message to that session by passing the session_id back. This is useful for multi-turn conversations with an agent, asking clarifying questions about its output, or retrying after timeouts or transient failures.
 ///
 /// Note:
 /// - Agents cannot use tools you don't have access to.
 /// - If spawning multiple agents that might write to the filesystem, provide guidance on how to avoid conflicts (e.g. assign each to different directories).
 #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
-pub struct SubagentToolInput {
+pub struct SpawnAgentToolInput {
     /// Short label displayed in the UI while the agent runs (e.g., "Researching alternatives")
     pub label: String,
     /// Describe the task for the agent to perform. Be specific about what you want accomplished. Include all necessary context (file paths, requirements, constraints) since the agent cannot see your conversation.
     pub task: String,
-    /// Optional session ID of an existing subagent to continue a conversation with. When provided, the task is sent as a follow-up message to that session instead of creating a new one. Use this to ask clarifying questions, request changes based on previous output, or retry after errors.
+    /// Optional session ID of an existing agent session to continue a conversation with. When provided, the task is sent as a follow-up message to that session instead of creating a new one. Use this to ask clarifying questions, request changes based on previous output, or retry after errors.
     #[serde(default)]
     pub session_id: Option<acp::SessionId>,
     /// Optional maximum runtime in seconds. The purpose of this timeout is to prevent the agent from getting stuck in infinite loops, NOT to estimate task duration. Be generous if setting. If not set, the agent runs until it completes or is cancelled.
@@ -44,7 +44,7 @@ pub struct SubagentToolInput {
 
 #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
 #[serde(untagged)]
-pub enum SubagentToolOutput {
+pub enum SpawnAgentToolOutput {
     Success {
         session_id: acp::SessionId,
         output: String,
@@ -57,21 +57,21 @@ pub enum SubagentToolOutput {
     },
 }
 
-impl From<SubagentToolOutput> for LanguageModelToolResultContent {
-    fn from(output: SubagentToolOutput) -> Self {
+impl From<SpawnAgentToolOutput> for LanguageModelToolResultContent {
+    fn from(output: SpawnAgentToolOutput) -> Self {
         serde_json::to_string(&output)
-            .unwrap_or_else(|e| format!("Failed to serialize subagent output: {e}"))
+            .unwrap_or_else(|e| format!("Failed to serialize spawn_agent output: {e}"))
             .into()
     }
 }
 
-/// Tool that spawns a subagent thread to work on a task.
-pub struct SubagentTool {
+/// Tool that spawns an agent thread to work on a task.
+pub struct SpawnAgentTool {
     parent_thread: WeakEntity<Thread>,
     environment: Rc<dyn ThreadEnvironment>,
 }
 
-impl SubagentTool {
+impl SpawnAgentTool {
     pub fn new(parent_thread: WeakEntity<Thread>, environment: Rc<dyn ThreadEnvironment>) -> Self {
         Self {
             parent_thread,
@@ -80,11 +80,11 @@ impl SubagentTool {
     }
 }
 
-impl AgentTool for SubagentTool {
-    type Input = SubagentToolInput;
-    type Output = SubagentToolOutput;
+impl AgentTool for SpawnAgentTool {
+    type Input = SpawnAgentToolInput;
+    type Output = SpawnAgentToolOutput;
 
-    const NAME: &'static str = "subagent";
+    const NAME: &'static str = "spawn_agent";
 
     fn kind() -> acp::ToolKind {
         acp::ToolKind::Other
@@ -97,7 +97,7 @@ impl AgentTool for SubagentTool {
     ) -> SharedString {
         input
             .map(|i| i.label.into())
-            .unwrap_or_else(|_| "Subagent".into())
+            .unwrap_or_else(|_| "Spawning agent".into())
     }
 
     fn run(
@@ -107,7 +107,7 @@ impl AgentTool for SubagentTool {
         cx: &mut App,
     ) -> Task<Result<Self::Output, Self::Output>> {
         let Some(parent_thread_entity) = self.parent_thread.upgrade() else {
-            return Task::ready(Err(SubagentToolOutput::Error {
+            return Task::ready(Err(SpawnAgentToolOutput::Error {
                 session_id: None,
                 error: "Parent thread no longer exists".to_string(),
             }));
@@ -133,7 +133,7 @@ impl AgentTool for SubagentTool {
         let subagent = match subagent {
             Ok(subagent) => subagent,
             Err(err) => {
-                return Task::ready(Err(SubagentToolOutput::Error {
+                return Task::ready(Err(SpawnAgentToolOutput::Error {
                     session_id: None,
                     error: err.to_string(),
                 }));
@@ -153,11 +153,11 @@ impl AgentTool for SubagentTool {
                 subagent
                     .wait_for_output(cx)
                     .await
-                    .map_err(|e| SubagentToolOutput::Error {
+                    .map_err(|e| SpawnAgentToolOutput::Error {
                         session_id: Some(subagent_session_id.clone()),
                         error: e.to_string(),
                     })?;
-            Ok(SubagentToolOutput::Success {
+            Ok(SpawnAgentToolOutput::Success {
                 session_id: subagent_session_id,
                 output,
             })
@@ -172,8 +172,8 @@ impl AgentTool for SubagentTool {
         _cx: &mut App,
     ) -> Result<()> {
         let session_id = match &output {
-            SubagentToolOutput::Success { session_id, .. } => Some(session_id),
-            SubagentToolOutput::Error { session_id, .. } => session_id.as_ref(),
+            SpawnAgentToolOutput::Success { session_id, .. } => Some(session_id),
+            SpawnAgentToolOutput::Error { session_id, .. } => session_id.as_ref(),
         };
 
         if let Some(session_id) = session_id {

crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs πŸ”—

@@ -2,7 +2,7 @@ mod profile_modal_header;
 
 use std::sync::Arc;
 
-use agent::{AgentTool, ContextServerRegistry, SubagentTool};
+use agent::{AgentTool, ContextServerRegistry, SpawnAgentTool};
 use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profiles};
 use editor::Editor;
 use feature_flags::{FeatureFlagAppExt as _, SubagentsFeatureFlag};
@@ -364,7 +364,7 @@ impl ManageProfilesModal {
                     agent::tool_supports_provider(name, provider)
                 });
                 let enabled_by_feature_flag =
-                    *name != SubagentTool::NAME || cx.has_flag::<SubagentsFeatureFlag>();
+                    *name != SpawnAgentTool::NAME || cx.has_flag::<SubagentsFeatureFlag>();
 
                 supported_by_provider && enabled_by_feature_flag
             })

crates/settings_ui/src/pages/tool_permissions_setup.rs πŸ”—

@@ -1412,7 +1412,7 @@ mod tests {
             "streaming_edit_file",
             // Subagent permission checks happen at the level of individual
             // tool calls within the subagent, not at the spawning level.
-            "subagent",
+            "spawn_agent",
         ];
 
         let tool_info_ids: Vec<&str> = TOOLS.iter().map(|t| t.id).collect();