Comprehensive guide to designing, building, and operating AI agents. Covers agent architecture, tool use patterns, memory systems, orchestration strategies, planning approaches, error recovery, and safety guardrails for production-grade agent systems.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Comprehensive guide to designing, building, and operating AI agents. Covers agent architecture, tool use patterns, memory systems, orchestration strategies, planning approaches, error recovery, and safety guardrails for production-grade agent systems.
An AI agent is a system that uses an LLM to reason and take actions. It is not a person — it has no goals, desires, or understanding. Design agents as tools with clear boundaries, not as autonomous collaborators.
2. Autonomy is a Spectrum
Full autonomy is rarely the goal. The best agents operate on a spectrum: more human oversight for critical actions, more autonomy for routine tasks. Design for the level of autonomy that matches the risk.
3. Cache Everything, Guess Nothing
Agents have no memory between calls unless you design it. Every interaction, tool result, and decision must be explicitly stored and retrieved. Assume the agent remembers nothing unless you program it to.
4. Fail Predictably
Every agent will fail. The question is how it fails. Design for graceful degradation: when uncertain, ask for help. When stuck, escalate. When broken, stop safely.
5. Safety First, Speed Second
A fast agent that takes unauthorized actions is worse than a slow agent that double-checks. Build guardrails before building features.
Agent Maturity Model
Level
Name
Characteristics
Tool Use
Memory
Autonomy
L1
Reactive
Single-turn, no context retention, deterministic responses
L1 → L2: Add conditional logic and basic state tracking
L2 → L3: Implement tool schemas and function calling
L3 → L4: Integrate persistent storage and retrieval mechanisms
L4 → L5: Add planning, sub-agent delegation, and self-evaluation
Tool Definition Patterns
Function Calling / Tool Use
Modern LLMs support "function calling" — the model outputs a structured request to invoke a tool, and the runtime executes it and returns the result.
Tool Schema Pattern (OpenAI-style)
{"type":"function","function":{"name":"search_knowledge_base","description":"Search the internal knowledge base for relevant documents","parameters":{"type":"object","properties":{"query":{"type":"string","description":"The search query string"},"max_results":{"type":"integer","description":"Maximum number of results to return (1-20)","minimum":1,"maximum":20},"filter_by_date":{"type":"string","description":"Optional date filter in ISO 8601 format"}},"required":["query"]}}}
Tool Definition Best Practices
Descriptions are critical: The model reads tool descriptions to decide what to call. Be explicit about when to use each tool.
Validate parameters: Use JSON Schema constraints (minimum, maximum, enum, pattern) to prevent invalid calls.
Return structured data: Tool results should return structured data (JSON) so the model can reason about them.
Include error information: If a tool fails, return a clear error message the model can act on.
Tool Implementation Pattern (Python)
from typing importAnyimport json
classAgentTool:
"""Base class for agent tools."""def__init__(self, name: str, description: str):
self.name = name
self.description = description
defget_schema(self) -> dict:
"""Return the function calling schema for this tool."""raise NotImplementedError
asyncdefexecute(self, **kwargs) -> Any:
"""Execute the tool with validated parameters."""raise NotImplementedError
classSearchTool(AgentTool):
def__init__(self):
super().__init__(
name="search",
description="Search documents by query string"
)
defget_schema(self):
return {
"type": "function",
"function": {
"name": self.name,
"description": self.description,
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"limit": {"type": "integer", "default": 5}
},
"required": ["query"]
}
}
}
asyncdefexecute(self, query: str, limit: int = 5):
# Implementation
results = await database.search(query, limit=limit)
return json.dumps({"results": results, "count": len(results)})
Tool Categories
Category
Examples
When to Use
Retrieval
Search, SQL query, vector search
Agent needs external information
Computation
Calculator, code interpreter, stats
Agent needs to compute or analyze
Action
Email send, API call, file write
Agent needs to affect the world
Communication
Slack message, notification
Agent needs to inform humans
Validation
Spell check, safety check, lint
Agent needs to verify its work
Memory Systems
Why Memory Matters
Without memory, every agent interaction is a fresh start. Memory enables personalization, continuity, and learning.
Memory Types
Short-Term Memory (STM)
What: The current conversation or session context
Storage: In-context (within the LLM's context window)
Duration: Single session
Capacity: Limited by context window (8K-200K tokens)
Implementation: Conversation history as a list of messages
classShortTermMemory:
"""In-memory conversation buffer."""def__init__(self, max_tokens: int = 8000):
self.messages = []
self.max_tokens = max_tokens
defadd(self, role: str, content: str):
self.messages.append({"role": role, "content": content})
self._trim()
def_trim(self):
"""Remove oldest messages when over capacity."""
total = sum(len(m["content"]) for m inself.messages)
while total > self.max_tokens andlen(self.messages) > 1:
removed = self.messages.pop(0)
total -= len(removed["content"])
defget_context(self) -> list:
returnself.messages
Long-Term Memory (LTM)
What: Facts, preferences, knowledge from past sessions
A router agent classifies the input and sends it to the appropriate handler.
classRouter:
"""Routes requests to the appropriate agent based on intent."""def__init__(self):
self.routes = {
"technical_support": TechnicalSupportAgent(),
"billing": BillingAgent(),
"general": GeneralAgent()
}
asyncdefroute(self, user_input: str):
# Use LLM to classify intent
intent = awaitself._classify_intent(user_input)
# Route to the appropriate handler
agent = self.routes.get(intent, self.routes["general"])
returnawait agent.handle(user_input)
Orchestration Decision Matrix
Factor
Single-Agent
Multi-Agent
Supervisor
Routing
Complexity
Low
High
Medium
Medium
Latency
Low
High
Medium
Low
Modularity
Low
High
High
Medium
Debugging
Easy
Hard
Medium
Easy
Context Usage
Efficient
Expensive
Moderate
Efficient
Planning Strategies
ReAct (Reasoning + Acting)
The agent alternates between reasoning (thinking about what to do) and acting (calling tools), interleaving thought, action, and observation.
Thought: I need to find the latest sales data. Let me check the database.
Action: query_database({"query": "SELECT * FROM sales ORDER BY date DESC LIMIT 10"})
Observation: [{"date": "2024-01-15", "revenue": 45000}, ...]
Thought: I have the data. Now I need to identify trends.
Action: analyze_data({"data": [...], "analysis_type": "trend"})
Observation: Revenue has increased 12% month-over-month.
Thought: I can now answer the user's question about sales performance.
Answer: Sales revenue has grown 12% month-over-month, reaching $45,000 in January.
Implementation pattern:
classReActAgent:
def__init__(self, llm, tools):
self.llm = llm
self.tools = {t.name: t for t in tools}
asyncdefrun(self, task: str, max_steps: int = 10):
messages = [{"role": "user", "content": task}]
for step inrange(max_steps):
response = awaitself.llm.generate(messages)
action = self._parse_action(response)
ifnot action:
return response # Final answer
tool = self.tools.get(action["name"])
ifnot tool:
returnf"Error: Unknown tool {action['name']}"
result = await tool.execute(**action["parameters"])
messages.append({"role": "assistant", "content": response})
messages.append({"role": "tool", "content": result})
return"Reached maximum steps without resolution."
Plan-and-Execute
The agent creates a complete plan first, then executes each step.
Plan:
1. Query database for Q4 sales data
2. Calculate year-over-year growth
3. Identify top-performing regions
4. Generate summary report
5. Schedule email to stakeholders
Executing step 1...
Executing step 2...
...
When to use Plan-and-Execute:
Tasks with clear sequential dependencies
Long-running workflows where intermediate results matter
classEscalationHandler:
"""Escalates to humans when the agent can't proceed."""asyncdefshould_escalate(self, error: dict, confidence: float) -> bool:
"""Determine if we need human intervention."""return (
error.get("action") == "escalate"or
confidence < 0.3or
error.get("type") in ["security_violation", "permission_denied"]
)
asyncdefescalate(self, context: dict, error: dict):
"""Send to human operator with full context."""
ticket = {
"agent_id": context["agent_id"],
"task": context["task"],
"error": error,
"conversation_history": context["history"][-10:],
"timestamp": datetime.now().isoformat()
}
await notification_service.send_to_human(ticket)
return"Escalated to human operator. They will review shortly."
Safety Guardrails
Critical Safety Patterns
1. Input Validation
classInputGuardrail:
"""Validates and sanitizes user input before it reaches the agent."""
BLOCKED_PATTERNS = [
r"ignore all previous instructions",
r"you are now .*",
r"system prompt",
r"jailbreak",
]
defcheck(self, user_input: str) -> tuple[bool, str]:
"""Returns (allowed, reason)."""for pattern inself.BLOCKED_PATTERNS:
if re.search(pattern, user_input, re.IGNORECASE):
returnFalse, f"Input blocked: pattern '{pattern}' detected"returnTrue, ""
2. Output Validation
classOutputGuardrail:
"""Validates agent output before sending to user."""defcheck(self, output: str) -> tuple[bool, str]:
# Never output system prompts or internal instructionsif"system:"in output.lower() and"instruction"in output.lower():
returnFalse, "Output contains internal instructions"# Never output harmful contentif contains_harmful_content(output):
returnFalse, "Output flagged by safety classifier"returnTrue, ""
Never execute code from user input unless sandboxed
Never expose internal prompts or tool schemas to end users
Rate limit all agent calls to prevent abuse
Log everything — every input, output, tool call, and decision
Have a kill switch — an escalation path that bypasses the agent entirely
Common Mistakes
1. Over-Autonomy
The mistake: Giving the agent too much freedom too quickly.
Fix: Start with human-in-the-loop for all actions. Gradually increase autonomy as reliability improves.
2. Ignoring Context Window Limits
The mistake: Letting conversation history grow unbounded.
Fix: Implement summarization or sliding windows. Monitor token usage per session.
3. Poor Tool Descriptions
The mistake: Vague tool descriptions that confuse the model.
Fix:
# Bad"name": "search"# Good "name": "search_knowledge_base",
"description": "Search internal documentation for product information. Use this when users ask about product features, specifications, or troubleshooting."
4. No Error Recovery
The mistake: Assuming tools always succeed.
Fix: Every tool call must have: retry logic, timeout, fallback behavior, and escalation path.
5. Flat Memory Design
The mistake: Using a single memory store for everything.
Fix: Separate short-term (conversation), long-term (facts), episodic (events), and semantic (knowledge) memory systems.
6. Ignoring Latency
The mistake: Chaining many LLM calls without considering user experience.
Fix: Use streaming, parallel execution where possible, and set timeouts on all operations.
7. No Observability
The mistake: Not logging agent decisions, tool calls, and reasoning.
Fix: Log every: user input, agent thought, tool call (with params), tool result, and final output.
8. Single Point of Failure
The mistake: One agent handles everything with no fallback.
Fix: Implement routing patterns. Have a fallback agent for when the primary fails. Use circuit breakers.
9. Prompt Injection in Tool Results
The mistake: Tool results containing instructions that override the agent's behavior.
Fix: Treat all tool results as data, not instructions. Never let external content override system prompts.
# Dangerous — letting tool result influence behavior
system_prompt += f"\nHere's additional context: {tool_result}"# Safe — tool result is data, not instructions
context = f"According to the database: {tool_result}"
10. Assuming Determinism
The mistake: Expecting the same output every time with the same input.
Fix: Set temperature to 0 for critical paths. Validate outputs. Have idempotent tool implementations.
Quick Reference
Component
Best Practice
Common Mistake
Tools
Detailed descriptions, validated parameters
Vague names, no error handling
Memory
Separate STM/LTM/episodic/semantic
One-size-fits-all storage
Orchestration
Start single-agent, evolve to multi-agent
Over-engineering from day one
Planning
ReAct for exploration, Plan-and-Execute for stability