Skip to content

Commit 3ed28dc

Browse files
feat(agent): delegate tool (#3073)
* feat: implement delegate tool for background agent management - Add delegate tool with list/launch/status operations - Validate agent names against available configs - Run agents silently in background with output capture - Store execution state in ~/.aws/amazonq/.subagents/ - Include PID monitoring for crash detection - Require experimental flag enablement * adds slash command * addresses comments * removes slash command * annotates chronos with seconds --------- Co-authored-by: abhraina-aws <[email protected]>
1 parent 888c276 commit 3ed28dc

File tree

10 files changed

+694
-8
lines changed

10 files changed

+694
-8
lines changed

crates/chat-cli/src/cli/chat/mod.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ use tool_manager::{
106106
ToolManager,
107107
ToolManagerBuilder,
108108
};
109+
use tools::delegate::status_all_agents;
109110
use tools::gh_issue::GhIssueContext;
110111
use tools::{
111112
NATIVE_TOOLS,
@@ -199,6 +200,8 @@ pub const EXTRA_HELP: &str = color_print::cstr! {"
199200
<black!>Change the keybind using: q settings chat.skimCommandKey x</black!>
200201
<em>Ctrl(^) + t</em> <black!>Toggle tangent mode for isolated conversations</black!>
201202
<black!>Change the keybind using: q settings chat.tangentModeKey x</black!>
203+
<em>Ctrl(^) + d</em> <black!>Start delegate command for task delegation</black!>
204+
<black!>Change the keybind using: q settings chat.delegateModeKey x</black!>
202205
<em>chat.editMode</em> <black!>The prompt editing mode (vim or emacs)</black!>
203206
<black!>Change using: q settings chat.skimCommandKey x</black!>
204207
"};
@@ -522,6 +525,7 @@ const CONTINUATION_LINE: &str = " ⋮ ";
522525
const PURPOSE_ARROW: &str = " ↳ ";
523526
const SUCCESS_TICK: &str = " ✓ ";
524527
const ERROR_EXCLAMATION: &str = " ❗ ";
528+
const DELEGATE_NOTIFIER: &str = "[BACKGROUND TASK READY]";
525529

526530
/// Enum used to denote the origin of a tool use event
527531
enum ToolUseStatus {
@@ -2347,7 +2351,7 @@ impl ChatSession {
23472351
os,
23482352
&mut self.stdout,
23492353
&mut self.conversation.file_line_tracker,
2350-
self.conversation.agents.get_active(),
2354+
&self.conversation.agents,
23512355
)
23522356
.await;
23532357

@@ -3374,7 +3378,14 @@ impl ChatSession {
33743378
None
33753379
};
33763380

3377-
prompt::generate_prompt(profile.as_deref(), all_trusted, tangent_mode, usage_percentage)
3381+
let mut generated_prompt =
3382+
prompt::generate_prompt(profile.as_deref(), all_trusted, tangent_mode, usage_percentage);
3383+
3384+
if ExperimentManager::is_enabled(os, ExperimentName::Delegate) && status_all_agents(os).await.is_ok() {
3385+
generated_prompt = format!("{DELEGATE_NOTIFIER}\n{generated_prompt}");
3386+
}
3387+
3388+
generated_prompt
33783389
}
33793390

33803391
async fn send_tool_use_telemetry(&mut self, os: &Os) {

crates/chat-cli/src/cli/chat/prompt.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ use super::tool_manager::{
4646
PromptQuery,
4747
PromptQueryResult,
4848
};
49-
use crate::cli::experiment::experiment_manager::ExperimentManager;
49+
use crate::cli::experiment::experiment_manager::{
50+
ExperimentManager,
51+
ExperimentName,
52+
};
5053
use crate::database::settings::Setting;
5154
use crate::os::Os;
5255
use crate::util::directories::chat_cli_bash_history_path;
@@ -417,6 +420,11 @@ impl Highlighter for ChatHelper {
417420
if let Some(components) = parse_prompt_components(prompt) {
418421
let mut result = String::new();
419422

423+
// Add notifier part if present (blue)
424+
if let Some(notifier) = components.delegate_notifier {
425+
result.push_str(&format!("[{}]\n", notifier).blue().to_string());
426+
}
427+
420428
// Add profile part if present (cyan)
421429
if let Some(profile) = components.profile {
422430
result.push_str(&format!("[{}] ", profile).cyan().to_string());
@@ -497,6 +505,18 @@ pub fn rl(
497505
}
498506
}
499507

508+
// Add custom keybinding for Ctrl+D to open delegate command (configurable)
509+
if ExperimentManager::is_enabled(os, ExperimentName::Delegate) {
510+
if let Some(key) = os.database.settings.get_string(Setting::DelegateModeKey) {
511+
if key.len() == 1 {
512+
rl.bind_sequence(
513+
KeyEvent(KeyCode::Char(key.chars().next().unwrap()), Modifiers::CTRL),
514+
EventHandler::Simple(Cmd::Insert(1, "/delegate ".to_string())),
515+
);
516+
}
517+
};
518+
}
519+
500520
// Add custom keybinding for Alt+Enter to insert a newline
501521
rl.bind_sequence(
502522
KeyEvent(KeyCode::Enter, Modifiers::ALT),

crates/chat-cli/src/cli/chat/prompt_parser.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::cli::agent::DEFAULT_AGENT_NAME;
33
/// Components extracted from a prompt string
44
#[derive(Debug, PartialEq)]
55
pub struct PromptComponents {
6+
pub delegate_notifier: Option<String>,
67
pub profile: Option<String>,
78
pub warning: bool,
89
pub tangent_mode: bool,
@@ -12,12 +13,27 @@ pub struct PromptComponents {
1213
/// Parse prompt components from a plain text prompt
1314
pub fn parse_prompt_components(prompt: &str) -> Option<PromptComponents> {
1415
// Expected format: "[agent] 6% !> " or "> " or "!> " or "[agent] ↯ > " or "6% ↯ > " etc.
16+
let mut delegate_notifier = None::<String>;
1517
let mut profile = None;
1618
let mut warning = false;
1719
let mut tangent_mode = false;
1820
let mut usage_percentage = None;
1921
let mut remaining = prompt.trim();
2022

23+
// Check for delegate notifier first
24+
if let Some(start) = remaining.find('[') {
25+
if let Some(end) = remaining.find(']') {
26+
if start < end {
27+
let content = &remaining[start + 1..end];
28+
// Only set profile if it's not "BACKGROUND TASK READY" or if it doesn't end with newline
29+
if content == "BACKGROUND TASK READY" && remaining[end + 1..].starts_with('\n') {
30+
delegate_notifier = Some(content.to_string());
31+
remaining = remaining[end + 1..].trim_start();
32+
}
33+
}
34+
}
35+
}
36+
2137
// Check for agent pattern [agent] first
2238
if let Some(start) = remaining.find('[') {
2339
if let Some(end) = remaining.find(']') {
@@ -55,6 +71,7 @@ pub fn parse_prompt_components(prompt: &str) -> Option<PromptComponents> {
5571
// Should end with "> " for both normal and tangent mode
5672
if remaining.trim_end() == ">" {
5773
Some(PromptComponents {
74+
delegate_notifier,
5875
profile,
5976
warning,
6077
tangent_mode,

crates/chat-cli/src/cli/chat/tool_manager.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ use crate::cli::chat::server_messenger::{
7171
UpdateEventMessage,
7272
};
7373
use crate::cli::chat::tools::custom_tool::CustomTool;
74+
use crate::cli::chat::tools::delegate::Delegate;
7475
use crate::cli::chat::tools::execute::ExecuteCommand;
7576
use crate::cli::chat::tools::fs_read::FsRead;
7677
use crate::cli::chat::tools::fs_write::FsWrite;
@@ -727,6 +728,9 @@ impl ToolManager {
727728
if !crate::cli::chat::tools::todo::TodoList::is_enabled(os) {
728729
tool_specs.remove("todo_list");
729730
}
731+
if !crate::cli::chat::tools::delegate::Delegate::is_enabled(os) {
732+
tool_specs.remove("delegate");
733+
}
730734

731735
#[cfg(windows)]
732736
{
@@ -873,6 +877,7 @@ impl ToolManager {
873877
"knowledge" => Tool::Knowledge(serde_json::from_value::<Knowledge>(value.args).map_err(map_err)?),
874878
"todo_list" => Tool::Todo(serde_json::from_value::<TodoList>(value.args).map_err(map_err)?),
875879
// Note that this name is NO LONGER namespaced with server_name{DELIMITER}tool_name
880+
"delegate" => Tool::Delegate(serde_json::from_value::<Delegate>(value.args).map_err(map_err)?),
876881
name => {
877882
// Note: tn_map also has tools that underwent no transformation. In otherwords, if
878883
// it is a valid tool name, we should get a hit.

0 commit comments

Comments
 (0)