{"slug": "give-your-ai-agent-a-memory-so-it-stops-repeating-the-same-failed-tool-call", "title": "Give Your AI Agent a Memory So It Stops Repeating the Same Failed Tool Call", "summary": "A developer created MemoryGatekeeper, a Python class that gives AI agents a persistent memory of past tool-call failures using Mem0's local vector store. The gatekeeper checks semantic similarity against recorded failures before executing a tool call, blocking repeated mistakes like retrying a rate-limited API with the same arguments. The project aims to improve agent reliability by remembering lessons across sessions.", "body_md": "Your agent calls a flaky API, gets a 429, retries with the same arguments, and gets rate-limited again. Ten minutes later, in a fresh session, it does the exact same thing. Nothing about the failure got remembered — the agent has no way to know it already learned this lesson, because the lesson lived in a transcript that got thrown away when the process exited.\n\nThis week a post about manually gatekeeping AI agent tool calls hit the front page of dev.to with 48 comments — mostly developers arguing over how much you can trust an agent's tool-calling loop. The honest answer is: you can trust it exactly as much as it remembers what already went wrong. Below is a complete, runnable gatekeeper that wraps any tool call, checks Mem0 for similar past failures before executing, and records the outcome afterward — so the second time your agent is about to make the same mistake, it knows.\n\nA `MemoryGatekeeper`\n\nclass that sits between your agent's decision to call a tool and the actual execution:\n\nYou'll run the same script twice: the first run fails and gets recorded, the second run gets blocked before it wastes a request.\n\nYou need Python 3.10+, an OpenAI API key (Mem0's default extraction pipeline uses it to turn raw text into structured memories — no separate Mem0 account required for this local setup), and the `mem0ai`\n\npackage.\n\n```\npip install mem0ai\nexport OPENAI_API_KEY=sk-...\n```\n\nMem0 defaults to a local, on-disk vector store, so nothing here talks to a hosted Mem0 service — it's a fully self-contained memory layer you own.\n\n``` python\n# gatekeeper.py\nfrom mem0 import Memory\nfrom datetime import datetime, timezone\n\nclass MemoryGatekeeper:\n    def __init__(self, agent_id=\"default-agent\", block_threshold=0.75):\n        self.memory = Memory()\n        self.agent_id = agent_id\n        self.block_threshold = block_threshold\n\n    def _describe_call(self, tool_name, args):\n        return f\"tool call: {tool_name} with args {args}\"\n\n    def check(self, tool_name, args):\n        \"\"\"Returns (allowed: bool, reason: str | None).\"\"\"\n        query = self._describe_call(tool_name, args)\n        hits = self.memory.search(query, user_id=self.agent_id, limit=3)\n\n        for hit in hits.get(\"results\", []):\n            score = hit.get(\"score\", 0)\n            memory_text = hit.get(\"memory\", \"\")\n            if score >= self.block_threshold and \"failed\" in memory_text.lower():\n                return False, memory_text\n        return True, None\n\n    def record(self, tool_name, args, success, detail):\n        outcome = \"succeeded\" if success else \"failed\"\n        text = (\n            f\"{self._describe_call(tool_name, args)} {outcome} \"\n            f\"at {datetime.now(timezone.utc).isoformat()}: {detail}\"\n        )\n        self.memory.add(text, user_id=self.agent_id)\n\n    def call(self, tool_name, args, fn):\n        allowed, reason = self.check(tool_name, args)\n        if not allowed:\n            print(f\"[BLOCKED] {tool_name}({args}) — remembered: {reason}\")\n            return {\"blocked\": True, \"reason\": reason}\n\n        try:\n            result = fn(*args.values()) if isinstance(args, dict) else fn(args)\n            self.record(tool_name, args, True, \"ok\")\n            return {\"blocked\": False, \"result\": result}\n        except Exception as exc:\n            self.record(tool_name, args, False, str(exc))\n            raise\n```\n\nThe `check`\n\nstep uses semantic search, not exact string matching — `fetch_weather(city=\"NYC\")`\n\nand `fetch_weather(city=\"New York\")`\n\nwill match each other, which is exactly the kind of near-duplicate an exact-match cache would miss.\n\n``` python\n# flaky_tool.py\nclass RateLimitError(Exception):\n    pass\n\ndef call_flaky_api(endpoint):\n    if endpoint == \"/reports/daily\":\n        raise RateLimitError(\"429: rate limit exceeded, retry after 3600s\")\n    return {\"status\": \"ok\", \"endpoint\": endpoint}\npython\n# run.py\nfrom gatekeeper import MemoryGatekeeper\nfrom flaky_tool import call_flaky_api\n\ngate = MemoryGatekeeper(agent_id=\"report-agent\")\n\ntry:\n    gate.call(\"call_flaky_api\", {\"endpoint\": \"/reports/daily\"}, call_flaky_api)\nexcept Exception as exc:\n    print(f\"First attempt failed as expected: {exc}\")\n```\n\nFirst run:\n\n```\nFirst attempt failed as expected: 429: rate limit exceeded, retry after 3600s\n```\n\nRun `python run.py`\n\nagain — same process, same tool, same arguments, but now the agent has memory of the earlier failure:\n\n```\n[BLOCKED] call_flaky_api({'endpoint': '/reports/daily'}) — remembered: tool call: call_flaky_api with args {'endpoint': '/reports/daily'} failed at 2026-08-18T09:12:04+00:00: 429: rate limit exceeded, retry after 3600s\n```\n\nNo second API call, no second rate-limit hit, and the agent gets a reason it can reason about (or relay to a human) instead of a raw stack trace.\n\nThree knobs matter once you move past the toy example:\n\n`block_threshold`\n\n`user_id=self.agent_id`\n\nkeeps one agent's bad luck from blocking a different agent's legitimate call to the same tool. If multiple agents genuinely share risk (same downstream API, same rate limit bucket), give them a shared `agent_id`\n\n.`check()`\n\nthat discards hits older than your retry window (e.g., `time.time() - hit[\"created_at\"] < 3600`\n\n) so stale failures don't permanently disable a tool that's since recovered. This one-line filter is the difference between a gatekeeper and a agent that's afraid of everything it's ever failed at once.A plain `dict`\n\nor `functools.lru_cache`\n\nwould catch the exact-repeat case in a single run, but it dies with the process and can't do semantic matching — it won't know that retrying `/reports/daily`\n\nand `/reports/daily/`\n\nare the same mistake. The value of routing this through Mem0 specifically is that the memory persists across restarts and deployments, and the same store can also hold *successful* patterns — tool calls that worked, arguments that were well-formed, sequences that completed cleanly — so the gatekeeper isn't just a blocklist, it's the beginning of an agent that actually gets better at using its tools over time instead of relearning the same lesson every cold start.\n\nThe full example above is under 80 lines and runs with nothing but a Python environment and an OpenAI key — clone it, run it twice, and you'll see the block happen live before you've read the rest of this sentence.", "url": "https://wpnews.pro/news/give-your-ai-agent-a-memory-so-it-stops-repeating-the-same-failed-tool-call", "canonical_source": "https://dev.to/mukesh_13/give-your-ai-agent-a-memory-so-it-stops-repeating-the-same-failed-tool-call-1n7m", "published_at": "2026-08-17 19:07:12+00:00", "updated_at": "2026-08-17 20:13:58.649516+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "machine-learning"], "entities": ["Mem0", "OpenAI", "MemoryGatekeeper"], "alternates": {"html": "https://wpnews.pro/news/give-your-ai-agent-a-memory-so-it-stops-repeating-the-same-failed-tool-call", "markdown": "https://wpnews.pro/news/give-your-ai-agent-a-memory-so-it-stops-repeating-the-same-failed-tool-call.md", "text": "https://wpnews.pro/news/give-your-ai-agent-a-memory-so-it-stops-repeating-the-same-failed-tool-call.txt", "jsonld": "https://wpnews.pro/news/give-your-ai-agent-a-memory-so-it-stops-repeating-the-same-failed-tool-call.jsonld"}}