Master multi-agent systems, MCP, plugins, and sophisticated architectures
Duration: 2 weeks Prerequisites: Parts 1-3
- Multi-Agent Systems
- Sub-Agent Spawning
- Model Context Protocol (MCP)
- Plugin Architecture
- Memory Systems
- Skills System
- Context Compression
- Hands-On Exercises
Single agent limitations:
- One context window (runs out of space)
- One focus area (can't multitask)
- One conversation thread (gets confused with parallel work)
Multi-agent benefits:
- Parallel execution (faster)
- Specialized agents (better quality)
- Isolated contexts (cleaner reasoning)
- Scalability (distribute work)
┌────────────────────────────────────────────────┐
│ Main Agent (Coordinator) │
│ "Build a web app with auth and payments" │
└─────────────┬──────────────────────────────────┘
│
┌──────┴──────┬────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Sub-Agent │ │ Sub-Agent │ │ Sub-Agent │
│ "Auth" │ │ "Payments" │ │ "UI" │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
[Tools] [Tools] [Tools]
Location: src/tools/AgentTool/builtInAgents.ts
| Agent Type | Purpose | Tools Available |
|---|---|---|
general-purpose |
Research, search, multi-step tasks | All tools |
Explore |
Fast codebase exploration | Read, Glob, Grep |
Plan |
Task planning | Read, Glob, Grep |
| Custom agents | User-defined specialized agents | User-specified |
Location: src/tools/AgentTool/AgentTool.tsx
// Main agent calls AgentTool to spawn sub-agent
{
type: "tool_use",
name: "Task",
input: {
subagent_type: "Explore",
prompt: "Find all API endpoints in the codebase",
description: "Exploring codebase for API endpoints"
}
}┌─────────────────────────────────────────────────┐
│ SUB-AGENT EXECUTION LIFECYCLE │
├─────────────────────────────────────────────────┤
│ │
│ 1. Main Agent calls Task tool │
│ input: {subagent_type, prompt} │
│ ↓ │
│ 2. AgentTool creates new QueryEngine │
│ - Fresh message history │
│ - Specialized tool subset │
│ - Independent context │
│ ↓ │
│ 3. Sub-agent executes autonomously │
│ - Calls tools │
│ - Reasons about task │
│ - Iterates until complete │
│ ↓ │
│ 4. Sub-agent returns final report │
│ - Summary of findings │
│ - Key results │
│ - Recommendations │
│ ↓ │
│ 5. Main agent receives report │
│ - Uses info to continue │
│ - May spawn more sub-agents │
│ │
└─────────────────────────────────────────────────┘
// AgentTool.tsx (simplified)
export const AgentTool = buildTool({
name: "Task",
async execute(input, context) {
// 1. Select agent definition
const agentDef = getAgentDefinition(input.subagent_type)
// 2. Configure sub-agent QueryEngine
const subAgentConfig: QueryEngineConfig = {
cwd: context.cwd,
tools: selectToolsForAgent(agentDef), // Subset of tools
initialMessages: [
{
role: 'user',
content: input.prompt
}
],
maxTurns: agentDef.maxTurns || 50,
customSystemPrompt: agentDef.systemPrompt
}
// 3. Run sub-agent
const result = await query(subAgentConfig)
// 4. Extract final message
const finalMessage = result.messages[result.messages.length - 1]
// 5. Return to main agent
return {
agentType: input.subagent_type,
result: finalMessage.content,
turnCount: result.turnCount,
cost: result.totalCost
}
}
})// Define a custom agent
type AgentDefinition = {
name: string
description: string
systemPrompt: string
availableTools: string[] // Tool names
maxTurns?: number
model?: string
}
// Example: CodeReviewer agent
const codeReviewerAgent: AgentDefinition = {
name: "code-reviewer",
description: "Reviews code for bugs, style, and best practices",
systemPrompt: `You are an expert code reviewer.
Focus on:
- Security vulnerabilities
- Performance issues
- Code style and readability
- Best practices`,
availableTools: [
"Read",
"Grep",
"Glob",
"AskUserQuestion"
],
maxTurns: 20
}Location: src/tools/TeamCreateTool/
// Main agent creates a team
{
type: "tool_use",
name: "TeamCreate",
input: {
teammates: [
{
name: "frontend",
prompt: "Build the React UI components",
tools: ["Read", "Write", "Edit"]
},
{
name: "backend",
prompt: "Create the API endpoints",
tools: ["Read", "Write", "Bash"]
},
{
name: "tests",
prompt: "Write integration tests",
tools: ["Read", "Write", "Bash"]
}
]
}
}
// All teammates execute IN PARALLEL
// Main agent coordinates their workMCP = Model Context Protocol
A standardized way for LLM applications to:
- Connect to external data sources
- Integrate third-party tools
- Access remote resources
- Share context across systems
Think of it as: USB for AI agents
┌──────────────────────────────────────┐
│ Claude Code (Client) │
└──────────────┬───────────────────────┘
│
┌──────┴──────┬────────────┬───────────┐
▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌─────────┐ ┌─────────┐
│ MCP Server │ │ MCP │ │ MCP │ │ MCP │
│ GitHub │ │ Notion │ │ Postgres│ │ ... │
└──────────────┘ └──────────┘ └─────────┘ └─────────┘
Provides: Provides: Provides: Provides:
- PRs - Pages - Queries - ...
- Issues - Blocks - Tables
- Repos - Databases - Rows
Location: src/services/mcp/
- Tools: Functions the agent can call
- Resources: Data the agent can read
- Prompts: Pre-defined prompt templates
// MCP Server exposes a tool
{
name: "github__create_issue",
description: "Create a GitHub issue",
inputSchema: {
type: "object",
properties: {
repo: {type: "string"},
title: {type: "string"},
body: {type: "string"}
}
}
}
// Claude Code discovers and uses it
{
type: "tool_use",
name: "mcp__github__create_issue",
input: {
repo: "myorg/myrepo",
title: "Bug: Login fails",
body: "Description of bug..."
}
}
// MCP client routes to GitHub server
// Server executes → Returns result
// Result flows back to Claude// MCP Server exposes resources
{
uri: "notion://databases/tasks",
name: "Task Database",
mimeType: "application/json"
}
// Claude Code reads resource
{
type: "tool_use",
name: "ReadMcpResource",
input: {
uri: "notion://databases/tasks"
}
}
// Returns: Task data from NotionLocation: ~/.config/claude-code/mcp-servers.json
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "ghp_..."
}
},
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "postgresql://..."
}
}
}
}From src/services/mcp/client.ts
// Connect to MCP server
async function connectToMCPServer(
config: McpServerConfig
): Promise<MCPServerConnection> {
// 1. Spawn server process
const process = spawn(config.command, config.args, {
env: config.env
})
// 2. Create stdio transport
const transport = new StdioClientTransport({
command: config.command,
args: config.args
})
// 3. Create MCP client
const client = new Client({
name: "claude-code",
version: "1.0.0"
}, {
capabilities: {
tools: {},
resources: {}
}
})
// 4. Connect
await client.connect(transport)
// 5. List available tools and resources
const tools = await client.listTools()
const resources = await client.listResources()
return {
name: config.name,
client,
tools,
resources
}
}Plugins extend Claude Code's functionality:
- Add custom tools
- Add custom commands
- Add custom UI components
- Hook into lifecycle events
my-plugin/
├── package.json
├── src/
│ ├── index.ts # Plugin entry point
│ ├── tools/ # Custom tools
│ ├── commands/ # Custom commands
│ └── hooks/ # Lifecycle hooks
└── README.md
type Plugin = {
name: string
version: string
// Initialization
initialize?: (context: PluginContext) => Promise<void>
// Provide tools
tools?: Tool[]
// Provide commands
commands?: Command[]
// Lifecycle hooks
hooks?: {
onToolUse?: (tool: string, input: unknown) => void
onQueryStart?: (query: string) => void
onQueryEnd?: (result: QueryResult) => void
}
}// my-plugin/src/index.ts
export default {
name: "my-plugin",
version: "1.0.0",
async initialize(context) {
console.log("Plugin initialized!")
},
tools: [
{
name: "MyCustomTool",
description: "Does something cool",
inputSchema: z.object({
input: z.string()
}),
async execute(input) {
return { result: "Cool!" }
}
}
],
commands: [
{
name: "/mycmd",
description: "My custom command",
async execute(args) {
console.log("Command executed!")
}
}
],
hooks: {
onToolUse(tool, input) {
console.log(`Tool used: ${tool}`)
}
}
}Location: src/utils/plugins/pluginLoader.ts
async function loadPlugins(): Promise<Plugin[]> {
const pluginDir = path.join(
getConfigDir(),
'plugins'
)
const pluginDirs = await fs.readdir(pluginDir)
const plugins = await Promise.all(
pluginDirs.map(async (dir) => {
const pluginPath = path.join(pluginDir, dir)
const plugin = await import(pluginPath)
return plugin.default
})
)
// Initialize plugins
for (const plugin of plugins) {
await plugin.initialize?.({
configDir: getConfigDir(),
workingDir: getCwd()
})
}
return plugins
}| Memory Type | Duration | Purpose | Storage |
|---|---|---|---|
| Short-term | Single conversation | Context window | In-memory |
| Session | Current session | Resume support | Disk (session file) |
| Long-term | Persistent across sessions | Facts, preferences | Disk (memdir) |
| Team memory | Shared across agents | Coordination | Shared state |
Location: src/memdir/
~/.config/claude-code/memory/
├── facts.md # General facts
├── preferences.md # User preferences
├── project-notes.md # Project-specific notes
└── custom/ # User-created memory files
Usage:
// Load memory into system prompt
const memoryContent = await loadMemoryPrompt()
systemPrompt += `\n\n## Memory\n${memoryContent}`
// Agent now "remembers" facts across sessionsExample memory content:
## Facts
- User prefers TypeScript over JavaScript
- Project uses React with Vite
- Tests use Vitest
- API base URL: https://api.example.com
## Preferences
- Code style: 2 space indentation
- No semicolons
- Use const over letLocation: src/utils/sessionStorage.ts
// Save session to disk
async function saveSession(messages: Message[]) {
const sessionPath = path.join(
getSessionDir(),
`session-${Date.now()}.json`
)
await fs.writeFile(
sessionPath,
JSON.stringify({
version: 1,
timestamp: Date.now(),
messages,
context: getSystemContext()
}, null, 2)
)
}
// Resume session
async function resumeSession(sessionId: string) {
const sessionPath = getSessionPath(sessionId)
const data = await fs.readFile(sessionPath, 'utf-8')
const session = JSON.parse(data)
return session.messages
}Location: src/utils/swarm/
// Shared memory across team agents
type TeamMemory = {
facts: Map<string, string> // Shared facts
progress: Map<string, Progress> // Each agent's progress
artifacts: Map<string, Artifact> // Shared outputs
}
// Agent A writes to team memory
teamMemory.facts.set("api-endpoint", "/api/users")
// Agent B reads from team memory
const endpoint = teamMemory.facts.get("api-endpoint")Skills are reusable workflows:
- Pre-defined sequences of actions
- Can be invoked like tools
- Combine multiple tools
- Encapsulate domain knowledge
type Skill = {
name: string
description: string
prompt: string // Instructions for the agent
tools?: string[] // Required tools
examples?: Example[] // Usage examples
}Location: src/skills/bundled/
| Skill | Purpose |
|---|---|
git-workflow |
Standard git operations |
code-review |
Review code changes |
test-generation |
Generate tests for code |
debug-assistant |
Help debug issues |
// User invokes skill
User: "/skill code-review"
// Skill expands to agent instructions
Agent receives prompt:
"""
You are performing a code review.
Steps:
1. Read the changed files (use git diff)
2. Check for:
- Bugs
- Security issues
- Performance problems
- Style violations
3. Provide feedback
Available tools: Read, Grep, Bash
"""// ~/.config/claude-code/skills/my-skill.json
{
"name": "deploy-app",
"description": "Deploy the application to production",
"prompt": `Deploy the application following these steps:
1. Run tests (npm test)
2. Build the app (npm run build)
3. Deploy to server (npm run deploy)
4. Verify deployment (curl health endpoint)
5. Report status
Be careful and ask for confirmation before deploying.`,
"tools": ["Bash", "Read", "AskUserQuestion"]
}Problem: Conversations grow too large
Turn 1: Read file (10KB)
Turn 2: Analyze code (5KB)
Turn 3: Read another file (10KB)
Turn 4: Make changes (8KB)
Turn 5: Test changes (50KB of test output)
Turn 6: Debug (20KB of logs)
Total: 103KB in context!
Solution: Compress old turns
Keep first N and last N turns, snip middle:
function snipCompress(
messages: Message[],
keepFirst: number,
keepLast: number
): Message[] {
if (messages.length <= keepFirst + keepLast) {
return messages
}
const first = messages.slice(0, keepFirst)
const last = messages.slice(-keepLast)
const snipped = messages.length - keepFirst - keepLast
return [
...first,
{
role: 'system',
content: `[Snipped ${snipped} turns]`
},
...last
]
}Use LLM to summarize old turns:
async function summarizeHistory(
messages: Message[]
): Promise<Message[]> {
// Extract old messages
const oldMessages = messages.slice(0, -10)
// Summarize with Claude
const summary = await claudeAPI.messages.create({
model: "claude-haiku-3-5", // Fast, cheap model
messages: [
{
role: 'user',
content: `Summarize this conversation in 200 words:\n\n${
JSON.stringify(oldMessages)
}`
}
]
})
// Replace old messages with summary
return [
{
role: 'system',
content: `Previous conversation summary:\n${summary.content}`
},
...messages.slice(-10) // Keep last 10
]
}Keep only relevant messages:
async function projectRelevant(
messages: Message[],
currentTask: string
): Promise<Message[]> {
// Score messages by relevance to current task
const scored = messages.map(msg => ({
message: msg,
score: calculateRelevance(msg, currentTask)
}))
// Sort by score
scored.sort((a, b) => b.score - a.score)
// Keep top 50%
return scored
.slice(0, Math.floor(scored.length / 2))
.map(s => s.message)
}Create a main agent that delegates to sub-agents:
// Main agent task: "Build a todo app"
// Sub-agents:
// - "database" - Set up database schema
// - "api" - Build REST API
// - "frontend" - Build React UI
// Your task: Implement the coordination logicBuild a simple MCP server:
// weather-mcp-server.ts
// Expose weather data as MCP tools and resources
Tools:
- get_weather(city: string)
- get_forecast(city: string, days: number)
Resources:
- weather://current/{city}
- weather://forecast/{city}Create a plugin that adds a custom tool:
// Plugin: Database query tool
// Tool: RunSQLQuery
// Input: SQL query string
// Output: Query results
// Safety: Only allow SELECT queriesBuild a memory system:
// Features:
// - Save facts
// - Retrieve facts
// - Persist to disk
// - Load on startup
class MemorySystem {
save(key: string, value: string): void
get(key: string): string | undefined
persist(): Promise<void>
load(): Promise<void>
}Implement a compression algorithm:
// Input: Long conversation (100 messages)
// Output: Compressed (20 messages)
// Preserve: Important context
// Remove: Redundant information✅ Multi-agent systems distribute work ✅ Sub-agents solve specialized tasks ✅ MCP integrates external tools/data ✅ Plugins extend functionality ✅ Memory provides persistence ✅ Skills encapsulate workflows ✅ Compression manages context limits
- Divide and conquer - Multiple agents > single agent for complex tasks
- MCP is powerful - Integrate any external system
- Plugins add flexibility - Users can extend the system
- Memory matters - Persistence across sessions is valuable
- Context is limited - Always compress when needed
Before Part 5, you should be able to:
- Spawn and coordinate sub-agents
- Integrate MCP servers
- Build a basic plugin
- Implement a memory system
- Compress conversation context
- Create reusable skills
In the final part, you'll:
- Build complete agent systems
- Solve real-world problems
- Apply all concepts learned
- Create production-ready agents
Continue to → Part 5: Practical Exercises
Last updated: March 31, 2026