Korely

Vercel AI SDK

The AI SDK is the TypeScript toolkit for building AI applications: generateText, streamText, agents, and UI hooks for React, Vue and Svelte. Korely adds a memory to it through the korely-memory Node package, or through plain fetch calls. You define small tool() wrappers around Korely's REST endpoints and pass them to the model. The model can then search memories, read facts and save new memories, all inside a normal generateText call.

What you get

Each tool wrapper calls one REST endpoint. The model receives JSON and decides when to use memory. For reading, the examples give the model search and get context, which returns the facts that are true now plus the best-matching memories. For writing, they give it a tool that adds a memory. The same pattern wraps any other endpoint (GET /v1/memories/{id}, GET /v1/facts, GET /v1/profile, DELETE /v1/memories/{id}) in a few lines.

Install

You need the AI SDK, the Korely Node package, and zod, which the examples use for the tools' input schemas. The examples were run with ai 7.0.159 (it needs Node 22 or later), korely-memory 0.1.17 and zod 4.6.7, with a fake model and the Korely calls answered by a local test server, so no model provider was called. The calls to the Korely Cloud that the ready-made tools make were also checked once against the real service.

Terminal window
npm install ai korely-memory zod

You also need a Korely API key. Copy it from API Keys in the dashboard at agent.korely.ai and export it. The examples name the model as a string ('anthropic/claude-sonnet-4.5'). The AI SDK sends such a string through the Vercel AI Gateway, so you also need an AI Gateway key. To call a provider directly, install its package (for example @ai-sdk/anthropic) and pass its model object. We did not call a real model, so the model name is yours to check.

Terminal window
export KORELY_API_KEY="kor_live_..."
export AI_GATEWAY_API_KEY="..." # for the 'provider/model' strings below

Authentication

Every request to https://api.korely.ai/v1 carries the key in the Authorization header:

const headers = {
Authorization: 'Bearer ' + process.env.KORELY_API_KEY,
'Content-Type': 'application/json',
};

Complete example

A single-file agent with two memory tools. The model decides when to use them. stopWhen: stepCountIs(5) lets the model chain up to five steps (for example search, then answer) before it must produce the final text.

agent.ts
import { generateText, stepCountIs, tool } from 'ai';
import { z } from 'zod';
const KORELY_API = 'https://api.korely.ai/v1';
const headers = {
Authorization: 'Bearer ' + process.env.KORELY_API_KEY,
'Content-Type': 'application/json',
};
const tools = {
search_memory: tool({
description: 'Search what has been remembered so far.',
inputSchema: z.object({
query: z.string().describe('Keyword-style query, 1 to 5 words'),
limit: z.number().optional().default(5),
}),
execute: async ({ query, limit }) => {
const res = await fetch(KORELY_API + '/memories/search', {
method: 'POST',
headers,
body: JSON.stringify({ query, limit }),
});
return res.json();
},
}),
save_memory: tool({
description: 'Save something worth remembering.',
inputSchema: z.object({ content: z.string() }),
execute: async ({ content }) => {
const res = await fetch(KORELY_API + '/memories', {
method: 'POST',
headers,
body: JSON.stringify({ content }),
});
return res.json();
},
}),
};
const { text } = await generateText({
model: 'anthropic/claude-sonnet-4.5',
tools,
stopWhen: stepCountIs(5),
prompt:
'What do my notes say about the Lisbon offsite? ' +
'Search memory before answering.',
});
console.log(text);

When you run it, the model calls search_memory on its own. The output below is an example, not yours:

terminal npx tsx agent.ts
$ npx tsx agent.ts

→ tool call search_memory(query="lisbon offsite", limit=5)

← JSON from /v1/memories/search:

   { "results": [
     { "id": "mem_...", "score": 0.94, "snippet": "Three days, June 24 to 26.
       Day 1 product review, day 2 strategy workshop, day 3 free morning +
       flights home. Hotel booked near Cais do Sodré.",
       "user_id": "...", "agent_id": null, "metadata": {} },
     { "id": "mem_...", "score": 0.81, "snippet": "Flights confirmed for the
       23rd evening. Bring the demo laptop, HDMI dongle, offsite agenda
       printouts...", "user_id": "...", "agent_id": null, "metadata": {} }
   ] }

Agent › The Lisbon offsite runs June 24 to 26: product review on
day 1, a strategy workshop on day 2, and a free morning on day 3
before flights home. You fly in the evening of the 23rd, the hotel
is near Cais do Sodré, and your checklist flags the demo laptop,
an HDMI dongle and printed agendas.

/v1/memories/search compares the meaning of your query with the saved memories and returns the closest ones. No language model runs on Korely's side when you read; your model does the reasoning. Reads are cheap for us, so every plan allows at least 100 times more queries than writes per month (see the pricing page).

Prefer get_context for recall. Search returns matching snippets. GET /v1/context returns a context block: the facts that are true now ("Luca upgraded to Pro", "prefers async standups"), with replaced facts left out, plus the best-matching memories. Give the model a get_context tool when you want it to know the user, and keep search for "find the exact memory that mentioned X".

Using the korely-memory Node SDK

If you prefer a typed client to raw fetch, the korely-memory package exports a Korely class that calls the same endpoints. It reads KORELY_API_KEY from the environment when you pass no key. This snippet reuses tool and z from the example above:

import { Korely } from 'korely-memory';
const korely = new Korely({ apiKey: process.env.KORELY_API_KEY });
// Recall: the facts that are true now. Reach for this first.
const recall = tool({
description: 'Recall settled facts and relevant memories about this user.',
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => korely.getContext({ query }),
});
const search_memory = tool({
description: 'Find the exact memory that mentioned something.',
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => korely.search(query, { limit: 5 }),
});
const save_memory = tool({
description: 'Save something worth remembering.',
inputSchema: z.object({ content: z.string() }),
execute: async ({ content }) => korely.add(content),
});

Ready-made: korely-memory/ai-sdk

The korely-memory package also ships korely-memory/ai-sdk. withKorelyMemory(model, { userId }) reads the memory before each call (it calls GET /v1/context for the latest user message and adds the result as system messages) and stores the turn after it. korelyTools({ userId }) gives the model searchMemory and addMemory tools; they never take the user from the model. Pass userId from your own login. The hand-written tool() examples on this page are the do-it-yourself path.

import { generateText } from 'ai';
import { withKorelyMemory, korelyTools } from 'korely-memory/ai-sdk';
const { text } = await generateText({
model: withKorelyMemory('anthropic/claude-sonnet-4.5', { userId }),
tools: korelyTools({ userId }),
prompt: 'What plan am I on?',
});

withKorelyMemory writes by default. After a call that ends the turn, it stores the user message and the reply as one memory (for example "user: What plan am I on?" followed by "assistant: ..."). That counts as a write against your monthly quota, on every turn. Pass remember: false if your app writes its own memories or has no consent to store the conversation.

Streaming

With streamText the call returns before the model finishes, and you read the text as it arrives. The tools are plain HTTP calls, so there is nothing to close. This example reuses the workspace-scoped tools defined above (the streaming loop itself was not run against a model):

import { streamText, stepCountIs } from 'ai';
const result = streamText({
model: 'anthropic/claude-sonnet-4.5',
tools,
stopWhen: stepCountIs(5),
prompt: 'Summarize what you know about my current apartment lease.',
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}

Serverless route handlers: define tools that carry no end-user id at module scope, so they are not rebuilt on every request. Create tools that carry a user_id (korelyTools({ userId }), withKorelyMemory, or the factory below) on every request, because the user_id must come from that request's session. Each execute is just a fetch call, so there is no connection to open or close.

One memory per end user

The examples above read and write the whole project, and a read without a user_id covers every end user. When each person who uses your product needs a private memory, label every read and write. The three labels are user_id (the end user; unlimited on every plan; you choose the string), agent_id (your application) and run_id (one session).

Take user_id from your session on the server, so the model cannot reach another person's memory whatever the conversation says:

import { generateText, stepCountIs, tool } from 'ai';
import { z } from 'zod';
const KORELY_API = 'https://api.korely.ai/v1';
const headers = {
Authorization: 'Bearer ' + process.env.KORELY_API_KEY,
'Content-Type': 'application/json',
};
// user_id is bound here, server-side. The model never sees or sets it.
function korelyMemoryTools(userId: string) {
return {
search_memory: tool({
description: 'Search everything known about this user.',
inputSchema: z.object({
query: z.string().describe('Keyword-style query, 1 to 5 words'),
}),
execute: async ({ query }) => {
const res = await fetch(KORELY_API + '/memories/search', {
method: 'POST',
headers,
body: JSON.stringify({ query, user_id: userId, limit: 5 }),
});
return res.json();
},
}),
save_memory: tool({
description: 'Save something worth remembering about this user.',
inputSchema: z.object({
content: z.string(),
}),
execute: async ({ content }) => {
const res = await fetch(KORELY_API + '/memories', {
method: 'POST',
headers,
body: JSON.stringify({
content,
user_id: userId,
agent_id: 'support-bot',
}),
});
return res.json();
},
}),
};
}
// Per chat request:
const { text } = await generateText({
model: 'anthropic/claude-sonnet-4.5',
tools: korelyMemoryTools(session.userId),
stopWhen: stepCountIs(5),
messages,
});

A read that carries a user_id returns only that user's data. When a user asks you to wipe what the bot knows, one call erases every memory for that user (DELETE /v1/users/{userId}/memories, see the API reference). The chatbot that remembers cookbook builds this pattern out end to end.

Troubleshooting

SymptomFix
401 from api.korely.ai Key missing or malformed. Check that KORELY_API_KEY starts with kor_live_ and is exported in the process environment where the agent runs.
The tools load but the model never calls them Add a line to the prompt or the system message ("search memory before answering"). The model chooses its tools, and smaller models need the hint more than larger ones.
429 on writes Read the error's code. quota_exceeded: you have used the month's writes for your plan (hobby: 250 writes / month, plus a 10% grace); upgrade on the pricing page or deduplicate before saving. agent_cap_exceeded: the plan's named agents are taken; reuse an agent_id or delete one. rate_limit_exceeded: slow down and retry after the Retry-After header.
One slow tool call stalls the whole turn Cap chains with stopWhen: stepCountIs(n) and keep limit low on search calls; 5 results is usually plenty for a chat turn.

For the full SDK method surface, see the SDK reference. For how facts, supersession and time work, see temporal facts.

Something not working? Email [email protected] with your ai and korely-memory versions and the error output. We read every message.