Skip to content

Conversation

abhraina-aws
Copy link
Contributor

  • 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

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.

- 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.
.stderr(Stdio::piped())
.stdin(Stdio::null());

let child = cmd.spawn()?;
Copy link
Contributor

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.

Copy link
Contributor Author

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 {
Copy link
Contributor

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 {
}
Copy link
Contributor

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\"}",
Copy link
Contributor

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
Copy link
Contributor

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,
Copy link
Contributor

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,
}

Copy link
Contributor Author

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) {
Copy link
Contributor

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

Copy link
Contributor Author

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"))
Copy link
Contributor

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

Copy link
Contributor Author

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;
Copy link
Contributor

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

Copy link
Contributor Author

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>,
Copy link
Contributor

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)]
Copy link
Contributor

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

Copy link
Contributor Author

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 {
Copy link
Contributor

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`
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants