@townco/agent 0.1.28 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/acp-server/adapter.d.ts +21 -14
  2. package/dist/acp-server/adapter.js +15 -3
  3. package/dist/acp-server/cli.d.ts +1 -3
  4. package/dist/acp-server/cli.js +5 -9
  5. package/dist/acp-server/http.d.ts +1 -3
  6. package/dist/acp-server/http.js +3 -3
  7. package/dist/bin.js +0 -0
  8. package/dist/index.js +8 -0
  9. package/dist/runner/agent-runner.d.ts +1 -0
  10. package/dist/runner/index.d.ts +1 -3
  11. package/dist/runner/index.js +14 -18
  12. package/dist/runner/langchain/index.d.ts +2 -2
  13. package/dist/runner/langchain/index.js +66 -7
  14. package/dist/runner/langchain/tools/subagent.d.ts +43 -0
  15. package/dist/runner/langchain/tools/subagent.js +278 -0
  16. package/dist/runner/langchain/tools/todo.d.ts +33 -48
  17. package/dist/runner/langchain/tools/todo.js +2 -1
  18. package/dist/runner/langchain/tools/web_search.d.ts +3 -0
  19. package/dist/runner/langchain/tools/web_search.js +55 -13
  20. package/dist/scaffold/templates/dot-claude/CLAUDE-append.md +73 -0
  21. package/dist/tsconfig.tsbuildinfo +1 -1
  22. package/dist/utils/index.d.ts +1 -1
  23. package/dist/utils/index.js +1 -1
  24. package/index.ts +8 -0
  25. package/package.json +8 -9
  26. package/dist/definition/mcp.d.ts +0 -0
  27. package/dist/definition/mcp.js +0 -0
  28. package/dist/definition/tools/todo.d.ts +0 -49
  29. package/dist/definition/tools/todo.js +0 -80
  30. package/dist/definition/tools/web_search.d.ts +0 -4
  31. package/dist/definition/tools/web_search.js +0 -26
  32. package/dist/dev-agent/index.d.ts +0 -2
  33. package/dist/dev-agent/index.js +0 -18
  34. package/dist/example.d.ts +0 -2
  35. package/dist/example.js +0 -19
  36. package/dist/utils/logger.d.ts +0 -39
  37. package/dist/utils/logger.js +0 -175
@@ -0,0 +1,278 @@
1
+ import { spawn } from "node:child_process";
2
+ import * as fs from "node:fs/promises";
3
+ import * as path from "node:path";
4
+ import { Readable, Writable } from "node:stream";
5
+ import { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION, } from "@agentclientprotocol/sdk";
6
+ import { z } from "zod";
7
+ import { SUBAGENT_MODE_KEY } from "../../../acp-server/adapter.js";
8
+ /**
9
+ * Name of the Task tool created by makeSubagentsTool
10
+ */
11
+ export const TASK_TOOL_NAME = "Task";
12
+ /**
13
+ * Creates a DirectTool that delegates work to one of multiple configured subagents.
14
+ *
15
+ * @param configs - Array of subagent configurations
16
+ * @returns A DirectTool named "Task" that can route to any configured subagent
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { makeSubagentsTool } from "@townco/agent/utils";
21
+ *
22
+ * const agent: AgentDefinition = {
23
+ * model: "claude-sonnet-4-5-20250929",
24
+ * systemPrompt: "You are a coordinator.",
25
+ * tools: [
26
+ * makeSubagentsTool([
27
+ * { agentName: "researcher", description: "Use this agent to research topics", cwd: "/path/to/agents" },
28
+ * { agentName: "writer", description: "Use this agent to write content", path: "/absolute/path/to/writer/index.ts" },
29
+ * ]),
30
+ * ]
31
+ * };
32
+ * ```
33
+ */
34
+ export function makeSubagentsTool(configs) {
35
+ if (configs.length === 0) {
36
+ throw new Error("makeSubagentsTool requires at least one subagent configuration");
37
+ }
38
+ // Build a map from agentName to resolved paths
39
+ const agentMap = new Map();
40
+ for (const config of configs) {
41
+ const { agentName } = config;
42
+ let agentPath;
43
+ let agentDir;
44
+ if ("path" in config) {
45
+ // Direct path variant
46
+ agentPath = config.path;
47
+ agentDir = path.dirname(agentPath);
48
+ }
49
+ else {
50
+ // Agent name + cwd variant
51
+ const { cwd: workingDirectory } = config;
52
+ const resolvedWorkingDirectory = workingDirectory ?? path.resolve("agents", agentName, "..", "..");
53
+ agentDir = path.join(resolvedWorkingDirectory, "agents", agentName);
54
+ agentPath = path.join(agentDir, "index.ts");
55
+ }
56
+ agentMap.set(agentName, { agentPath, agentDir });
57
+ }
58
+ // Build the tool description with all subagent descriptions
59
+ const agentDescriptions = configs
60
+ .map((config) => `"${config.agentName}": ${config.description}`)
61
+ .join("\n");
62
+ const agentNames = configs.map((c) => c.agentName);
63
+ return {
64
+ type: "direct",
65
+ name: TASK_TOOL_NAME,
66
+ description: `Launch a new agent to handle complex, multi-step tasks autonomously.
67
+
68
+ The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
69
+
70
+ Available agent types and the tools they have access to:
71
+ ${agentDescriptions}
72
+
73
+ When NOT to use the Task tool:
74
+ - If you want to read a specific file path, use the Read or Glob tool instead of the Task tool, to find the match more quickly
75
+ - If you are searching for a specific class definition like "class Foo", use the Glob tool instead, to find the match more quickly
76
+ - If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead of the Task tool, to find the match more quickly
77
+ - Other tasks that are not related to the agent descriptions above
78
+
79
+
80
+ Usage notes:
81
+ - Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
82
+ - When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
83
+ - Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
84
+ - The agent's outputs should generally be trusted
85
+ - Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent
86
+ - If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
87
+ - If the user specifies that they want you to run agents "in parallel", you MUST send a single message with multiple Task tool use content blocks. For example, if you need to launch both a code-reviewer agent and a test-runner agent in parallel, send a single message with both tool calls.
88
+
89
+ Example usage:
90
+
91
+ <example_agent_descriptions>
92
+ "code-reviewer": use this agent after you are done writing a signficant piece of code
93
+ "greeting-responder": use this agent when to respond to user greetings with a friendly joke
94
+ </example_agent_description>
95
+
96
+ <example>
97
+ user: "Please write a function that checks if a number is prime"
98
+ assistant: Sure let me write a function that checks if a number is prime
99
+ assistant: First let me use the Write tool to write a function that checks if a number is prime
100
+ assistant: I'm going to use the Write tool to write the following code:
101
+ <code>
102
+ function isPrime(n) {
103
+ if (n <= 1) return false
104
+ for (let i = 2; i * i <= n; i++) {
105
+ if (n % i === 0) return false
106
+ }
107
+ return true
108
+ }
109
+ </code>
110
+ <commentary>
111
+ Since a signficant piece of code was written and the task was completed, now use the code-reviewer agent to review the code
112
+ </commentary>
113
+ assistant: Now let me use the code-reviewer agent to review the code
114
+ assistant: Uses the Task tool to launch the code-reviewer agent
115
+ </example>
116
+
117
+ <example>
118
+ user: "Hello"
119
+ <commentary>
120
+ Since the user is greeting, use the greeting-responder agent to respond with a friendly joke
121
+ </commentary>
122
+ assistant: "I'm going to use the Task tool to launch the greeting-responder agent"
123
+ </example>
124
+ `,
125
+ schema: z.object({
126
+ agentName: z
127
+ .enum(agentNames)
128
+ .describe("The name of the subagent to use"),
129
+ query: z.string().describe("The query or task to send to the subagent"),
130
+ }),
131
+ fn: async (input) => {
132
+ const { agentName, query } = input;
133
+ const agent = agentMap.get(agentName);
134
+ if (!agent) {
135
+ throw new Error(`Unknown agent: ${agentName}`);
136
+ }
137
+ return await querySubagent(agent.agentPath, agent.agentDir, query);
138
+ },
139
+ };
140
+ }
141
+ /**
142
+ * Internal function that spawns a subagent process and collects its response.
143
+ */
144
+ async function querySubagent(agentPath, agentWorkingDirectory, query) {
145
+ // Validate that the agent exists
146
+ try {
147
+ await fs.access(agentPath);
148
+ }
149
+ catch (_error) {
150
+ throw new Error(`Agent not found at ${agentPath}. Make sure the agent exists and has an index.ts file.`);
151
+ }
152
+ let agentProcess = null;
153
+ let connection = null;
154
+ try {
155
+ // Spawn the agent process
156
+ agentProcess = spawn("bun", [agentPath], {
157
+ cwd: agentWorkingDirectory,
158
+ env: { ...process.env },
159
+ stdio: ["pipe", "pipe", "pipe"],
160
+ });
161
+ if (!agentProcess.stdin || !agentProcess.stdout || !agentProcess.stderr) {
162
+ throw new Error("Failed to create stdio pipes for agent process");
163
+ }
164
+ // Convert Node.js streams to Web streams
165
+ const outputStream = Writable.toWeb(agentProcess.stdin);
166
+ const inputStream = Readable.toWeb(agentProcess.stdout);
167
+ // Create the bidirectional stream using ndJsonStream
168
+ const stream = ndJsonStream(outputStream, inputStream);
169
+ // Track accumulated response text
170
+ let responseText = "";
171
+ // Create ACP client implementation factory
172
+ const clientFactory = (_agent) => {
173
+ return {
174
+ async requestPermission(_params) {
175
+ // Deny all permission requests from the subagent
176
+ return { outcome: { outcome: "cancelled" } };
177
+ },
178
+ async sessionUpdate(params) {
179
+ // Handle session updates from the agent
180
+ const paramsExtended = params;
181
+ const update = paramsExtended.update;
182
+ // Accumulate agent_message_chunk text content
183
+ if (update?.sessionUpdate === "agent_message_chunk") {
184
+ const content = update.content;
185
+ if (content &&
186
+ content.type === "text" &&
187
+ typeof content.text === "string") {
188
+ responseText += content.text;
189
+ }
190
+ }
191
+ },
192
+ async writeTextFile() {
193
+ // Subagents should not write files outside their scope
194
+ throw new Error("Subagent attempted to write files, which is not allowed");
195
+ },
196
+ async readTextFile() {
197
+ // Subagents should not read files outside their scope
198
+ throw new Error("Subagent attempted to read files, which is not allowed");
199
+ },
200
+ };
201
+ };
202
+ // Create the client-side connection
203
+ connection = new ClientSideConnection(clientFactory, stream);
204
+ // Set up timeout for the entire operation
205
+ const timeoutMs = 5 * 60 * 1000; // 5 minutes
206
+ const timeoutPromise = new Promise((_resolve, reject) => {
207
+ setTimeout(() => {
208
+ reject(new Error(`Subagent query timed out after ${timeoutMs / 1000} seconds`));
209
+ }, timeoutMs);
210
+ });
211
+ // Handle process errors and exit
212
+ const processExitPromise = new Promise((_resolve, reject) => {
213
+ agentProcess?.on("exit", (code, signal) => {
214
+ if (code !== 0 && code !== null) {
215
+ reject(new Error(`Agent process exited with code ${code} and signal ${signal}`));
216
+ }
217
+ });
218
+ agentProcess?.on("error", (error) => {
219
+ reject(new Error(`Agent process error: ${error.message}`));
220
+ });
221
+ });
222
+ // Run the query with timeout and error handling
223
+ const queryPromise = (async () => {
224
+ // Initialize the connection
225
+ await connection?.initialize({
226
+ protocolVersion: PROTOCOL_VERSION,
227
+ clientCapabilities: {
228
+ fs: {
229
+ readTextFile: false,
230
+ writeTextFile: false,
231
+ },
232
+ },
233
+ });
234
+ // Create a new session with subagent mode flag
235
+ const sessionResponse = await connection?.newSession({
236
+ cwd: agentWorkingDirectory,
237
+ mcpServers: [],
238
+ _meta: {
239
+ [SUBAGENT_MODE_KEY]: true,
240
+ },
241
+ });
242
+ // Send the prompt
243
+ await connection?.prompt({
244
+ sessionId: sessionResponse.sessionId,
245
+ prompt: [
246
+ {
247
+ type: "text",
248
+ text: query,
249
+ },
250
+ ],
251
+ });
252
+ return responseText;
253
+ })();
254
+ // Race between query execution, timeout, and process exit
255
+ return await Promise.race([
256
+ queryPromise,
257
+ timeoutPromise,
258
+ processExitPromise,
259
+ ]);
260
+ }
261
+ finally {
262
+ // Cleanup: kill process and close connection
263
+ if (agentProcess) {
264
+ agentProcess.kill();
265
+ }
266
+ if (connection) {
267
+ try {
268
+ await Promise.race([
269
+ connection.closed,
270
+ new Promise((resolve) => setTimeout(resolve, 1000)),
271
+ ]);
272
+ }
273
+ catch {
274
+ // Ignore cleanup errors
275
+ }
276
+ }
277
+ }
278
+ }
@@ -1,49 +1,34 @@
1
1
  import { z } from "zod";
2
- export declare const todoItemSchema: z.ZodObject<
3
- {
4
- content: z.ZodString;
5
- status: z.ZodEnum<{
6
- pending: "pending";
7
- in_progress: "in_progress";
8
- completed: "completed";
9
- }>;
10
- activeForm: z.ZodString;
11
- },
12
- z.core.$strip
13
- >;
14
- export declare const todoWrite: import("langchain").DynamicStructuredTool<
15
- z.ZodObject<
16
- {
17
- todos: z.ZodArray<
18
- z.ZodObject<
19
- {
20
- content: z.ZodString;
21
- status: z.ZodEnum<{
22
- pending: "pending";
23
- in_progress: "in_progress";
24
- completed: "completed";
25
- }>;
26
- activeForm: z.ZodString;
27
- },
28
- z.core.$strip
29
- >
30
- >;
31
- },
32
- z.core.$strip
33
- >,
34
- {
35
- todos: {
36
- content: string;
37
- status: "pending" | "in_progress" | "completed";
38
- activeForm: string;
39
- }[];
40
- },
41
- {
42
- todos: {
43
- content: string;
44
- status: "pending" | "in_progress" | "completed";
45
- activeForm: string;
46
- }[];
47
- },
48
- string
49
- >;
2
+ export declare const TODO_WRITE_TOOL_NAME = "todo_write";
3
+ export declare const todoItemSchema: z.ZodObject<{
4
+ content: z.ZodString;
5
+ status: z.ZodEnum<{
6
+ pending: "pending";
7
+ in_progress: "in_progress";
8
+ completed: "completed";
9
+ }>;
10
+ activeForm: z.ZodString;
11
+ }, z.core.$strip>;
12
+ export declare const todoWrite: import("langchain").DynamicStructuredTool<z.ZodObject<{
13
+ todos: z.ZodArray<z.ZodObject<{
14
+ content: z.ZodString;
15
+ status: z.ZodEnum<{
16
+ pending: "pending";
17
+ in_progress: "in_progress";
18
+ completed: "completed";
19
+ }>;
20
+ activeForm: z.ZodString;
21
+ }, z.core.$strip>>;
22
+ }, z.core.$strip>, {
23
+ todos: {
24
+ content: string;
25
+ status: "pending" | "in_progress" | "completed";
26
+ activeForm: string;
27
+ }[];
28
+ }, {
29
+ todos: {
30
+ content: string;
31
+ status: "pending" | "in_progress" | "completed";
32
+ activeForm: string;
33
+ }[];
34
+ }, string>;
@@ -1,5 +1,6 @@
1
1
  import { tool } from "langchain";
2
2
  import { z } from "zod";
3
+ export const TODO_WRITE_TOOL_NAME = "todo_write";
3
4
  export const todoItemSchema = z.object({
4
5
  content: z.string().min(1),
5
6
  status: z.enum(["pending", "in_progress", "completed"]),
@@ -9,7 +10,7 @@ export const todoWrite = tool(({ todos }) => {
9
10
  // Simple implementation that confirms the todos were written
10
11
  return `Successfully updated todo list with ${todos.length} items`;
11
12
  }, {
12
- name: "todo_write",
13
+ name: TODO_WRITE_TOOL_NAME,
13
14
  description: `Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
14
15
  It also helps the user understand the progress of the task and overall progress of their requests.
15
16
 
@@ -13,8 +13,11 @@ export declare function makeWebSearchTools(): readonly [import("langchain").Dyna
13
13
  };
14
14
  }>>, import("langchain").DynamicStructuredTool<z.ZodObject<{
15
15
  url: z.ZodString;
16
+ prompt: z.ZodString;
16
17
  }, z.core.$strip>, {
17
18
  url: string;
19
+ prompt: string;
18
20
  }, {
19
21
  url: string;
22
+ prompt: string;
20
23
  }, string>];
@@ -1,3 +1,4 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
1
2
  import Exa from "exa-js";
2
3
  import { tool } from "langchain";
3
4
  import { z } from "zod";
@@ -46,7 +47,7 @@ export function makeWebSearchTools() {
46
47
  }),
47
48
  });
48
49
  // WebFetch tool - get contents of specific URLs
49
- const webFetch = tool(async ({ url }) => {
50
+ const webFetch = tool(async ({ url, prompt }) => {
50
51
  const client = getExaClient();
51
52
  try {
52
53
  const result = await client.getContents([url], {
@@ -60,16 +61,32 @@ export function makeWebSearchTools() {
60
61
  if (!page) {
61
62
  return `No content found for URL: ${url}`;
62
63
  }
63
- let output = `Title: ${page.title}\n`;
64
- output += `URL: ${page.url}\n`;
65
- if (page.author) {
66
- output += `Author: ${page.author}\n`;
64
+ const pageContent = page.text || "(No text content available)";
65
+ // Process the content with Anthropic API
66
+ const apiKey = process.env.ANTHROPIC_API_KEY;
67
+ if (!apiKey) {
68
+ throw new Error("ANTHROPIC_API_KEY environment variable is required to use the WebFetch tool. " +
69
+ "Please set it to your Anthropic API key.");
67
70
  }
68
- if (page.publishedDate) {
69
- output += `Published: ${page.publishedDate}\n`;
71
+ const anthropicClient = new Anthropic({ apiKey });
72
+ const userMessage = buildWebFetchUserMessage(pageContent, prompt);
73
+ const response = await anthropicClient.messages.create({
74
+ model: "claude-haiku-4-5-20251001",
75
+ max_tokens: 1024,
76
+ system: "You are a helpful assistant",
77
+ messages: [
78
+ {
79
+ role: "user",
80
+ content: userMessage,
81
+ },
82
+ ],
83
+ });
84
+ // Extract text from response
85
+ const textContent = response.content.find((block) => block.type === "text");
86
+ if (!textContent || textContent.type !== "text") {
87
+ return "Error: No text response from AI model";
70
88
  }
71
- output += `\nContent:\n${page.text || "(No text content available)"}`;
72
- return output;
89
+ return textContent.text;
73
90
  }
74
91
  catch (error) {
75
92
  if (error instanceof Error) {
@@ -79,16 +96,41 @@ export function makeWebSearchTools() {
79
96
  }
80
97
  }, {
81
98
  name: "WebFetch",
82
- description: "Fetches the full text content of a specific webpage using its URL. " +
83
- "Returns the page title, URL, author (if available), published date (if available), and full text content. " +
84
- "Use this tool when you need to read the complete contents of a webpage that you already know the URL for. " +
85
- "For finding URLs, use the WebSearch tool first.",
99
+ description: "- Fetches content from a specified URL and processes it using an AI model\n" +
100
+ "- Takes a URL and a prompt as input\n" +
101
+ "- Fetches the URL content, converts HTML to markdown\n" +
102
+ "- Processes the content with the prompt using a small, fast model\n" +
103
+ "- Returns the model's response about the content\n" +
104
+ "- Use this tool when you need to retrieve and analyze web content\n" +
105
+ "\n" +
106
+ "Usage notes:\n" +
107
+ " - The URL must be a fully-formed valid URL\n" +
108
+ " - HTTP URLs will be automatically upgraded to HTTPS\n" +
109
+ " - The prompt should describe what information you want to extract from the page\n" +
110
+ " - This tool is read-only and does not modify any files\n" +
111
+ " - Results may be summarized if the content is very large\n" +
112
+ " - When a URL redirects to a different host, the tool will inform you and provide the redirect URL in a special format. You should then make a new WebFetch request with the redirect URL to fetch the content.\n",
86
113
  schema: z.object({
87
114
  url: z
88
115
  .string()
89
116
  .url()
90
117
  .describe("The URL of the webpage to fetch content from"),
118
+ prompt: z.string().describe("The prompt to run on the fetched content"),
91
119
  }),
92
120
  });
93
121
  return [webSearch, webFetch];
94
122
  }
123
+ function buildWebFetchUserMessage(pageContent, prompt) {
124
+ return `Web page content:
125
+ ---
126
+ ${pageContent}
127
+ ---
128
+
129
+ ${prompt}
130
+
131
+ Provide a concise response based only on the content above. In your response:
132
+ - Enforce a strict 125-character maximum for quotes from any source document. Open Source Software is ok as long as we respect the license.
133
+ - Use quotation marks for exact language from articles; any language outside of the quotation should never be word-for-word the same.
134
+ - You are not a lawyer and never comment on the legality of your own prompts and responses.
135
+ - Never produce or reproduce exact song lyrics.`;
136
+ }
@@ -97,3 +97,76 @@ const agent: AgentDefinition = {
97
97
  ],
98
98
  };
99
99
  ```
100
+
101
+ ## Adding Subagents
102
+ Subagents allow you to delegate complex, multi-step tasks to specialized agents. The `makeSubagentsTool` utility creates a "Task" tool that can route work to different subagents based on their capabilities.
103
+
104
+ ### Configuration
105
+ Import `makeSubagentsTool` and add it to your agent's tools:
106
+
107
+ ```typescript
108
+ import { makeSubagentsTool } from '@townco/agent/utils';
109
+
110
+ const agent: AgentDefinition = {
111
+ model: "claude-sonnet-4-5-20250929",
112
+ systemPrompt: "You are a coordinator agent.",
113
+ tools: [
114
+ "todo_write",
115
+ "filesystem",
116
+ makeSubagentsTool([
117
+ {
118
+ agentName: "researcher",
119
+ description: "Use this agent to research topics and gather information",
120
+ },
121
+ {
122
+ agentName: "code-reviewer",
123
+ description: "Use this agent to review code for bugs and improvements",
124
+ path: "/absolute/path/to/code-reviewer/index.ts",
125
+ },
126
+ ]),
127
+ ],
128
+ };
129
+ ```
130
+
131
+ ### Configuration options
132
+ Each subagent config supports two variants:
133
+
134
+ **1. Adding an agent in the current workspace:**
135
+ This should be used for agents that are in the current Town workspace (in the
136
+ same `agents/` directory).
137
+ ```typescript
138
+ {
139
+ agentName: "researcher",
140
+ description: "Agent description for the LLM",
141
+ }
142
+ ```
143
+ This expects the agent at `{workspace_dir}/agents/{agentName}/index.ts`
144
+
145
+ **2. Using `path` (direct path):**
146
+ ```typescript
147
+ {
148
+ agentName: "code-reviewer",
149
+ description: "Agent description for the LLM",
150
+ path: "/absolute/path/to/agent/index.ts",
151
+ }
152
+ ```
153
+ This uses the exact path specified (this is useful for agents outside the
154
+ standard location -- this is rarely the case)
155
+
156
+ ### Usage example
157
+ Once configured, the parent agent can delegate tasks:
158
+ ```typescript
159
+ // The agent will see a "Task" tool with these parameters:
160
+ // - agentName: enum of ["researcher", "code-reviewer"]
161
+ // - query: string describing the task
162
+
163
+ // Example prompt that triggers the Task tool:
164
+ // "Please research the latest best practices for React hooks"
165
+ // -> Agent uses Task tool with agentName="researcher"
166
+ ```
167
+
168
+ ### Best practices
169
+ - Give each subagent a clear, specific description of its purpose
170
+ - Keep subagent responsibilities focused and non-overlapping
171
+ - Use descriptive agent names that indicate their role
172
+ - Remember that subagents cannot use the Task tool (no nested subagents)