-
Notifications
You must be signed in to change notification settings - Fork 297
feat: implement delegate tool for agent task management #2890
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Add delegate tool for launching and managing asynchronous agent processes - Support agent validation with approval UI for security - Enable status checking for individual agents or all agents - Implement background process monitoring and execution tracking - Store execution state in ~/.aws/amazonq/.subagents/ - Support both global (~/.aws/amazonq/cli-agents/) and local (.amazonq/cli-agents/) agent configurations - Add experimental flag for delegate functionality - Clean error handling with proper agent not found messages - Unicode characters instead of emojis for better terminal compatibility The delegate tool allows users to offload tasks to specialized agents that run independently, enabling parallel work and better task organization. Agents require user approval before execution for security, and the tool provides comprehensive status tracking.
797e348
to
bc6356a
Compare
.stderr(Stdio::piped()) | ||
.stdin(Stdio::null()); | ||
|
||
let child = cmd.spawn()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't gone through the rest of the PR yet but I see here we are spawning the the child process without promoting them to process group leader.
Any signals received by the main process is also going to be received by the child process. So a SIGINT from the main process is going to terminate this. Not entirely sure if that's what we want, just wanted to point that out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I think I would want to keep the background process running if the main one is killed. Also another angle is that we are trying to keep this simple. But Matt probably is the best to comment on this.
pub output: String, | ||
} | ||
|
||
mod status_serde { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this to be deserialized with a custom deserializer? It does not look like the logic here is doing anything all that special.
@@ -114,156 +114,156 @@ impl FsRead { | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this file just formatting change?
}, | ||
"delegate": { | ||
"name": "delegate", | ||
"description": "Launch and manage asynchronous agent processes. This tool allows you to delegate tasks to agents that run independently in the background.\n\nOperations:\n- launch: Start a new task with an agent (requires task parameter, agent is optional)\n- status: Check agent status and get full output if completed. Agent is optional - defaults to 'all' if not specified\n\nIf no agent is specified for launch, uses 'default_agent'. Only one task can run per agent at a time. Files are stored in ~/.aws/amazonq/.subagents/\n\nIMPORTANT: If a specific agent is requested but not found, DO NOT automatically retry with 'default_agent' or any other agent. Simply report the error and available agents to the user.\n\nExample usage:\n1. Launch with agent: {\"operation\": \"launch\", \"agent\": \"rust-agent\", \"task\": \"Create a snake game\"}\n2. Launch without agent: {\"operation\": \"launch\", \"task\": \"Write a Python script\"}\n3. Check specific agent: {\"operation\": \"status\", \"agent\": \"rust-agent\"}\n4. Check all agents: {\"operation\": \"status\", \"agent\": \"all\"}\n5. Check all agents (shorthand): {\"operation\": \"status\"}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about running multiple delegate tasks using the same agent config? This seems to only support one agent at a time
@@ -114,156 +114,156 @@ impl FsRead { | |||
} | |||
|
|||
let is_in_allowlist = matches_any_pattern(&agent.allowed_tools, "fs_read"); | |||
let settings = agent.tools_settings.get("fs_read").cloned() | |||
let settings = agent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These only seem to be formatting changes, right?
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
pub struct Delegate { | ||
/// Operation to perform: launch or status | ||
pub operation: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can define operation as an enum, and use strum
derive macros for defining it.
I think something like:
#[derive(strum::EnumString)]
#[strum(serialize_all = "lowercase")]
enum Operation {
Launch,
Status,
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure
) | ||
} | ||
|
||
async fn monitor_agent_execution(mut execution: AgentExecution, os: Os) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't seem clear as to what is this supposed to be doing? It seems that we're just launching another q
instance to run the task.
monitor_agent_execution
being launched on another task through start_monitoring
would lead me to believe we're doing some loop and continually polling the status of an agent, but instead we're just launching the same task again as is done in spawn_agent_process
? Or am I missing something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me check this out.
|
||
pub async fn cli_agents_dir(os: &Os) -> Result<PathBuf> { | ||
let home_dir = os.env.home().unwrap_or_default(); | ||
Ok(home_dir.join(".aws").join("amazonq").join("cli-agents")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have this already defined in src/util/directories.rs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I can check that out.
@@ -0,0 +1,27 @@ | |||
pub struct AgentError; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't being used as an error? We should implement std::error::Error
(easy with thiserror::Error
derive macro) and use this as an error return value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to discuss this.
#[serde(default)] | ||
pub launched_at: String, | ||
#[serde(default)] | ||
pub completed_at: Option<String>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the format of this timestamp? Could we use chrono
crate instead?
e.g.
#[serde(with = "chrono::serde::ts_seconds_option")]
pub completed_at: Option<DateTime<Utc>>,
Serialize, | ||
}; | ||
|
||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use strum::EnumString
and strum::Display
instead of having to manually implement serialize/deserialize/fromstr/as_str
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct AgentConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this used for? Just deserializing a subset of the full agent config?
@@ -79,6 +79,35 @@ Amazon Q CLI includes experimental features that can be toggled on/off using the | |||
**Settings:** | |||
- `chat.enableTodoList` - Enable/disable TODO list functionality (boolean) | |||
|
|||
### Delegate | |||
|
|||
**Command:** `/delegate` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of this is left unimplemented intentionally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will update it since this is legacy now.
The delegate tool allows users to offload tasks to specialized agents that run independently, enabling parallel work and better task organization. Agents require user approval before execution for security, and the tool provides comprehensive status tracking.
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.