Tool Simplification Pattern: Reducing AI Cognitive Load
When building AI agents, we give them tools. The intuition is: more flexible tools = more capable agent.
This is wrong.
We found that flexible tools with many optional parameters actually hurt agent performance. The fix: encode decisions in tool names, not parameters.
Result: 71% reduction in decision-making burden and near-zero incorrect tool usage.
The Problem: Parameter Overload
AI agents face a hidden cost with powerful, flexible tools: every optional parameter is a decision point.
Consider this tool:
conversation_history_query({
order?: "asc" | "desc", // Decision 1
limit?: number, // Decision 2
offset?: number, // Decision 3
role?: string, // Decision 4
afterDate?: string, // Decision 5
beforeDate?: string // Decision 6
})
Every time the agent calls this tool, it must make 6 decisions. Multiply this across a conversation, and you get:
- Cognitive overhead: Decisions compound
- Incorrect usage: Agent copies parameters from history without understanding context
- Unclear intent: Same tool serves multiple purposes
- Self-reinforcing errors: Incorrect usage gets saved in history, agent mimics it
Real Example
We observed our agent copying subjectId from previous tool calls. User asks "Find discussions about authentication" → agent searches only one subject because it copied a subjectId from an earlier call.
The user wanted a full search. The agent's history poisoned its behavior.
The Solution: Tool Name = Decision
"The tool name is the decision, not the parameters."
Instead of one flexible tool, create multiple focused tools where the name expresses intent.
Before: One Flexible Tool
doThing({ mode?: "list" | "search" | "filter", ...options })
Agent thinks: "Which mode? Which options? What combination?"
After: Multiple Focused Tools
list_things() // Clear: I want a list
search_things({ query }) // Clear: I want to search
filter_things({ criteria }) // Clear: I want to filter
Agent thinks: "I want to search" → uses search_things.
The hardest decision (what kind of operation?) is now encoded in the tool name.
Before/After: Semantic Search
This pattern solved our history-copying bug.
Before (5 Parameters)
conversation_semantic_search({
query: string,
subjectId?: string, // Agent copies from history
messageId?: string, // Agent copies from history
limit?: number,
threshold?: number,
})
Agent sees subjectId in previous calls, copies it even when user wants full search.
After (2 Focused Tools)
// Default: search everything
search_messages({
query: string,
limit?: number = 5,
threshold?: number = 0.3,
})
// Explicit: search specific subject
search_subject_messages({
query: string,
subjectId: string, // Required, not optional
limit?: number = 5,
threshold?: number = 0.3,
})
Now:
- Default is unscoped: Can't accidentally filter
- Scoped version requires explicit ID: Forces intentionality
- No parameter to copy:
search_messagesdoesn't acceptsubjectId
Scenario Analysis
We measured decision reduction across 6 common scenarios:
| Scenario | Before | After | Reduction |
|---|---|---|---|
| Recent messages | 6 decisions | 1 | 83% |
| User messages yesterday | 6 | 2 | 67% |
| Find discussions | 4 | 1 | 75% |
| Search in subject | 5 | 2 | 60% |
| Assistant last night | 7 | 2 | 71% |
| Average | 5.6 | 1.5 | 71% |
Design Guidelines
When to Split a Tool
1. Optional parameters change behavior
// Bad: mode changes what tool does
doThing({ mode?: "list" | "search" })
// Good: separate tools
list_things()
search_things({ query })
2. More than 3 optional parameters
- 0-1 params: Perfect
- 2-3 params: Good
- 4-5 params: Warning
- 6+ params: Split it
3. Parameters get copied from history
If you observe the agent copying values it shouldn't, split the tool so the problematic parameter doesn't exist in the default version.
How to Name Tools
Use action verb + scope + filter:
browse_messages() // No filter
browse_messages_by_role() // Filter: role
browse_messages_by_date() // Filter: date
browse_messages_by_role_and_date() // Filter: both
The pattern:
do_thing()= base actiondo_thing_by_X()= filtered by Xdo_thing_by_X_and_Y()= filtered by both
Parameter Design
Make parameters required when:
- Tool can't function without them
- Value must be intentionally provided
- You want to prevent accidental omission
Make parameters optional when:
- There's a sensible default
- It fine-tunes but isn't essential
Always provide sensible defaults:
// Bad: agent must decide every time
browse_messages({ limit: number, order: "asc" | "desc" })
// Good: sensible defaults
browse_messages({
limit?: number = 5,
order?: "asc" | "desc" = "asc"
})
Anti-Patterns
The Swiss Army Knife
// Bad: one tool does everything
manage_conversation({
action: "list" | "search" | "filter" | "delete",
target?: "messages" | "subjects",
filters?: { ... },
options?: { ... }
})
The Mode Parameter
// Bad: mode is manual tool selection
do_thing({ mode: "list" | "search" | "filter" })
// Good: mode IS the tool name
list_things()
search_things()
filter_things()
The Context Polluter
// Bad: optional IDs get copied
search({
query: string,
contextId?: string, // Gets copied from history
})
// Good: scoped version is separate
search({ query })
search_in_context({ query, contextId }) // ID required
Results
On our conversation history system:
Before:
- 5 complex tools
- 3-6 parameters per tool
- 5.6 decisions per call average
- Frequent parameter copying bugs
After:
- 9 focused tools
- 0-3 parameters per tool
- 1.5 decisions per call average
- Zero parameter copying issues
Impact:
- 71% reduction in decision burden
- Zero accidental filtering
- Self-documenting API (tool names explain themselves)
Quick Reference
Tool Naming
| Pattern | Example | Use |
|---|---|---|
action_things() | list_subjects() | Get all |
action_things_by_X() | browse_messages_by_role() | Filter by one |
action_scoped_thing() | search_subject_messages() | Specific scope |
Red Flags
- More than 3 optional parameters
- Mode/type parameter that changes behavior
- Boolean flags that change what tool does
- Parameters frequently copied from history
- Unclear what happens when parameter omitted
Takeaways
- Fewer decisions = better results
- Tool name = intent (not parameters)
- Required beats optional for intentional params
- Split when in doubt: Multiple simple > one complex
- Measure decision count as a design metric
The goal: tools that are easy to use correctly and hard to use incorrectly.
Building AI agents that need reliable tool use? Let's talk.