1
1 Comment

The Tool Registry pattern that saved me €200/month in AI API costs

Working with multiple AI agents, I kept hitting the same problem: each agent was making duplicate tool calls, burning through my OpenAI budget like crazy.

The specific problem:

  • Agent A searches "competitor analysis" → €0.15
  • Agent B searches "competitor analysis" → €0.15
  • Agent C searches "competitor analysis" → €0.15
  • Same query, 3x cost, same result

Multiply this by hundreds of daily tasks across multiple agents = budget nightmare.

The solution: Centralized Tool Registry with intelligent caching

Instead of each agent having its own tools, I built a shared registry that all agents access:

# Before: Scattered, duplicated tools
class MarketingAgent:
    def __init__(self):
        self.websearch = WebSearchTool()  # Each agent = new instance

class AnalysisAgent:
    def __init__(self):
        self.websearch = WebSearchTool()  # Duplicate!

# After: Shared registry with caching
@tool_registry.register("websearch")
class WebSearchTool:
    def __init__(self):
        self.cache = {}
        self.cache_ttl = 3600  # 1 hour
    
    async def execute(self, query: str):
        cache_key = hashlib.md5(query.encode()).hexdigest()
        
        if cache_key in self.cache:
            logger.info(f"Cache hit for: {query}")
            return self.cache[cache_key]
        
        # Only make API call if not cached
        result = await actual_web_search(query)
        self.cache[cache_key] = result
        return result

The results that surprised me

Cost reduction: 60% fewer API calls overall
Speed improvement: Cached responses in 50ms vs 2-3s for fresh calls
Consistency: All agents see the same data for the same query

Real example from my logs:

  • Week before: 847 search API calls, €127 cost
  • Week after: 341 search API calls, €51 cost
  • Same workload, 60% savings

The architecture pattern

class ToolRegistry:
    def __init__(self):
        self._tools = {}
    
    def register(self, tool_name):
        def decorator(tool_class):
            self._tools[tool_name] = tool_class()
            return tool_class
        return decorator
    
    def get_tool(self, tool_name):
        return self._tools.get(tool_name)

# Global registry
tool_registry = ToolRegistry()

# Any agent can access any tool
search_tool = tool_registry.get_tool("websearch")
result = await search_tool.execute("market trends 2024")

Beyond cost savings: The unexpected benefits

1. Debugging becomes trivial
All tool calls go through one place. Easy to log, monitor, and debug.

2. Rate limiting is centralized
Instead of each agent hitting limits, the registry manages quotas intelligently.

3. Tool upgrades are instant
Update the WebSearchTool once, all agents automatically get the new version.

4. A/B testing tools
Want to test a new search provider? Easy to swap in the registry without touching agent code.

Implementation checklist

If you're building multi-agent systems, consider:

  • [ ] Centralize tool access through a registry pattern
  • [ ] Add intelligent caching for expensive operations
  • [ ] Implement rate limiting at the tool level, not agent level
  • [ ] Log everything for debugging and cost analysis
  • [ ] Version your tools for easy rollbacks

The gotcha that almost got me

Semantic caching is tricky. "competitor analysis" and "competitive landscape" should probably share a cache, but simple string matching misses this.

My solution: hash normalized queries (lowercased, stemmed, stop-words removed) for better cache hit rates.

Questions for the community

Are you building multi-agent systems? What patterns have you found for managing shared resources?

Have you hit similar cost optimization challenges with AI APIs? How did you solve them?

Anyone using more sophisticated caching strategies like vector similarity for semantic matching?


This pattern was one of many lessons learned building a production AI orchestration system. The full technical deep-dive covers 15 architectural principles that emerged from months of debugging and optimization.

on August 18, 2025
  1. 1

    This is a useful pattern. I would pair the registry/cache layer with a gateway-level ledger, because caching only answers "did we avoid this call?" It does not always answer "who paid for the calls that still happened?"

    For multi-agent SaaS, the cost record I want for every task is:

    • API key or project owner
    • tool/cache hit versus fresh call
    • selected model route
    • fallback chain
    • retry count
    • settlement bucket or budget bucket
    • hard stop that should have applied

    That is the angle we are taking with Tokens Forge: lower-cost routing matters, but the bigger win is making every model/tool call explainable after the fact. A shared tool registry cuts duplicate calls; a route ledger keeps the remaining spend accountable.