feat: deploy existing agents as subagents via Task tool (#591)
Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
@@ -14,6 +14,8 @@ Launch a subagent to perform a task. Parameters:
|
||||
- **prompt**: Detailed, self-contained instructions for the agent (agents cannot ask questions mid-execution)
|
||||
- **description**: Short 3-5 word summary for tracking
|
||||
- **model** (optional): Override the model for this agent
|
||||
- **agent_id** (optional): Deploy an existing agent instead of creating a new one
|
||||
- **conversation_id** (optional): Resume from an existing conversation
|
||||
|
||||
### Refresh
|
||||
Re-scan the `.letta/agents/` directories to discover new or updated custom subagents:
|
||||
@@ -45,6 +47,57 @@ Use this after creating or modifying custom subagent definitions.
|
||||
- **Parallel execution**: Launch multiple agents concurrently by calling Task multiple times in a single response
|
||||
- **Specify return format**: Tell agents exactly what information to include in their report
|
||||
|
||||
## Deploying an Existing Agent
|
||||
|
||||
Instead of spawning a fresh subagent from a template, you can deploy an existing agent to work in your local codebase.
|
||||
|
||||
### Access Levels (subagent_type)
|
||||
Use subagent_type to control what tools the deployed agent can access:
|
||||
- **explore**: Read-only access (Read, Glob, Grep) - safer for exploration tasks
|
||||
- **general-purpose**: Full read-write access (Bash, Edit, Write, etc.) - for implementation tasks
|
||||
|
||||
If subagent_type is not specified when deploying an existing agent, it defaults to "general-purpose".
|
||||
|
||||
### Parameters
|
||||
|
||||
- **agent_id**: The ID of an existing agent to deploy (e.g., "agent-abc123")
|
||||
- Starts a new conversation with that agent
|
||||
- The agent keeps its own system prompt and memory
|
||||
- Tool access is controlled by subagent_type
|
||||
|
||||
- **conversation_id**: Resume from an existing conversation (e.g., "conv-xyz789")
|
||||
- Does NOT require agent_id (conversation IDs are unique and encode the agent)
|
||||
- Continues from the conversation's existing message history
|
||||
- Use this to continue context from:
|
||||
- A prior Task tool invocation that returned a conversation_id
|
||||
- A message thread started via the messaging-agents skill
|
||||
|
||||
### Examples
|
||||
|
||||
```typescript
|
||||
// Deploy agent with read-only access
|
||||
Task({
|
||||
agent_id: "agent-abc123",
|
||||
subagent_type: "explore",
|
||||
description: "Find auth code",
|
||||
prompt: "Find all auth-related code in this codebase"
|
||||
})
|
||||
|
||||
// Deploy agent with full access (default)
|
||||
Task({
|
||||
agent_id: "agent-abc123",
|
||||
description: "Fix auth bug",
|
||||
prompt: "Fix the bug in auth.ts"
|
||||
})
|
||||
|
||||
// Continue an existing conversation
|
||||
Task({
|
||||
conversation_id: "conv-xyz789",
|
||||
description: "Continue implementation",
|
||||
prompt: "Now implement the fix we discussed"
|
||||
})
|
||||
```
|
||||
|
||||
## Examples:
|
||||
|
||||
```typescript
|
||||
|
||||
@@ -25,10 +25,15 @@ interface TaskArgs {
|
||||
prompt?: string;
|
||||
description?: string;
|
||||
model?: string;
|
||||
agent_id?: string; // Deploy an existing agent instead of creating new
|
||||
conversation_id?: string; // Resume from an existing conversation
|
||||
toolCallId?: string; // Injected by executeTool for linking subagent to parent tool call
|
||||
signal?: AbortSignal; // Injected by executeTool for interruption handling
|
||||
}
|
||||
|
||||
// Valid subagent_types when deploying an existing agent
|
||||
const VALID_DEPLOY_TYPES = new Set(["explore", "general-purpose"]);
|
||||
|
||||
/**
|
||||
* Task tool - Launch a specialized subagent to handle complex tasks
|
||||
*/
|
||||
@@ -61,18 +66,31 @@ export async function task(args: TaskArgs): Promise<string> {
|
||||
return `Refreshed subagents list: found ${totalCount} total (${customCount} custom)${errorSuffix}`;
|
||||
}
|
||||
|
||||
// For run command, validate required parameters
|
||||
validateRequiredParams(
|
||||
args,
|
||||
["subagent_type", "prompt", "description"],
|
||||
"Task",
|
||||
);
|
||||
// Determine if deploying an existing agent
|
||||
const isDeployingExisting = Boolean(args.agent_id || args.conversation_id);
|
||||
|
||||
// Extract validated params (guaranteed to exist after validateRequiredParams)
|
||||
const subagent_type = args.subagent_type as string;
|
||||
// Validate required parameters based on mode
|
||||
if (isDeployingExisting) {
|
||||
// Deploying existing agent: prompt and description required, subagent_type optional
|
||||
validateRequiredParams(args, ["prompt", "description"], "Task");
|
||||
} else {
|
||||
// Creating new agent: subagent_type, prompt, and description required
|
||||
validateRequiredParams(
|
||||
args,
|
||||
["subagent_type", "prompt", "description"],
|
||||
"Task",
|
||||
);
|
||||
}
|
||||
|
||||
// Extract validated params
|
||||
const prompt = args.prompt as string;
|
||||
const description = args.description as string;
|
||||
|
||||
// For existing agents, default subagent_type to "general-purpose" for permissions
|
||||
const subagent_type = isDeployingExisting
|
||||
? args.subagent_type || "general-purpose"
|
||||
: (args.subagent_type as string);
|
||||
|
||||
// Get all available subagent configs (built-in + custom)
|
||||
const allConfigs = await getAllSubagentConfigs();
|
||||
|
||||
@@ -82,6 +100,11 @@ export async function task(args: TaskArgs): Promise<string> {
|
||||
return `Error: Invalid subagent type "${subagent_type}". Available types: ${available}`;
|
||||
}
|
||||
|
||||
// For existing agents, only allow explore or general-purpose
|
||||
if (isDeployingExisting && !VALID_DEPLOY_TYPES.has(subagent_type)) {
|
||||
return `Error: When deploying an existing agent, subagent_type must be "explore" (read-only) or "general-purpose" (read-write). Got: "${subagent_type}"`;
|
||||
}
|
||||
|
||||
// Register subagent with state store for UI display
|
||||
const subagentId = generateSubagentId();
|
||||
registerSubagent(subagentId, subagent_type, description, toolCallId);
|
||||
@@ -93,6 +116,8 @@ export async function task(args: TaskArgs): Promise<string> {
|
||||
model,
|
||||
subagentId,
|
||||
signal,
|
||||
args.agent_id,
|
||||
args.conversation_id,
|
||||
);
|
||||
|
||||
// Mark subagent as completed in state store
|
||||
@@ -111,6 +136,9 @@ export async function task(args: TaskArgs): Promise<string> {
|
||||
const header = [
|
||||
`subagent_type=${subagent_type}`,
|
||||
result.agentId ? `agent_id=${result.agentId}` : undefined,
|
||||
result.conversationId
|
||||
? `conversation_id=${result.conversationId}`
|
||||
: undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
|
||||
Reference in New Issue
Block a user