FishMem

Cloudflare Agents SDK

Add per-user FishMem memory to a Cloudflare Agent (Durable Object) — search and persist on each message.

The @fishmem/cloudflare-agents adapter wires FishMem into the Cloudflare Agents SDK (agents package). Each Agent is backed by a Durable Object, which is a natural per-user boundary — bind a FishMem session to it and the agent recalls and persists memory on every message.

FishMem Cloud runs on Cloudflare (Workers + D1 + Vectorize). Calls from your own Worker to https://fishmem.com stay on Cloudflare's network, so recall and persistence are low-latency.

Install

npm install agents @fishmem/cloudflare-agents

Bind the API key as a secret

Don't hardcode the key. Set it as a Worker secret so it's available as an environment binding inside the Durable Object.

npx wrangler secret put FISHMEM_API_KEY
# paste your fm_... key when prompted
wrangler.jsonc
{
  "name": "memory-agent",
  "main": "src/index.ts",
  "compatibility_date": "2026-06-01",
  "durable_objects": {
    "bindings": [{ "name": "MemoryAgent", "class_name": "MemoryAgent" }]
  },
  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MemoryAgent"] }]
}

The Agent

Subclass Agent and create a FishMem session keyed to the Durable Object's name, which here is the user's id. On each message the agent searches FishMem, generates a reply grounded in the recalled facts, then adds the exchange back.

src/index.ts
import { Agent, type AgentNamespace, routeAgentRequest } from "agents";
import { createFishMemSession } from "@fishmem/cloudflare-agents";

interface Env {
  MemoryAgent: AgentNamespace<MemoryAgent>;
  FISHMEM_API_KEY: string;
}

export class MemoryAgent extends Agent<Env> {
  async onMessage(text: string): Promise<string> {
    // The DO name is the per-user key; scope all memory to it.
    const memory = createFishMemSession({
      apiKey: this.env.FISHMEM_API_KEY,
      userId: this.name,
    });

    // Search relevant memories for this user.
    const { results } = await memory.search(text, { top_k: 5 });
    const context = results.map((m) => `- ${m.memory}`).join("\n");

    const reply = await this.generate(text, context);

    // Persist the new facts from the exchange.
    await memory.add([
      { role: "user", content: text },
      { role: "assistant", content: reply },
    ]);

    return reply;
  }

  private async generate(text: string, context: string): Promise<string> {
    // Call your model of choice here, grounding it in `context`.
    return `(reply grounded in:\n${context}\n)`;
  }
}

export default {
  fetch(request: Request, env: Env): Promise<Response> {
    return routeAgentRequest(request, env).then(
      (res) => res ?? new Response("Not found", { status: 404 }),
    );
  },
};

Because each user maps to a distinct Durable Object instance, this.name is a stable per-user key — passing it as userId keeps every user's memory isolated. See scoping for the full model.

Next steps

On this page