
Building Agents with Claude: When Flexibility Beats Structure

Building Agents with Claude: When Flexibility Beats Structure
Learn when to let Claude figure out the steps instead of defining them yourself. Discover how to build powerful agents with simple, combinable tools that handle unpredictable tasks.
Agents represent a shift from the structured workflows we've been working with. While workflows are perfect when you know the exact steps needed to complete a task, agents shine when you're not sure what those steps should be. Instead of defining a rigid sequence, you give Claude a goal and a set of tools, then let it figure out how to combine those tools to achieve the objective.
This flexibility makes agents attractive for building applications that need to handle varied, unpredictable tasks. You can create an agent once, ensure it works reasonably well, and then deploy it to solve a wide range of problems. However, this flexibility comes with trade-offs in reliability and cost that we'll explore later.
How Tools Make the Agent
The real power of agents lies in their ability to combine simple tools in unexpected ways. Consider a basic set of datetime tools:
get_current_datetime— Gets the current date and timeadd_duration_to_datetime— Adds time to a given dateset_reminder— Creates a reminder for a specific time
These tools seem simple individually, but Claude can chain them together to handle surprisingly complex requests:
Simple Request
User: "What's the time?"
Claude's approach: Call get_current_datetime
Result: "It's 2:47 PM"
Moderate Complexity
User: "What day of the week is it in 11 days?"
Claude's approach:
- Call
get_current_datetimeto get today's date - Call
add_duration_to_datetimewith 11 days - Extract the day of the week from the result
Result: "In 11 days it will be Thursday"
High Complexity
User: "Set a reminder for my gym session next Wednesday at 6 PM"
Claude's approach:
- Call
get_current_datetimeto get today's date - Call
add_duration_to_datetimeto find next Wednesday - Call
set_reminderwith the calculated date and time
Result: "Reminder set for Wednesday, May 3rd at 6:00 PM"
Handling Missing Information
Claude can even recognize when it needs more information. If you ask "When does my 90-day warranty expire?", it knows to ask when you purchased the item before calculating the expiration date.
Claude's response: "I'd be happy to calculate that. When did you purchase the item?"
User: "March 15th"
Claude's approach:
- Parse the purchase date
- Call
add_duration_to_datetimewith 90 days - Return the expiration date
Result: "Your 90-day warranty expires on June 13th"
Tools Should Be Abstract
The key insight for building effective agents is providing reasonably abstract tools rather than hyper-specialized ones. Claude Code demonstrates this principle perfectly.
What Claude Code Has
Claude Code has access to generic, flexible tools like:
bash— Run any commandread— Read any filewrite— Create any fileedit— Modify filesglob— Find filesgrep— Search file contents
What Claude Code Doesn't Have
It notably doesn't have specialized tools like:
- ❌ "refactor code"
- ❌ "install dependencies"
- ❌ "run tests"
- ❌ "deploy to production"
- ❌ "generate documentation"
Instead, Claude figures out how to use the basic tools to accomplish these complex tasks.
Example: Installing Dependencies
When you ask Claude Code to "install the dependencies for this project," it doesn't call an install_dependencies tool. Instead, it:
- Uses
readto check forpackage.json,requirements.txt, orGemfile - Uses
bashto runnpm install,pip install -r requirements.txt, orbundle install - Uses
readto verify the installation succeeded
This abstraction allows it to handle countless programming scenarios that the developers never explicitly planned for.
Best Practice: Combinable Tools
When designing agents, provide tools that Claude can combine in creative ways. For example, a social media video agent might include:
Tool Set
bash— Access to FFMPEG for video processinggenerate_image— Create images from promptstext_to_speech— Convert text to audiopost_media— Upload content to social platforms
Simple Workflow
User: "Create and post a video about coffee brewing"
Claude's approach:
- Generate script
- Call
text_to_speechto create voiceover - Call
generate_imagefor visuals - Call
bashwith FFMPEG to combine audio and images - Call
post_mediato upload
Interactive Workflow
User: "Create a video about coffee brewing, but show me the thumbnail first"
Claude's approach:
- Generate script
- Call
generate_imagefor thumbnail - Show thumbnail to user and wait for approval
- If approved, proceed with
text_to_speechand video creation - Call
post_mediato upload
The agent can adapt its approach based on user feedback and preferences, something that would be difficult to achieve with a rigid workflow. This flexibility is what makes agents powerful for building dynamic, user-responsive applications.
Real-World Example: Research Agent
Let's build a research agent with these tools:
Tool Set
web_search— Search the web for informationread_url— Fetch and parse a webpagesave_note— Save information to a noteask_user— Request clarification from the user
User Request
"Research the latest developments in quantum computing and summarize the key breakthroughs"
Agent's Approach (Emergent, Not Scripted)
-
Initial search:
- Call
web_search("latest quantum computing breakthroughs 2026") - Get list of relevant URLs
- Call
-
Evaluate sources:
- Call
read_urlon the top 3 results - Identify which sources are most authoritative
- Call
-
Deep dive:
- Call
web_searchfor specific topics mentioned (e.g., "quantum error correction 2026") - Call
read_urlon additional sources
- Call
-
Clarify scope:
- Call
ask_user("I found breakthroughs in error correction, quantum networking, and new qubit designs. Which areas interest you most?") - Adjust research based on response
- Call
-
Synthesize findings:
- Call
save_notewith organized summary - Present findings to user
- Call
The key: You didn't script these exact steps. Claude figured out this approach based on the goal and available tools.
Designing Effective Agent Tools
✅ Good Tool Design
Abstract and composable:
@tool
def execute_sql(query: str) -> str:
"""Execute a SQL query and return results"""
# Implementation
This single tool can:
- Fetch data
- Update records
- Create tables
- Analyze data
- Generate reports
Why it works: Claude can construct any SQL query needed for the task.
❌ Poor Tool Design
Too specific:
@tool
def get_user_by_email(email: str) -> dict:
"""Get user record by email"""
# Implementation
@tool
def get_user_by_id(user_id: int) -> dict:
"""Get user record by ID"""
# Implementation
@tool
def get_users_by_signup_date(date: str) -> list:
"""Get users who signed up on a specific date"""
# Implementation
Why it's problematic:
- You need a new tool for every query pattern
- Claude can't handle unexpected queries
- Maintenance nightmare as requirements grow
Better approach: One execute_sql tool that handles all cases.
Tool Design Principles
1. Favor Primitives Over Composites
Primitive (good):
read_filewrite_filelist_directory
Composite (avoid):
backup_and_restore_filessync_directories
Claude can build composites from primitives, but not vice versa.
2. Make Tools Stateless
Stateless (good):
@tool
def search_products(query: str, filters: dict) -> list:
"""Search products with filters"""
# Implementation
Stateful (avoid):
@tool
def set_search_filter(key: str, value: str):
"""Set a search filter for subsequent searches"""
# Stores state between calls
Stateless tools are easier to reason about and combine.
3. Provide Clear Error Messages
@tool
def send_email(to: str, subject: str, body: str) -> dict:
"""Send an email
Returns:
{"status": "sent", "message_id": "..."}
Raises:
ValueError: If email address is invalid
PermissionError: If sender is not authorized
RateLimitError: If rate limit exceeded
"""
# Implementation
Claude uses error messages to adjust its approach.
4. Include Usage Examples
@tool
def format_date(date_string: str, format: str) -> str:
"""Format a date string
Args:
date_string: Date in ISO format (e.g., "2026-04-26")
format: Output format (e.g., "%B %d, %Y" for "April 26, 2026")
Examples:
format_date("2026-04-26", "%B %d, %Y") -> "April 26, 2026"
format_date("2026-04-26", "%m/%d/%y") -> "04/26/26"
"""
# Implementation
Examples help Claude understand how to use the tool correctly.
Agent vs. Workflow: A Side-by-Side Comparison
Scenario: Generate a Blog Post
Workflow Approach:
Step 1: Research topic (web_search)
Step 2: Create outline (call Claude)
Step 3: Write draft (call Claude)
Step 4: Add images (generate_image)
Step 5: Format for CMS (call Claude)
Step 6: Publish (post_to_cms)
Pros:
- Predictable
- Easy to debug
- Consistent output
Cons:
- Can't adapt to unexpected needs
- Requires manual updates for new scenarios
Agent Approach:
Goal: "Create and publish a blog post about serverless architecture"
Tools:
- web_search
- generate_image
- call_claude (for writing)
- post_to_cms
- ask_user (for clarification)
Pros:
- Adapts to user feedback
- Handles edge cases gracefully
- Can ask clarifying questions
Cons:
- Less predictable
- Harder to debug
- May take unexpected paths
Trade-offs: When Agents Cost More
Agents are powerful, but they come with costs:
1. Token Usage
Workflow: Fixed token count per execution
Agent: Variable token count based on:
- How many tools it tries
- How many iterations it takes
- How much context it needs
Example:
- Workflow: 5,000 tokens every time
- Agent: 3,000 tokens (simple task) to 50,000 tokens (complex task)
2. Latency
Workflow: Predictable execution time
Agent: Variable latency based on:
- Number of tool calls
- Sequential vs. parallel execution
- User interaction requirements
3. Reliability
Workflow: Fails in predictable ways
Agent: Can fail in unexpected ways:
- Gets stuck in loops
- Calls wrong tools
- Misinterprets user intent
Making Agents More Reliable
1. Set Clear Boundaries
system_prompt = """
You are a customer support agent with access to billing and account tools.
Rules:
- Never access user data without explicit permission
- Always confirm before making changes to accounts
- Escalate to human if user is frustrated or issue is complex
- Maximum 5 tool calls per request (ask user if you need more)
"""
2. Implement Safety Checks
def execute_tool(tool_name: str, args: dict):
# Check if tool is allowed
if tool_name in RESTRICTED_TOOLS:
if not user_has_permission(current_user, tool_name):
raise PermissionError(f"Not authorized to use {tool_name}")
# Execute with timeout
result = run_with_timeout(tool_name, args, timeout=30)
# Log for audit
log_tool_execution(tool_name, args, result)
return result
3. Add Guardrails
# Limit iterations
max_iterations = 10
for i in range(max_iterations):
action = agent.next_action()
if action.type == "finish":
break
execute_action(action)
else:
raise RuntimeError("Agent exceeded maximum iterations")
4. Monitor and Alert
# Track agent behavior
metrics = {
"tool_calls": count_tool_calls(),
"tokens_used": count_tokens(),
"execution_time": measure_time(),
"success_rate": calculate_success_rate()
}
# Alert if anomalies detected
if metrics["tool_calls"] > 20:
alert("Agent making excessive tool calls")
When to Use Agents
Use agents when:
✅ The task is open-ended — "Research this topic" vs. "Fetch these 3 specific data points"
✅ User needs vary widely — Customer support, personal assistants, research tools
✅ You want to handle edge cases gracefully — Agent can adapt instead of failing
✅ Interactive refinement is valuable — Agent can ask clarifying questions
✅ You have good tools — Abstract, composable tools that Claude can combine creatively
When to Use Workflows
Use workflows when:
✅ The task is well-defined — You know exactly what needs to happen
✅ Consistency is critical — Same input should always produce same output
✅ Cost control matters — Predictable token usage and latency
✅ Debugging is important — Easier to trace through fixed steps
✅ Compliance is required — Auditable, deterministic processes
Conclusion
Agents are powerful when you need flexibility and can tolerate some unpredictability. The key to building effective agents is providing simple, abstract, combinable tools that Claude can use creatively to solve problems you never explicitly planned for.
The agent mindset:
- Give Claude a goal, not a script
- Provide primitives, not composites
- Let Claude figure out the steps
- Add guardrails to prevent runaway behavior
- Monitor and iterate based on real usage
Start with a small set of well-designed tools. Test your agent with diverse requests. Add safety checks. Monitor its behavior. Over time, you'll develop intuition for when agents shine and when workflows are the better choice.
What agents are you building with Claude? Share your tool designs and lessons learned in the comments or join the discussion on the Anablock community forum.
Want to dive deeper into Claude development patterns? Check out our other guides:
Related Articles


