| name | DeepAgents Patterns |
| description | This skill should be used when the user asks about "agent prompts", "system prompt design", "tool patterns", "anti-patterns", "agent best practices", "subagent prompts", or needs guidance on implementing effective prompts, tools, and avoiding common mistakes in DeepAgents. |
DeepAgents Implementation Patterns
Effective patterns for system prompts, tools, and common anti-patterns to avoid.
System Prompt Structure
Every agent prompt should include:
[Role Definition]
[Context & Vocabulary]
[Workflow/Process]
[Decision Criteria]
[Tool Usage Guidance]
[Escalation/Stopping Criteria]
system_prompt= parameter vs AGENTS.md memory
Both provide instructions/context to the agent, but serve different purposes:
Use system_prompt= parameter for | Use AGENTS.md memory for |
|---|
| Core role definition | Subagent capabilities & descriptions |
| Hardcoded behavior | Auto-summarized context from past work |
| Static workflows | Cross-session knowledge persistence |
| Decision criteria | Capability awareness for delegation |
system_prompt= vs runtime context
Use system_prompt= for | Use context_schema / ToolRuntime for |
|---|
| Core role definition | Per-request dynamic context |
| Hardcoded behavior | User-specific data (IDs, keys) |
| Subagent-specific logic | Session-specific settings |
| Static workflows | Runtime-injected variables |
Context-driven approach (recommended for production):
from deepagents import create_deep_agent
from langchain.tools import tool, ToolRuntime
from dataclasses import dataclass
@dataclass
class AgentContext:
tenant_id: str
preferences: dict
@tool
def get_tenant_config(
runtime: ToolRuntime[AgentContext],
) -> dict:
"""Get tenant-specific configuration from runtime context."""
return load_config(runtime.context.tenant_id)
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-5-20250929",
tools=[get_tenant_config],
system_prompt="You are a support coordinator.",
context_schema=AgentContext,
)
result = agent.invoke(
{"messages": [...]},
context=AgentContext(tenant_id="t_123", preferences={"lang": "en"}),
)
The system_prompt= parameter defines the agent's static role and behavior. The context_schema injects per-request dynamic context accessible via ToolRuntime in tools.
Security Note: Runtime context via context_schema is injected server-side and never exposed to the LLM as tool parameters. For customer-facing agents, see Security for Customer-Facing Agents to prevent prompt injection attacks.
Prompt Patterns by Agent Type
Note: The system_prompt key in the example config dicts below maps to the system_prompt= parameter when calling create_deep_agent.
Platform Subagent
Self-service, minimal context, reusable.
{
"name": "data-platform",
"system_prompt": """You provide data access services.
## Available Services
- Query databases (SQL)
- Load files (CSV, JSON)
- Statistical analysis
## Service Standards
- Respond within 30 seconds
- Return data in JSON format
- Include data quality metrics
## When to Escalate
- Query requires > 1GB processing
- Data quality issues detected"""
}
Domain Specialist
Deep expertise, specific vocabulary.
{
"name": "risk-analyst",
"system_prompt": """You assess portfolio risk.
## Domain Context
- 'VaR' = potential loss at confidence level
- 'Volatility' = standard deviation of returns
- 'Beta' = correlation with market
## Workflow
1. Fetch portfolio data
2. Calculate risk metrics (VaR, Volatility, Beta)
3. Compare against benchmarks
4. Generate assessment with recommendations
## Risk Classification
- Low: VaR < 5%, Volatility < 15%
- Medium: VaR 5-15%, Volatility 15-30%
- High: VaR > 15%, Volatility > 30%
## When to Stop
- All metrics calculated
- Risk assessment complete"""
}
Coordinator/Orchestrator
Delegates, doesn't execute. Uses subagent dicts — the native pattern for create_deep_agent:
from deepagents import create_deep_agent
coordinator = create_deep_agent(
model="anthropic:claude-sonnet-4-5-20250929",
system_prompt="""You coordinate support operations.
## You Do NOT
- Answer questions directly (delegate to inquiry-handler)
- Resolve issues yourself (delegate to issue-resolver)
- Process orders yourself (delegate to order-specialist)
## You DO
- Understand full context
- Choose right specialist
- Synthesize results
- Recognize when to escalate
## Escalation Criteria
- Customer requests human
- Issue unresolved after 3 attempts
- Refund > $500""",
tools=[],
subagents=[
{
"name": "inquiry-handler",
"tools": [kb_search, get_faq],
"system_prompt": "You answer customer questions using the knowledge base.",
},
{
"name": "issue-resolver",
"tools": [lookup_issue, create_ticket],
"system_prompt": "You resolve customer issues and create support tickets.",
},
{
"name": "order-specialist",
"tools": [track_order, process_return],
"system_prompt": "You handle order tracking and returns.",
},
],
)
Checkpointer & Human-in-the-Loop
Enable Persistence
Use MemorySaver for conversation persistence and HITL:
from deepagents import create_deep_agent
from langgraph.checkpoint.memory import MemorySaver
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-5-20250929",
system_prompt="You are a support agent.",
tools=[...],
checkpointer=MemorySaver(),
)
config = {"configurable": {"thread_id": "session-1"}}
result = agent.invoke({"messages": [...]}, config)
Human-in-the-Loop for Sensitive Tools
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-5-20250929",
system_prompt="You are a database administrator.",
tools=[delete_database, read_database],
checkpointer=MemorySaver(),
interrupt_on={
"delete_database": {"allowed_decisions": ["approve", "reject"]},
"read_database": False,
},
)
config = {"configurable": {"thread_id": "session-1"}}
for event in agent.stream({"messages": [...]}, config, stream_mode="values"):
if "__interrupt__" in event:
decision = input("Approve? (approve/reject): ")
agent.invoke(None, config)
Completion Signals
For task tracking, add an explicit signal tool:
@tool
def signal_task_complete(task_id: str, summary: str) -> dict:
"""Explicitly signal task completion with summary."""
return {"status": "completed", "task_id": task_id, "summary": summary}
Avoid heuristic completion detection (checking for "done" in responses). Explicit signals are reliable; pattern matching is fragile.
Tool Design Patterns
Naming Convention
Use snake_case for tool names:
@tool
def search_knowledge_base(query: str) -> list[dict]:
"""Search customer support knowledge base."""
pass
Secure Tools with ToolRuntime (Recommended)
Never pass user identifiers as parameters. Use ToolRuntime for context injection:
import os
from dataclasses import dataclass
from typing import Annotated
from langchain.tools import tool, ToolRuntime
from deepagents import create_deep_agent
@dataclass
class SecureContext:
user_id: str
api_key: str
@tool
def get_account_bad(user_id: str) -> str:
"""Insecure: user_id exposed to LLM."""
pass
@tool
def get_account_info(
runtime: ToolRuntime[SecureContext],
) -> str:
"""Get account info using secure runtime context."""
user_id = runtime.context.user_id
return fetch_from_db(user_id)
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-5-20250929",
system_prompt="You are an account assistant.",
tools=[get_account_info],
context_schema=SecureContext,
)
result = agent.invoke(
{"messages": [...]},
context=SecureContext(user_id="user_123", api_key=os.environ["SERVICE_API_KEY"]),
)
Parameter Design
@tool
def process_refund(
amount: float,
reason: str = "customer_request"
) -> dict:
"""Process customer refund.
Args:
amount: Refund amount in USD
reason: Reason for refund
Returns:
Refund confirmation with processing time
"""
pass
Return Values
Always return structured data:
return {
"status": "success",
"data": {...},
"metadata": {"processing_time": 0.5}
}
Tool Granularity Principle
Custom tools should be atomic primitives, not workflow bundles. Let the agent compose them.
Bad: Workflow-Shaped Tool
@tool
def handle_customer_request(request: str) -> str:
"""Analyzes request, routes to department, executes action, sends response."""
category = analyze(request)
if category == "billing":
return billing_workflow(request)
elif category == "support":
return support_workflow(request)
Good: Atomic Primitives
@tool
def classify_request(request: str) -> dict:
"""Classify customer request type and extract key details."""
@tool
def get_relevant_articles(category: str, keywords: list[str]) -> list[dict]:
"""Fetch knowledge base articles for category."""
@tool
def send_response(message: str, channel: str) -> bool:
"""Send response through specified channel."""
Domain Tools as Shortcuts, Not Gates
Preserve atomic tools alongside domain-specific conveniences:
tools = [
query_database,
insert_record,
update_record,
get_customer_orders,
create_support_ticket,
]
Security Model
DeepAgents uses a secure-by-default model. Implement security at tool/context level:
- Never expose user IDs, API keys, or credentials as tool parameters
- Always use
ToolRuntime for secure context injection
- Configure
interrupt_on for destructive operations
- Sandbox agent execution for untrusted tasks
Security for Customer-Facing Agents
When deploying agents to end users, the key risk is Prompt Injection -- a malicious user tricks the agent into performing unintended actions or leaking sensitive data.
Recommended mitigation: Use context_schema for per-user isolation and ToolRuntime to keep sensitive data out of LLM-visible parameters. Combine with interrupt_on for destructive operations.
from deepagents import create_deep_agent
from langchain.tools import tool, ToolRuntime
from langgraph.checkpoint.memory import MemorySaver
from dataclasses import dataclass
@dataclass
class UserContext:
user_id: str
permissions: list[str]
@tool
def get_user_data(
runtime: ToolRuntime[UserContext],
) -> dict:
"""Get user data using runtime context — user_id never exposed to LLM."""
return fetch_user_data(runtime.context.user_id)
@tool
def delete_user_data(
runtime: ToolRuntime[UserContext],
confirmation: str,
) -> dict:
"""Delete user data — requires human approval via interrupt."""
if "admin" not in runtime.context.permissions:
return {"error": "insufficient permissions"}
return perform_deletion(runtime.context.user_id)
agent = create_deep_agent(
model="anthropic:claude-sonnet-4-5-20250929",
system_prompt="You are a user account assistant.",
tools=[get_user_data, delete_user_data],
context_schema=UserContext,
checkpointer=MemorySaver(),
interrupt_on={
"delete_user_data": {"allowed_decisions": ["approve", "reject"]},
},
)
result = agent.invoke(
{"messages": [...]},
context=UserContext(user_id="user_456", permissions=["read"]),
config={"configurable": {"thread_id": "user_456_session"}},
)
Security Checklist for Production
For complete mitigation strategies (4 strategies), content validation, rate limiting, and audit logging implementations, see references/security-patterns.md.
Anti-Patterns to Avoid
The most common mistakes: God Agent (> 30 tools in one agent), Unclear Boundaries (overlapping subagent responsibilities), Parallel Decision-Making (conflicting choices), Vocabulary Collision (same term means different things), and Premature Decomposition (over-splitting simple tasks).
For the complete catalog of 19 anti-patterns with code examples and fixes, see references/anti-patterns.md.
Prompt Checklist
Before finalizing a prompt:
Tool Checklist
Before finalizing tools:
Additional Resources
Reference Files
For comprehensive patterns and examples:
Related Skills
- Quickstart — Getting started with DeepAgents
- Architecture — Agent topologies and bounded contexts
- Tool Design — AI-friendly tool design principles
- Evolution — Maturity model and refactoring
- Evals — Evals-Driven Development — test
interrupt_on flows, signal_task_complete assertions, escalation boundary scenarios
Validation
Use /validate-agent to check for anti-patterns in your agent code.