When this skill is activated, always start your first response with the 🧢 emoji.
AI Agent Design
AI agents are autonomous LLM-powered systems that perceive their environment, decide on actions, execute tools, observe outcomes, and iterate toward a goal. Effective agent design requires deliberate choices about the loop structure, tool schemas, memory strategy, failure modes, and evaluation methodology.
When to use this skill
Trigger this skill when the user:
- Designs or implements an agent loop (ReAct, plan-and-execute, reflection)
- Defines tool schemas for LLM function-calling
- Builds multi-agent systems with orchestration (sequential, parallel, hierarchical)
- Implements agent memory (working, episodic, semantic)
- Applies planning strategies like chain-of-thought or task decomposition
- Adds safety guardrails, max-iteration limits, or human-in-the-loop gates
- Evaluates agent behavior, trajectory quality, or task success
- Debugs an agent that loops, hallucinates tools, or gets stuck
Do NOT trigger this skill for:
- Framework-specific agent APIs (use the Mastra or a2a-protocol skill instead)
- Pure LLM prompt engineering with no tool use or autonomy involved
Key principles
-
Tools over knowledge - agents should act through tools, not hallucinate facts. Every external lookup, write, or side effect belongs in a tool.
-
Constrain agent scope - give each agent a narrow, well-defined goal. A focused agent with 3 tools outperforms a general agent with 20.
-
Plan-act-observe loop - structure the core loop as: generate a plan, execute one action, observe the result, update the plan. Never batch unobserved actions.
-
Fail gracefully with max iterations - every agent loop must have a hard ceiling on steps. When the limit is hit, return a partial result with a clear error message - never loop indefinitely.
-
Evaluate agent behavior not just output - measure trajectory quality (tool selection accuracy, step efficiency), not only final answer correctness. A correct answer reached via a broken path will fail in production.
Core concepts
Agent loop anatomy
User Input
|
v
[ Planner / Reasoner ] <---- working memory + observations
|
v
[ Action Selection ] ----> tool call OR final answer
|
v
[ Tool Execution ]
|
v
[ Observation ] ----> append to context, loop back
The loop terminates when: (a) the agent produces a final answer, (b) max iterations is reached, or (c) an explicit stop condition triggers.
Tool schemas
Tools are the agent's interface to the world. Each tool needs:
- A precise, action-oriented
description(the LLM's primary signal) - A strict
inputSchema(validated before execution) - An
outputSchema(validated before returning to the agent) - Deterministic, idempotent behavior where possible
Planning strategies
| Strategy | When to use | Characteristics |
|---|---|---|
| ReAct | Interactive tasks with frequent tool use | Interleaves reasoning and acting; recovers from errors |
| Chain-of-thought (CoT) | Complex reasoning before a single action | Produces a scratchpad; no intermediate observations |
| Plan-and-execute | Long-horizon tasks with predictable subtasks | Upfront decomposition; each step is an independent mini-agent |
| Tree search (LATS) | Tasks where multiple solution paths exist | Explores branches; expensive but highest quality |
| Reflexion | Tasks requiring iterative self-improvement | Agent critiques its own output and retries |
Memory types
| Type | Scope | Storage | Use case |
|---|---|---|---|
| Working memory | Current run | In-context (string/JSON) | Current task state, scratchpad |
| Episodic memory | Per session | DB (keyed by thread/session) | Recall past interactions |
| Semantic memory | Cross-session | Vector store | Long-term knowledge retrieval |
| Procedural memory | Global | Prompt / fine-tune | Baked-in skills and habits |
Multi-agent topologies
| Topology | Structure | Best for |
|---|---|---|
| Sequential | A -> B -> C | Pipelines where each step builds on the last |
| Parallel | A, B, C run concurrently, results merged | Independent subtasks (research, drafting, validation) |
| Hierarchical | Orchestrator -> worker agents | Complex tasks requiring delegation and synthesis |
| Debate | Multiple agents argue, judge decides | High-stakes decisions needing diverse perspectives |
Common tasks
1. Build a ReAct agent loop
interface Tool {
name: string
description: string
execute: (input: unknown) => Promise<unknown>
}
interface AgentStep {
thought: string
action: string
actionInput: unknown
observation: string
}
async function reactAgent(
goal: string,
tools: Tool[],
llm: (prompt: string) => Promise<string>,
maxIterations = 10,
): Promise<string> {
const toolMap = Object.fromEntries(tools.map(t => [t.name, t]))
const toolDescriptions = tools
.map(t => `- ${t.name}: ${t.description}`)
.join('\n')
const history: AgentStep[] = []
for (let i = 0; i < maxIterations; i++) {
const context = history
.map(s => `Thought: ${s.thought}\nAction: ${s.action}[${JSON.stringify(s.actionInput)}]\nObservation: ${s.observation}`)
.join('\n')
const prompt = `You are an agent. Available tools:\n${toolDescriptions}\n\nGoal: ${goal}\n\n${context}\n\nThought:`
const response = await llm(prompt)
if (response.includes('Final Answer:')) {
return response.split('Final Answer:')[1].trim()
}
const actionMatch = response.match(/Action: (\w+)\[(.*)\]/s)
if (!actionMatch) break
const [, actionName, rawInput] = actionMatch
const tool = toolMap[actionName]
if (!tool) {
history.push({ thought: response, action: actionName, actionInput: rawInput, observation: `Error: tool "${actionName}" not found` })
continue
}
let input: unknown
try { input = JSON.parse(rawInput) } catch { input = rawInput }
const observation = await tool.execute(input)
history.push({ thought: response, action: actionName, actionInput: input, observation: JSON.stringify(observation) })
}
return `Max iterations (${maxIterations}) reached. Last state: ${JSON.stringify(history.at(-1))}`
}
2. Define tool schemas
import { z } from 'zod'
// Input and output schemas are the contract between the LLM and your system.
// Keep descriptions action-oriented and specific.
const searchWebSchema = {
name: 'search_web',
description: 'Search the web for current information. Use for facts, news, or data not in training.',
inputSchema: z.object({
query: z.string().describe('Specific search query. Be precise - avoid vague terms.'),
maxResults: z.number().int().min(1).max(10).default(5).describe('Number of results to return'),
}),
outputSchema: z.object({
results: z.array(z.object({
title: z.string(),
url: z.string().url(),
snippet: z.string(),
})),
totalFound: z.number(),
}),
}
const writeFileSchema = {
name: 'write_file',
description: 'Write content to a file on disk. Overwrites if file exists.',
inputSchema: z.object({
path: z.string().describe('Absolute file path'),
content: z.string().describe('Full file content to write'),
encoding: z.enum(['utf-8', 'base64']).default('utf-8'),
}),
outputSchema: z.object({
success: z.boolean(),
bytesWritten: z.number(),
}),
}
3. Implement agent memory
interface WorkingMemory {
goal: string
completedSteps: string[]
currentPlan: string[]
facts: Record<string, string>
}
interface EpisodicStore {
save(sessionId: string, entry: { role: string; content: string }): Promise<void>
load(sessionId: string, limit?: number): Promise<Array<{ role: string; content: string }>>
}
class AgentMemory {
private working: WorkingMemory
private episodic: EpisodicStore
private sessionId: string
constructor(goa