Agents that forget everything between sessions aren't agents — they're stateless functions with a chat UI. A new open-source integration wires the Vercel AI SDK into a self-hosted Dakera memory server, so recall persists across sessions and is ranked by importance × recency × semantic relevance — not just raw vector distance.
The problem: a great SDK with no memory
The Vercel AI SDK gives you a clean, provider-agnostic surface — swap openai("gpt-4o") for Anthropic or Google, keep calling generateText and streamText, and everything just works. But each call is stateless. Whatever the user told your assistant last Tuesday is gone the moment the request ends. The usual workaround — stuffing an ever-growing transcript back into the prompt — burns tokens, blows past context windows, and still can't reach across processes or deployments.
What you actually want is a memory store that keeps the useful facts, forgets the noise, and surfaces the right ones at the right time. That's what @dakera-ai/ai-sdk (MIT, on npm) adds — without touching your model or provider code.
Two extension points, zero lock-in
The package plugs into the AI SDK's two standard extension points — language model middleware and tools — and exports exactly two helpers. Nothing about your provider code changes; you keep calling generateText and streamText exactly as before. It needs Node ≥ 20 and the peer deps ai ≥ 6.0.0, @dakera-ai/dakera ≥ 0.11.54, and zod ≥ 3.23.0.
| Helper | Extension point | Who drives memory |
|---|---|---|
createDakeraMemoryMiddleware | Language model middleware | The framework (transparent) |
createDakeraTools | Tools (ToolSet) | The model (deliberate) |
Pattern 1 — middleware (transparent)
createDakeraMemoryMiddleware wraps any language model. Before each call it recalls the most relevant memories and injects them as a system message; after each call it stores the new exchange back into Dakera — all invisibly.
import { generateText, wrapLanguageModel } from "ai";
import { openai } from "@ai-sdk/openai";
import { createDakeraMemoryMiddleware } from "@dakera-ai/ai-sdk";
const model = wrapLanguageModel({
model: openai("gpt-4o-mini"),
middleware: createDakeraMemoryMiddleware({ agentId: "user-1", recallK: 5 }),
});
// Session 1 — the exchange is stored automatically
await generateText({ model, prompt: "I'm building a Rust vector DB called Velox." });
// Session 2 — days later, a different process
const { text } = await generateText({ model, prompt: "What am I working on?" });
// → "You're building Velox, a Rust vector database." — nothing in the prompt
Under the hood, recall lives in the middleware's transformParams hook (it reads the last user message, recalls, and prepends a system block), and write-back lives in wrapGenerate (it stores User: … and Assistant: … after the model responds). The default recall is 5 memories at importance 0.7 — both configurable.
Concretely, when recall returns memories the model sees one extra system message at the top of the prompt — nothing else about your call changes:
Relevant memories from previous sessions (use them if helpful):
- User: I'm building a Rust vector DB called Velox.
- Assistant: Nice — a Rust vector database, Velox. Want help with the index?
The header line is configurable via the header option, and every knob has a sensible default:
| Option | Default | What it does |
|---|---|---|
agentId | — | Scopes memories to a user or agent (required) |
recallK | 5 | Memories recalled and injected per call |
minImportance | 0 | Minimum importance threshold for recall |
importance | 0.7 | Importance assigned to stored exchanges |
store | true | Whether to write the exchange back after generation |
Pattern 2 — tools (model-driven)
createDakeraTools returns two AI SDK tools, recallMemory and storeMemory, and lets the model decide when to look something up or persist a fact — useful for agentic workflows where explicit memory control improves quality.
import { createDakeraTools } from "@dakera-ai/ai-sdk";
const tools = createDakeraTools({ agentId: "user-1" });
await generateText({
model: openai("gpt-4o-mini"),
tools,
maxSteps: 4,
prompt: "Remember I prefer metric units. Then convert 5 miles to km.",
});
// Model calls storeMemory(...), then answers "5 miles = 8.047 km"
The tools are zod-typed: recallMemory takes a query (and optional topK) and returns { content, importance, score } results; storeMemory takes content (and optional importance from 0 to 1) and returns { id, status: "stored" }. On a later turn the model can look that preference back up before it answers:
await generateText({
model: openai("gpt-4o-mini"),
tools,
maxSteps: 3,
system: "Check memory for the user's preferences before answering.",
prompt: "Show me how to read a file.",
});
// Model calls recallMemory("unit / formatting preferences"),
// sees the metric-units fact, and answers accordingly.
Pattern 3 — both at once
Run the middleware with store: false for automatic recall, and let the tools own persistence — so continuity is transparent but the model still decides deliberately what's worth keeping. Share one DakeraClient across the two and every agent in your app reuses the same connection.
const client = new DakeraClient({ baseUrl: process.env.DAKERA_URL! });
const agentId = "user-1234";
const model = wrapLanguageModel({
model: openai("gpt-4o"),
middleware: createDakeraMemoryMiddleware({ client, agentId, store: false }),
});
const tools = createDakeraTools({ client, agentId });
Storage never breaks generation
Memory is best-effort; generation is not. The middleware runs the model first, then attempts write-back inside a try / catch that swallows storage errors. If your Dakera server is down, mid-deploy, or slow, the call still returns a completion — it just degrades to a stateless one instead of throwing a 500. Recall is defensive in the same spirit: a call with no user text, or an empty memory store on day one, passes the prompt through untouched.
Streaming and the edge
Recall injection happens in transformParams, which the SDK applies to both generateText and streamText — so streamed calls recall context transparently, before the first token. Automatic write-back happens in wrapGenerate, which covers generate calls; to persist streamed turns, reach for the tools pattern or call storeMemory yourself.
// app/api/chat/route.ts — Next.js Route Handler
import { streamText, wrapLanguageModel } from "ai";
import { openai } from "@ai-sdk/openai";
import { createDakeraMemoryMiddleware } from "@dakera-ai/ai-sdk";
export async function POST(req: Request) {
const { messages, userId } = await req.json();
const model = wrapLanguageModel({
model: openai("gpt-4o-mini"),
middleware: createDakeraMemoryMiddleware({ agentId: userId }),
});
return streamText({ model, messages }).toUIMessageStreamResponse();
}
The integration is plain server-side TypeScript, so it drops cleanly into Next.js Route Handlers, Server Actions, and server components — read your keys from the server environment and keep the wrapped model off the client.
Self-hosted, no cloud key. The integration runs against a local Dakera server — git clone dakera-deploy && docker compose up -d brings up the server plus MinIO on :3000. Your memory data never leaves your infrastructure.
Transparent or deliberate — pick per surface
The two patterns aren't rivals; they answer different questions. Reach for the middleware when continuity should simply happen — a support assistant that remembers a customer's plan, a coding copilot that recalls the project you're on. Reach for the tools when you want the model to reason about memory — an agent that decides a preference is worth persisting, or that a question needs a lookup before it answers.
- Middleware — recall and store on every call, no prompt changes, defensive by default.
- Tools —
recallMemory/storeMemorythe model calls on its own, with structured results it can reason over. - Both — middleware with
store: falsefor automatic recall, tools for deliberate writes, one sharedDakeraClient.
Because both helpers accept a pre-built DakeraClient, a fleet of agents can share a single connection while each scopes its own memory by agentId — a support bot and a sales bot on the same server, never crossing wires.
Why decay-weighted recall matters
A fixed-TTL store either keeps stale facts forever or drops them on a timer. Dakera ranks recalled memories by importance × recency × semantic relevance, so the most contextually useful memories surface first and old context stops competing with fresh, relevant facts. That's the same engine that scores 88.2% Recall@20 on the 1,540-question LoCoMo benchmark.
Get started
Install everything with one command:
npm install @dakera-ai/ai-sdk ai @dakera-ai/dakera zod
Point it at a Dakera server with DAKERA_URL (default http://localhost:3000) and DAKERA_API_KEY, wrap your model or add the tools, and you're done. Read the Vercel AI SDK integration guide → for the full options tables, streaming details, and the combined pattern.
The Vercel AI SDK joins the existing lineup — LangChain, LlamaIndex, CrewAI, AutoGen, LangChain.js, Strands Agents, and the MCP server for Claude, Cursor, and Windsurf. See them all on the integrations page →
