Build1 distinct publisher3 min readUpdated
A runnable Mem0-backed wrapper blocks an agent's repeat tool call before it burns the request. The pattern is sound; the default policy will block you forever.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A dev.to walkthrough published this week ships a small, complete `MemoryGatekeeper` class that sits between an agent's decision to call a tool and the actual execution: search memory for similar past attempts first, block if a similar attempt failed, write the outcome back either way [3]. The failure mode it targets is a billing line, not a prompting problem: an agent hits a 429, retries with identical arguments, gets rate-limited again, and then repeats the whole sequence in a fresh session because the lesson lived in a transcript that was discarded when the process exited [1].
The framing is worth noting because the same week a post about manually gatekeeping agent tool calls reached the dev.to front page with 48 comments, mostly developers arguing about how far an agent's tool-calling loop can be trusted [2]. This is the version of that argument with code attached.
The mechanics are unremarkable, which is the point. `check()` builds a query string of the form "tool call: {name} with args {args}" [14], runs `memory.search` with `limit=3` against the agent's memory namespace, and returns an (allowed, reason) pair [7]. It blocks when a hit scores at or above a `block_threshold` that defaults to 0.75 and the remembered text contains the substring "failed" [6]. `record()` appends a line stating whether the call succeeded or failed, a UTC ISO timestamp, and a detail string [8]. The wrapper prints a `[BLOCKED]` line and returns `{"blocked": True, "reason": reason}` instead of calling the function, and on a real exception it records the failure and re-raises [9].
Setup is Python 3.10+, the `mem0ai` package, and an OpenAI key, which Mem0's default extraction pipeline uses to turn raw text into structured memories; no separate Mem0 account is needed [4]. Mem0 defaults to a local on-disk vector store, so the memory layer does not call a hosted service [5]. The demo is a fake API that raises `RateLimitError("429: rate limit exceeded, retry after 3600s")` for the endpoint `/reports/daily` [11]. First run fails and records; second run of the same script prints the block with the remembered failure text and timestamp [12]. No second API call, no second rate-limit hit [13].
Two things to understand before you put this in a hot path. First, the block is semantic, not exact: the post notes that `fetch_weather(city="NYC")` and `fetch_weather(city="New York")` will match each other, which an exact-match cache would miss [10]. That is the real argument for the vector store over a dict, and it is also the reason a 0.75 threshold is a policy decision you now own rather than a constant you copy.
Second, the code remembers the retry-after but never reads it. `check()` compares score and substring only; nothing compares the recorded timestamp against now [6][7][8], so a single recorded failure blocks that call shape indefinitely, long past the 3600 seconds the error message asked for [1]. A transient 429 becomes a permanent refusal until you delete the memory. Add a TTL or parse the retry-after before shipping.
On cost, an allowed call carries one search plus one add; a blocked call carries the search alone [2]. With Mem0's extraction pipeline in the path, "cheap lookup" is relative to the API call you avoided, not free [4].
Watch whether the block policy grows a recency term, and whether the substring test survives contact with real error strings: a successful call records "succeeded" with detail "ok" and will not trip the filter, but any detail text containing the word "failed" will [3].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The first run prints "First attempt failed as expected: 429: rate limit exceeded, retry after 3600s"; running python run.py again prints a [BLOCKED] line quoting the remembered failure text including the timestamp 2026-08-18T09:12:04+00:00.
On the second run there is no second API call and no second rate-limit hit.
An agent calls a flaky API, gets a 429, retries with the same arguments, and is rate-limited again; ten minutes later in a fresh session it repeats the same call, because the lesson lived in a transcript that was thrown away when the process exited.
A post about manually gatekeeping AI agent tool calls hit the front page of dev.to this week with 48 comments, mostly developers arguing over how much an agent's tool-calling loop can be trusted.
The article presents a MemoryGatekeeper class that sits between an agent's decision to call a tool and the execution: before calling, search memory for semantically similar past attempts; if a similar attempt failed recently, block the call and return the remembered reason instead of burning a real API call; after every call, success or failure, write the outcome back to memory.
Requirements are Python 3.10+, an OpenAI API key (Mem0's default extraction pipeline uses it to turn raw text into structured memories), and the mem0ai package installed via pip; no separate Mem0 account is required for this local setup.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Complete runnable code, single publisher, no independent verification
The mechanism is unusually legible: full listings for gatekeeper.py, flaky_tool.py and run.py let the check/record/call contracts and the missing timestamp comparison be verified by reading the source. But everything rests on one dev.to post, the only outcome evidence is an author-pasted two-run console transcript whose memory timestamp postdates publication, and nothing is measured - no latency, cost, or false-positive figures.
No adoption evidence in supplied sources
The cluster contains no deployment, release, usage-disclosure, pricing or benchmark facts. The only external signal is an unlinked dev.to front-page post said to have 48 comments, which is discussion volume rather than evidence that anyone runs this gatekeeper pattern in production.
Modest and largely self-caveated, with a small generalization gap
The article's framing is restrained: the dek itself warns the default policy will block you forever, and the closing knobs concede threshold risk, per-agent scoping and expiry. The mild overstatement is that a two-file toy demo against a hardcoded RateLimitError is presented as a general reliability wrapper for 'any tool call', while the shipped default policy is exactly the version the author says not to trust, and no overhead or false-positive numbers accompany the claim.
Practitioner tutorial hooked to a trending post, routing readers to specific tooling
Observable incentives are those of platform tutorial content: the piece opens by attaching itself to a same-week dev.to front-page discussion, and its setup steps direct readers to a specific stack (pip install mem0ai plus an OpenAI API key for the extraction pipeline). No sponsorship, employment or vendor relationship is disclosed in the supplied material, and the local self-hosted framing cuts against a hosted-service upsell, so the pull is engagement and tooling advocacy rather than a documented commercial stake.
High confidence in the code-level reading, low confidence in real-world effect
What the code does and where it fails can be stated with near-certainty because the listings are published, so the mechanism and derived defect claims are solid. Confidence drops on whether this actually reduces wasted calls in practice: one publisher, one self-reported transcript, no adoption data, and no measurement of overhead or false positives.
build
Force the tool call, then hand Lightsail a long-lived key1 distinct publisher
build
Stop timing your GraphQL tests and start counting loader calls1 distinct publisher
build
Your 90% Cache Hit Ratio Is a Lagging Indicator. Alert on Cold Misses Per Key1 distinct publisher
build
Your inference bill is an architecture defect: declare the task before you call the model1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 17, 2026