Chapter 09: Subagents
Add the subagent system — specialized agents with different prompts and tools. Build a code-map generator and integrate the prompt system.
TUTOR WITH THEFOCUS.AI
Copy this prompt into Claude, ChatGPT, or any external AI assistant. It points the assistant to the course instructions and links it to your student profile to track your progress and customize observations.
You are not enrolled yet. Enroll to generate a Student ID to track lesson completions and store learning notes.
Chapter 09: Subagents
What We’re Building
Subagents are the key to managing context and specialization. Instead of one giant agent that knows everything, we spawn focused subagents for specific tasks. Each subagent:
- Has its own system prompt and tool set
- Runs in a nested
runTurnloop - Returns a result to the parent agent
- Has its own session ID in the logs (with parent tracking)
Step 1: Build the Prompt System
First, refactor how prompts work. Paste:
Clean up the prompt system. We want to support multiple prompts, for example code-explorer, or feature-planner, or tech-researcher, that will have access to different tools and write out different results. Each prompt should have a description describing what it’s appropriate for, and a list of tools that it has access to. We want to add a sub-prompt tool that will create a new agent with a detailed message, and then the agent can return the result or ask more questions.
The Prompt System Architecture
Prompts are defined as .md files in src/prompts/ with YAML frontmatter:
---
name: default
description: The default full-featured agent.
tools:
[
bash,
list_files,
read_file,
write_file,
replace_in_file,
search_files,
run_check,
git_diff,
download_url,
]
subagents: [feature-planner, tech-researcher, code-map, poet]
---
You are a helpful AI assistant capable of running bash commands and editing files...
The src/prompts/index.ts loader parses all .md files, extracts frontmatter, and builds Prompt objects:
export interface Prompt {
name: string;
description: string;
systemPrompt: string;
tools: any[];
}
export function getPrompt(name: string): Prompt {
if (!PROMPTS_CACHE) {
PROMPTS_CACHE = loadPrompts();
}
return PROMPTS_CACHE[name] || PROMPTS_CACHE.default;
}
Dynamic Tool Assignment
Each prompt specifies which tools it can access. Prompts that list subagents get an additional run_agent tool:
const promptTools = TOOLS.filter((t) => toolNames.includes(t.function.name));
if (subagentNames.length > 0) {
promptTools.push({
type: "function",
function: {
name: "run_agent",
description: "Delegate a task to a specialized subagent",
parameters: {
type: "object",
properties: {
agentName: {
type: "string",
enum: subagentNames,
description: "The name of the subagent to run",
},
task: {
type: "string",
description: "The task description for the subagent",
},
},
required: ["agentName", "task"],
},
},
});
}
Step 2: Implement Subagent Execution
The run_agent tool in executeTool calls agentRunner:
case "run_agent":
if (!agentRunner) throw new Error("Agent runner not provided");
return agentRunner(args.agentName, args.task);
The AgentRunner is implemented inside runTurn:
const agentRunner: AgentRunner = async (subAgentName: string, task: string) => {
console.log(`\n--- [Subagent Start] ${subAgentName} ---`);
console.log(`Task: ${task}\n`);
const prompt = getPrompt(subAgentName);
const subQuery: Message[] = [
{ role: "system", content: prompt.systemPrompt },
{ role: "user", content: task },
];
const subLogger = logger ? logger.child(subAgentName) : undefined;
const { messages } = await runTurn(
subQuery,
apiCaller,
toolExecutor,
prompt.tools,
subLogger,
subAgentName,
);
const lastMsg = messages[messages.length - 1];
console.log(`\n--- [Subagent End] ${subAgentName} ---`);
if (lastMsg.role === "assistant" && lastMsg.content) {
return lastMsg.content;
}
return "Subagent completed without specific output.";
};
This creates a nested runTurn call with the subagent’s prompt and tools. The result flows back into the parent agent’s context.
Step 3: Build the Code Map Agent
Paste:
Create a code explorer agent that is in the prompt directory. You will write out a very detailed prompt that can be run over and over again. The prompt looks through the entire codebase and documents it. The format should be structured and optimized for LLMs to read.
The agent creates src/prompts/code-map.md — a specialized agent that:
- Explores directory structure with
list_files - Reads every source and test file with
read_file - Analyzes architecture, module relationships, types
- Identifies security issues, test gaps, code quality problems
- Writes a comprehensive
docs/YYYY-MM-DD-code-map.mdfile
The output format is specifically designed for LLM consumption:
---
type: code-map
date: 2025-04-15
---
# Codebase Map
## Summary
[2 sentence summary]
## Architecture
[High-level explanation]
## Main Modules
- **Agent Core**: Handles conversation loop and subagent delegation
- **Tools**: Filesystem, system, search, web, git operations
## Critical Issues (Priority Order)
1. **Security**: bash tool allows arbitrary command execution
2. **Data Integrity**: replace_in_file reads entire file into memory
3. **Usability**: search_files clips at 50 matches
## File Map
### src/agent.ts
- **Methods**:
- `runTurn(history, apiCaller, toolExecutor, tools, logger, agentName): TurnResult`
- **Dependencies**: lib/api.ts, tools/index.ts
Step 4: Integrate Everything
Now wire it all together:
Integrate the prompt system with the subagent system, and make sure that the list of prompts, subagents, and tools is dynamically specified for each prompt.
Step 5: Verify Subagent Logging
examine the log system and make sure that entries are logged immediately after response, and that we are correctly tracking how subagent logs map to the parent agent
The child() method on SessionLogger creates a new session with parentId linking back to the parent:
// Parent message
{"sessionId":"abc123","parentId":null,"agentName":"main","message":{...}}
// Subagent messages (same file, different sessionId, parentId=abc123)
{"sessionId":"def456","parentId":"abc123","agentName":"code-map","message":{...}}
{"sessionId":"def456","parentId":"abc123","agentName":"code-map","message":{...}}
Step 6: The Full Prompt Arsenal
By the end of this chapter, you have these agents:
| Agent | File | Tools | Purpose |
|---|---|---|---|
| default | default.md | bash, files, search, check, diff, download | General coding |
| code-map | code-map.md | list_files, read_file, search_files, write_file | Document codebase |
| tech-researcher | tech-researcher.md | search, download, tavily, ask_user, write_file | Web research |
| feature-planner | feature-planner.md | list_files, read_file, search_files, write_file | Plan features |
| poet | poet.md | ask_user | Interactive poem writing |
The Poet — Human-in-the-Loop Test
The poet agent demonstrates subagent→user interaction:
---
name: poet
description: A subagent that asks for your name and a color, then writes a poem.
tools: [ask_user]
---
You are a poetic assistant. Your goal is to write a poem for the user.
You MUST follow this EXACT sequence:
1. CALL the `ask_user` tool with the question "What is your name?".
2. After receiving the name, CALL the `ask_user` tool with the question "What is your favorite color?".
3. ONLY AFTER you have both the name and the color, write a short poem.
4. You MUST use the `ask_user` tool. Do not guess or make up a name or color.
5. Provide the final poem as your response.
Test it:
Can you write me a poem?
The Agent Tree
When the default agent spawns subagents, the structure looks like:
main (default prompt, all tools)
├── code-map (explore-only tools)
│ └── writes docs/2025-04-15-code-map.md
├── tech-researcher (search tools)
│ └── writes reports/2025-04-15-topic-researcher.md
├── feature-planner (read-only tools)
│ └── writes plan.md
└── poet (ask_user only)
└── returns poem text
Each branch runs independently. The parent agent collects results and continues the conversation.