Implement local sub-agent execution for OSS/Bedrock mode
- Fix start_agent tool mapping: route to Tool::StartAgent instead of dead-end Tool::Subagent so tool calls become executable actions - Block start_agent until child finishes: parent waits for child conversation to complete and receives full output as tool result - Fix tool result delivery: add StartAgent/StartAgentV2 cases to extract_tool_result_content so the model actually sees agent output - Support parallel agent spawning: change StartAgent action phase from Serial to Parallel, and track multiple pending agents via Vec - Mark child conversations as Success on EndTurn: emit ConversationStatus::Success when a child stream ends with no actions - Skip orchestration SSE in local mode: prevent app freeze from trying to connect to non-existent server - Remove send_message_to_agent and suggest_next_prompt from tool list: these require server infrastructure that doesn't exist in OSS mode - Add "Waiting for sub-agents..." status message while agents process - Fix child agent pane close: actually dismiss instead of re-hiding, track dismissed IDs to prevent re-creation on restart - Fix cache_miss_tokens calculation and show per-block cache stats - Add [tool-debug] logging throughout tool invocation pipeline Bump version to 1.6.0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
17ecf67970
commit
a309006458
@@ -1109,7 +1109,7 @@ pub fn default_tool_definitions() -> Vec<ToolDefinition> {
|
||||
},
|
||||
ToolDefinition {
|
||||
name: "start_agent".to_string(),
|
||||
description: "Start a sub-agent to handle a specific task autonomously. Use for delegating independent work that can run in parallel. The agent gets its own conversation context and tool access.".to_string(),
|
||||
description: "Start a sub-agent to handle a specific task autonomously. Use for delegating independent work that can run in parallel. The agent gets its own conversation context and tool access. IMPORTANT: Only use this for the initial investigation or when genuinely new research is needed. Do NOT re-spawn agents for follow-up questions if you already have their output in context — just answer from the information you already have.".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1119,18 +1119,9 @@ pub fn default_tool_definitions() -> Vec<ToolDefinition> {
|
||||
"required": ["name", "prompt"]
|
||||
}),
|
||||
},
|
||||
ToolDefinition {
|
||||
name: "send_message_to_agent".to_string(),
|
||||
description: "Send a message to a running sub-agent. Use to provide additional context, ask for updates, or redirect the agent's work.".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"agent_id": { "type": "string", "description": "ID of the target sub-agent" },
|
||||
"message": { "type": "string", "description": "Message to send to the agent" }
|
||||
},
|
||||
"required": ["agent_id", "message"]
|
||||
}),
|
||||
},
|
||||
// send_message_to_agent removed — child agents in local/Bedrock mode
|
||||
// run autonomously and cannot receive messages. Exposing this tool
|
||||
// causes the model to poll in a loop.
|
||||
ToolDefinition {
|
||||
name: "ask_user_question".to_string(),
|
||||
description: "Ask the user a question when you need clarification or a decision. Present clear options when possible. Use sparingly — prefer making reasonable assumptions.".to_string(),
|
||||
@@ -1143,18 +1134,10 @@ pub fn default_tool_definitions() -> Vec<ToolDefinition> {
|
||||
"required": ["question"]
|
||||
}),
|
||||
},
|
||||
ToolDefinition {
|
||||
name: "suggest_next_prompt".to_string(),
|
||||
description: "After completing a task, suggest a relevant follow-up action. Only call once at the end of your response. Keep labels concise and action-oriented.".to_string(),
|
||||
input_schema: serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"prompt": { "type": "string", "description": "The full prompt text sent to the agent if the user clicks" },
|
||||
"label": { "type": "string", "description": "Short display label (under 40 chars)" }
|
||||
},
|
||||
"required": ["prompt", "label"]
|
||||
}),
|
||||
},
|
||||
// suggest_next_prompt removed — its executor hangs waiting for UI
|
||||
// interaction that doesn't exist in the Bedrock path. The stream-level
|
||||
// skip (response_translator) prevents deadlocks, but removing the tool
|
||||
// definition avoids wasting output tokens on calls that will be discarded.
|
||||
ToolDefinition {
|
||||
name: "read_skill".to_string(),
|
||||
description: "Read a skill definition to understand available capabilities and how to use them.".to_string(),
|
||||
@@ -1398,6 +1381,28 @@ fn extract_tool_result_content(result: &api::request::input::ToolCallResult) ->
|
||||
}
|
||||
None => ("Write to shell command completed.".to_string(), false),
|
||||
},
|
||||
api::request::input::tool_call_result::Result::StartAgent(start_agent_result) => {
|
||||
match &start_agent_result.result {
|
||||
Some(api::start_agent_result::Result::Success(success)) => {
|
||||
(success.agent_id.clone(), false)
|
||||
}
|
||||
Some(api::start_agent_result::Result::Error(error)) => {
|
||||
(format!("Agent error: {}", error.error), true)
|
||||
}
|
||||
None => ("Agent completed.".to_string(), false),
|
||||
}
|
||||
}
|
||||
api::request::input::tool_call_result::Result::StartAgentV2(start_agent_result) => {
|
||||
match &start_agent_result.result {
|
||||
Some(api::start_agent_v2_result::Result::Success(success)) => {
|
||||
(success.agent_id.clone(), false)
|
||||
}
|
||||
Some(api::start_agent_v2_result::Result::Error(error)) => {
|
||||
(format!("Agent error: {}", error.error), true)
|
||||
}
|
||||
None => ("Agent completed.".to_string(), false),
|
||||
}
|
||||
}
|
||||
_ => ("Tool completed successfully.".to_string(), false),
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -104,6 +104,7 @@ pub fn bedrock_stream_to_response_events(
|
||||
let mut current_tool_name = String::new();
|
||||
let mut current_tool_input_json = String::new();
|
||||
let mut _has_tool_calls = false;
|
||||
let mut has_start_agent_calls = false;
|
||||
let mut input_tokens: i32 = 0;
|
||||
let mut output_tokens: i32 = 0;
|
||||
let mut cache_read_input_tokens: i32 = 0;
|
||||
@@ -275,6 +276,9 @@ pub fn bedrock_stream_to_response_events(
|
||||
current_tool_name, current_tool_use_id, current_tool_input_json
|
||||
));
|
||||
}
|
||||
if current_tool_name == "start_agent" {
|
||||
has_start_agent_calls = true;
|
||||
}
|
||||
let tool_msg = build_tool_call_message(
|
||||
&task_id,
|
||||
¤t_tool_use_id,
|
||||
@@ -372,12 +376,29 @@ pub fn bedrock_stream_to_response_events(
|
||||
if !text_flushed {
|
||||
let add_msg = build_add_agent_output_message(&task_id, &msg_id, &buffered_text);
|
||||
yield Ok(add_msg);
|
||||
text_flushed = true;
|
||||
current_text_message_id = Some(msg_id);
|
||||
} else {
|
||||
let append = build_append_text(&task_id, &msg_id, &buffered_text);
|
||||
yield Ok(append);
|
||||
}
|
||||
}
|
||||
|
||||
// Show a status message while sub-agents are working so the user
|
||||
// knows we're waiting on them.
|
||||
if has_start_agent_calls {
|
||||
let status_text = "\n\n*Waiting for sub-agents to finish, then I'll review and continue...*";
|
||||
if text_flushed {
|
||||
let msg_id = current_text_message_id.as_ref().unwrap();
|
||||
let append = build_append_text(&task_id, msg_id, status_text);
|
||||
yield Ok(append);
|
||||
} else {
|
||||
let msg_id = Uuid::new_v4().to_string();
|
||||
let add_msg = build_add_agent_output_message(&task_id, &msg_id, status_text);
|
||||
yield Ok(add_msg);
|
||||
}
|
||||
}
|
||||
|
||||
let cost = estimate_cost_cents(
|
||||
input_tokens as u32,
|
||||
output_tokens as u32,
|
||||
@@ -985,13 +1006,14 @@ fn build_tool_call_message(
|
||||
))
|
||||
}
|
||||
"start_agent" => {
|
||||
let _name = input.get("name").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let name = input.get("name").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let prompt = input.get("prompt").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
Some(api::message::tool_call::Tool::Subagent(
|
||||
api::message::tool_call::Subagent {
|
||||
task_id: String::new(),
|
||||
payload: prompt,
|
||||
metadata: None,
|
||||
Some(api::message::tool_call::Tool::StartAgent(
|
||||
api::StartAgent {
|
||||
name,
|
||||
prompt,
|
||||
execution_mode: None,
|
||||
lifecycle_subscription: None,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user