TypeScript quickstart
The npm client for llmslim serve, with memory tools for the Vercel AI SDK and Mastra.
Install
The client is a thin layer over the HTTP API, with no dependencies. It runs on Node 18+, Bun, Deno and edge runtimes. Start a server, then add the package.
pip install "llmslim[server]" && llmslim serve # the server: one process, one SQLite file
npm install llmslim # the clientStore and search
baseUrl and apiKey default to LLMSLIM_BASE_URL (then http://127.0.0.1:8080) and LLMSLIM_API_KEY.
import { LLMSlim } from "llmslim";
const slim = new LLMSlim({ baseUrl: "http://127.0.0.1:8080", apiKey: process.env.LLMSLIM_API_KEY });
await slim.memories.add({
userId: "asha",
messages: [
{ role: "user", content: "I'm allergic to peanuts, so never suggest satay." },
{ role: "assistant", content: "Understood: no peanuts." },
],
});
const context = await slim.memories.search({ userId: "asha", query: "Am I allergic to anything?", budget: 1500 });
console.log(context.text); // the selected turns under their dates; use it as data, not instructionsVercel AI SDK
llmslim/ai-sdk (with ai 7) gives the model recall_memory and remember tools, and a middleware that saves each user message and reply. Neither changes your prompt: recalled memory arrives as a tool result. The user, agent and run ids come from your code, never from the model. Running this needs a model provider's key (here ANTHROPIC_API_KEY).
import { generateText, stepCountIs, wrapLanguageModel } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { LLMSlim } from "llmslim";
import { memoryMiddleware, memoryTools } from "llmslim/ai-sdk";
const client = new LLMSlim();
const model = wrapLanguageModel({
model: anthropic("claude-haiku-5-5"),
middleware: memoryMiddleware({ client, userId: session.userId }),
});
const result = await generateText({
model,
tools: memoryTools({ client, userId: session.userId }),
stopWhen: stepCountIs(5),
messages,
});Mastra
llmslim/mastra (with @mastra/core 1.29.1 or newer, which needs Node 22.13+) gives an agent the same tools, and an output processor that saves the latest user message and the reply once per generate() or stream(). scope is called with Mastra's request context, so one agent serves many users; when it returns no id, the tools refuse and nothing is saved. Running this needs a model provider's key.
import { Agent } from "@mastra/core/agent";
import { MASTRA_RESOURCE_ID_KEY } from "@mastra/core/request-context";
import { LLMSlim } from "llmslim";
import { memoryProcessor, memoryTools, type MemoryOptions } from "llmslim/mastra";
const memory: MemoryOptions = {
client: new LLMSlim(),
// Whose memory: the signed-in user, from your server, for each request.
scope: (requestContext) => ({ userId: requestContext.get(MASTRA_RESOURCE_ID_KEY) }),
};
export const assistant = new Agent({
id: "assistant",
name: "Assistant",
instructions: "You are a helpful assistant.",
model: "anthropic/claude-haiku-5-5",
tools: { ...memoryTools(memory) },
outputProcessors: [memoryProcessor(memory)], // after any guardrails
});The processor saves nothing when a guardrail's tripwire stopped the run, when the run was aborted or failed, or while it waits for a tool approval, and a failed save never fails the response. There is no input processor that injects memory: the model calls recall_memory when an answer depends on earlier conversations.
The client's API
A failed call throws LLMSlimError with the server's status, type and message; when the server can't be reached, status is 0 and type is "unreachable".
| Method | Does |
|---|---|
memories.add({ userId?, agentId?, runId?, messages, timestamp?, metadata? }) | Stores messages (a string, a { role, content } object, or a list). Needs one scope id. |
memories.search({ ...scope, query, budget?, relevance?, since?, until? }) | Returns the turns that answer query: text, tokens, turns, historyTokens. |
memories.list({ ...scope, limit?, offset?, since?, until? }) | A scope's messages, oldest first. |
memories.get(id), memories.update(id, { content?, metadata? }) | Read or correct one message; null when it doesn't exist. |
memories.delete(id), memories.deleteAll(scope) | Delete one message, or a whole scope. |
context.fit({ text, budget?, query? }) | Shrinks long text to the budget, keeping what answers query. |
info() | The server's version and budgets. |
Explanations
The client's memories.search doesn't pass explain yet. To see why each turn was kept from TypeScript, post "explain": true to /v1/memories/search yourself and read explanation from the response.
Questions
Which server does the client need?
llmslim serve from llmslim 0.10.0 on, which added the HTTP API. The client is llmslim 0.2.0 on npm.
Can I use it in the browser?
No. An API key gives access to every user's memory, so call the server from your backend.
Why isn't recalled memory added to the prompt for me?
Adding it to the system prompt or the latest user message would change what earlier turns were sent with: prompt caches would stop hitting, and on Claude models with preserved thinking the next request would fail or drop the reasoning. As a tool result it stays in the history like any other message.