I shipped middleware in @mnemopay/sdk last week that wraps a Gemini agent and gives it three things it can't get from the Google SDK alone:
- Persistent memory that survives process restarts.
- A SHA-256 hash-chained audit log of every call.
- A payment rail it can charge against (Stripe, Lightning, Paystack, x402, or AP2 — pick one).
This post walks through the first two with runnable code. If you've ever built an agent that "forgot everything after the page reloaded" or had to prove to a compliance reviewer that your agent didn't fabricate a tool call, this is for you.
The setup
npm install @mnemopay/sdk @google/generative-ai
The middleware pattern is intentionally small: it intercepts the agent's chat method, writes every turn into a recall store, and appends every call to an AuditChain JSONL file.
The 3 lines
Start with a vanilla Gemini agent:
import { GoogleGenerativeAI } from "@google/generative-ai";
const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
const chat = model.startChat();
Now wrap it:
import { wrapGemini } from "@mnemopay/sdk/middleware/gemini";
import { RecallEngine, localEmbed } from "@mnemopay/sdk/recall";
import { AuditChain } from "@mnemopay/sdk/governance";
const recall = new RecallEngine({ embed: localEmbed });
const audit = new AuditChain({ path: "./audit.jsonl" });
const wrapped = wrapGemini(chat, { agentId: "support-bot-1", recall, audit });
That's it. Everything wrapped.sendMessage(...) does is now:
- Embedded and stored in
recallso the next call can pull related history without re-stuffing the context window - Logged to
./audit.jsonlas an append-only hash-chained event - Tagged with
agentIdso a multi-tenant deployment can isolate memory per agent
What the audit log looks like
After three turns the file looks like:
{"ts":"2026-05-22T13:01:11.221Z","agentId":"support-bot-1","kind":"chat.in","payload":{"text":"Hi, how do I reset my password?"},"prev":"GENESIS","hash":"3f0a..."}
{"ts":"2026-05-22T13:01:12.490Z","agentId":"support-bot-1","kind":"chat.out","payload":{"text":"Sure — go to..."},"prev":"3f0a...","hash":"7c14..."}
{"ts":"2026-05-22T13:02:08.110Z","agentId":"support-bot-1","kind":"chat.in","payload":{"text":"Still doesn't work"},"prev":"7c14...","hash":"a921..."}
Each hash is SHA-256(prev + ts + agentId + kind + JSON.stringify(payload)). Drop a single byte in any record and the chain breaks at that record. You can verify it later:
const isValid = await audit.verify();
console.log(isValid); // true
verify() walks the chain once and recomputes every hash. On my 5-year-old laptop it does ~50k records/second. There's a eventJsonCache parallel list so verify() doesn't pay for double serialization — that landed in 1.10.
Why bother
Three reasons depending on who you are:
Building for EU customers. The AI Act's Article 12 ("Record-Keeping") wants automatic logs of high-risk AI system events that allow traceability. A JSONL file with a hash chain is the simplest viable evidence trail. You don't need a vendor for this. You do need the file.
Selling into banks or healthcare. "Where is the audit log?" is the second question in every security review. "It's in Datadog" is not a satisfying answer when Datadog can edit it. A hash-chained local file is.
Debugging your own agent. You wrote a prompt three weeks ago and your agent is doing something weird this week. The audit log is the single source of truth for what actually went in and what came out, in order.
Why recall is more than just a vector DB
@mnemopay/sdk/recall does cosine over a local embedding store by default (localEmbed is a deterministic 384-dim embed; no network). You can swap in any embedder. The thing that's different from a vector DB is that it's keyed on agentId and has a forget() method that respects scope. So when a GDPR delete request comes in for one user, you call recall.forget({ agentId, userId }) and the rows actually leave the disk, not just the index.
Picking a payment rail
I skipped over the third thing the wrapper adds — a payment rail. Short version: if your agent needs to charge a user, your code looks like this regardless of which rail you pick:
import { StripeRail } from "@mnemopay/sdk/rails/stripe";
const rail = new StripeRail({ apiKey: process.env.STRIPE_SECRET });
await wrapped.charge({ rail, amount: 999, currency: "usd", description: "API call" });
Swap StripeRail for LightningRail, PaystackRail, X402Rail, or GoogleAP2Rail and the agent's code doesn't change. The 1.9.0 release added AP2 verifiable credentials. The python port (mnemopay-python) has the same rails behind the same interface — useful if your agent backend is Python and your frontend SDK is TypeScript.
The repo
github.com/mnemopay/mnemopay-sdk — Apache 2.0, npm @mnemopay/sdk, current stable 1.11.0. The Gemini middleware is @mnemopay/sdk/middleware/gemini; OpenAI, Anthropic, and LangGraph wrappers ship alongside it under the same pattern.
Good first issues are tagged. PRs welcome.
— Jeremiah (@mnemopay)
Top comments (0)