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.
From deepagents-buildernpx claudepluginhub spulido99/claude-toolkit --plugin deepagents-builderThis skill uses the workspace's default tool permissions.
references/anti-patterns.mdreferences/api-cheatsheet.mdreferences/prompt-patterns.mdreferences/security-patterns.mdreferences/tool-patterns.mdDesigns and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Effective patterns for system prompts, tools, and common anti-patterns to avoid.
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 memoryBoth 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 contextUse 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], # Invisible to LLM
) -> 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.", # Static role
context_schema=AgentContext, # Dynamic context
)
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_schemais 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.
Note: The
system_promptkey in the example config dicts below maps to thesystem_prompt=parameter when callingcreate_deep_agent.
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"""
}
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"""
}
Delegates, doesn't execute. Uses subagent dicts — the native pattern for create_deep_agent:
from deepagents import create_deep_agent
# Coordinator uses subagent dicts for delegation
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.",
},
],
)
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(), # Required for interrupt_on
)
# Use thread_id for conversation persistence
config = {"configurable": {"thread_id": "session-1"}}
result = agent.invoke({"messages": [...]}, config)
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={
"tool": {
"allowed_decisions": ["approve", "reject", "modify"],
}
},
)
# Agent pauses before sensitive tools, awaits human decision
config = {"configurable": {"thread_id": "session-1"}}
for event in agent.stream({"messages": [...]}, config, stream_mode="values"):
if "__interrupt__" in event:
# Review and approve or reject
decision = input("Approve? (approve/reject): ")
agent.invoke(None, config) # Resume execution
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.
Use snake_case for tool names:
@tool
def search_knowledge_base(query: str) -> list[dict]:
"""Search customer support knowledge base."""
pass
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
# Bad: user_id as parameter (security risk)
@tool
def get_account_bad(user_id: str) -> str:
"""Insecure: user_id exposed to LLM."""
pass
# Good: user_id from ToolRuntime context
@tool
def get_account_info(
runtime: ToolRuntime[SecureContext], # Invisible to LLM
) -> str:
"""Get account info using secure runtime context."""
user_id = runtime.context.user_id
return fetch_from_db(user_id)
# Create agent with context schema
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,
)
# Invoke with context (not visible to LLM)
result = agent.invoke(
{"messages": [...]},
context=SecureContext(user_id="user_123", api_key=os.environ["SERVICE_API_KEY"]),
)
@tool
def process_refund(
amount: float, # Required, with units implied
reason: str = "customer_request" # Optional with default
) -> dict:
"""Process customer refund.
Args:
amount: Refund amount in USD
reason: Reason for refund
Returns:
Refund confirmation with processing time
"""
pass
Always return structured data:
return {
"status": "success",
"data": {...},
"metadata": {"processing_time": 0.5}
}
Custom tools should be atomic primitives, not workflow bundles. Let the agent compose them.
@tool
def handle_customer_request(request: str) -> str:
"""Analyzes request, routes to department, executes action, sends response."""
# Decision logic buried in tool—agent can't adapt
category = analyze(request)
if category == "billing":
return billing_workflow(request)
elif category == "support":
return support_workflow(request)
# Agent has no visibility or control
@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."""
# Agent composes: classify -> get_articles -> formulate answer -> send
# Agent can skip steps, retry failures, or handle edge cases creatively
Preserve atomic tools alongside domain-specific conveniences:
tools = [
query_database, # Atomic: any query
insert_record, # Atomic: any table
update_record, # Atomic: any update
# PLUS domain shortcuts
get_customer_orders, # Convenience: common query
create_support_ticket, # Convenience: common workflow
]
# Agent can use shortcuts for speed OR compose primitives for novel tasks
DeepAgents uses a secure-by-default model. Implement security at tool/context level:
ToolRuntime for secure context injectioninterrupt_on for destructive operationsWhen 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={
"tool": {"allowed_decisions": ["approve", "reject"]},
},
)
# Each user gets isolated context
result = agent.invoke(
{"messages": [...]},
context=UserContext(user_id="user_456", permissions=["read"]),
config={"configurable": {"thread_id": "user_456_session"}},
)
context_schema + ToolRuntime, never as tool parametersinterrupt_on configured for destructive operationsFor complete mitigation strategies (4 strategies), content validation, rate limiting, and audit logging implementations, see references/security-patterns.md.
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.
Before finalizing a prompt:
Before finalizing tools:
snake_case namingFor comprehensive patterns and examples:
interrupt_on flows, signal_task_complete assertions, escalation boundary scenariosUse /validate-agent to check for anti-patterns in your agent code.