Korely

Concepts

The memory model

Korely is memory for AI agents: what an agent learns in one session stays available in later sessions. Korely keeps two kinds of data: memories, the texts you write, and facts, short statements that Korely's extraction model pulls out of them. A run_id label groups memories by session.

This page shows what all three hold and which call reads or writes them. Your agent saves text with add(). Before your model answers, get_context() returns a context block to add to its prompt: the current facts and the memories most similar to the question.

Terms

The Glossary defines the words used across the docs, and each one links there where it first appears. This page adds one.

  • Korely's extraction model and your model: two different language models. Korely's extraction model is the one Korely runs to write facts from your memories (extraction). Your model is the one your app calls to answer, and it reads the context block.

A small example

You need Python 3.9 or later. Install the SDK:

pip install korely-memory

Get a free API key on the Quickstart page of these docs, then export it:

export KORELY_API_KEY=kor_live_...

Without the key, Korely() raises KorelyError: "No API key. Pass api_key=..., set KORELY_API_KEY...".

The script saves one memory for the end user customer-giulia and waits 20 seconds, about twice the usual extraction time, to leave a margin. Then it prints each fact as open (true now) or closed (no longer true, kept as history), and the context block. Checked with SDK 0.1.28.

import time
from korely_memory import Korely
korely = Korely() # reads KORELY_API_KEY from the environment
korely.add("Giulia is on the Advanced plan. She prefers email follow-ups. "
"She is based in Turin.", user_id="customer-giulia")
time.sleep(20) # extraction takes about ten seconds
for f in korely.get_facts(user_id="customer-giulia"):
state = "closed" if f.invalid_at else "open"
print(f.subject, "|", f.predicate, "|", f.object, "|", state)
result = korely.get_context("How should we follow up with Giulia?", user_id="customer-giulia")
print(result.context) # prints the block for your model

Always pass user_id when your project serves several customers.

Without it, a read covers every end user in the project. See Narrowing a read.

It prints facts like these:

Giulia | prefers | email follow-ups | open
Giulia | based_in | Turin | open
Giulia | subscribes_to | Advanced plan | open

Then the context block, shortened: the real block starts with a fixed note for your model and lists the facts closest to your question (up to ten). The dates are the day you run the script.

## Known facts
- Giulia prefers email follow-ups (since 2026-11-01)
## Relevant memories
- [2026-11-01] Giulia is on the Advanced plan. She prefers email follow-ups. She is based in Turin.
  • Korely's extraction model picks predicate names, so yours may differ.
  • The subject is usually the name in the text, but sometimes it is your user_id, so do not rely on it being the name. For a memory written with an agent_id and no user_id, the model is asked to use what the statement is about as the subject, and a fact about user is kept only when the text says user (since 1.0.12).
  • Empty list? Wait a few seconds and call get_facts() again. Still empty after a minute? See Add a memory.

Memories and facts

flowchart LR
    M["Memories<br/>texts you write"]
    F["Facts<br/>with dates"]
    R["run_id label"]

    M -->|"extraction model reads"| F
    R -.- M
    style R stroke-dasharray: 5 5
The dashed box is the run_id label, a label on a memory.
What it holdsHow long it lasts
MemoriesThe texts you writeUntil you delete them or close your Korely account
FactsSubject, predicate and object, with their datesA closed fact stays as history until you close your Korely account or erase the end user (see Forget an end user)
run_id labelA label on a memory, no data of its ownAs long as the memory

Memories

search() finds memories by meaning, even when no word matches. get_all() reads back every memory of an end user, page by page.

Facts

You read facts with get_facts(). For each new fact, the contradiction check asks Korely's extraction model whether it replaces an older one. If it does, Korely closes the old fact and keeps it as history. Illustration of the format, where valid_from and invalid_at are when a fact became true and when it stopped:

(Giulia, subscribes_to, Starter plan) valid_from 2026-03-01 invalid_at 2026-06-07
(Giulia, subscribes_to, Advanced plan) valid_from 2026-06-07 invalid_at null

invalid_at null means the fact is still true. By default, get_facts() returns only open facts. With include_invalidated=True, closed facts come back too. Temporal facts shows how to read the facts true on a past date.

get_profile() lists the current facts of one end user. correct_fact() replaces a wrong fact. forget_fact() closes a fact at once: get_facts(), get_profile() and the facts in get_context() stop returning it, while include_invalidated=True still shows it.

forget_fact() touches the fact only: it becomes a closed fact, and the memory it came from stays, so search() and get_context() can still return that text. delete() takes the memory out of reads (see Delete a memory). Erasing the end user removes both.

The run_id label

Pass the same run_id to every add() of one long task, such as a research session. A later search() or get_all() with that run_id returns only those memories. get_context() and get_facts() have no run_id: they cover every session of the end user.

How a write flows

When an agent calls add():

  1. Korely saves the memory. Normally search() can find it as soon as the call returns; under a burst the vector is computed a little later and search finds it once it exists.
  2. After the call returns, Korely's extraction model extracts the facts, in about ten seconds.
  3. Each new fact goes through the contradiction check.

Add a memory explains how to follow a memory while Korely works on it.

How a read flows

Korely's extraction model runs only when you write: reading calls no language model. To find similar memories, Korely compares embeddings, lists of numbers that stand for the meaning of each text.

get_context() keeps the context block within a size limit, the token budget. search() returns the most similar memories first, each with a score. Search memories explains the score and how to drop weak matches.

Which call touches what

SDK method / REST callTouches MemoriesTouches FactsWhat it does
get_context() / GET /v1/contextReadReadBuilds the context block
search() / POST /v1/memories/searchReadFinds similar memories
get_all() / GET /v1/memoriesReadLists memories
add() / POST /v1/memoriesWriteWriteSaves a memory, then extracts facts
update() / PATCH /v1/memories/{id}WriteWriteChanges a memory and re-extracts facts; facts the new text no longer states are closed (Update a memory)
delete() / DELETE /v1/memories/{id}WriteDeletes one memory
get_facts() / GET /v1/factsReadLists facts
get_profile() / GET /v1/profileReadLists one end user's current facts
add_fact_triple() / POST /v1/factsWriteWrites one fact (subject, predicate, object) directly
correct_fact() / PATCH /v1/facts/{id}WriteReplaces a wrong fact
forget_fact() / POST /v1/facts/{id}/forgetWriteCloses a fact

The Node.js SDK spells the same calls differently, such as getContext. The API reference has every call in full.

Write what is true now.

Say "Giulia is on the Advanced plan", not "Giulia upgraded to Advanced". The second says something that already ended, so its fact can have no time when it is true now: Korely then stores it closed from the start. A plain get_facts() leaves out a fact such as Giulia / upgrades_to / Advanced plan; get_facts(include_invalidated=True) returns it. That case needs no contradiction check.

Details

  • Narrowing a read. An API key reads only its own project. Inside it, user_id narrows a read to one end user, run_id to one session (on search() and get_all() only), and agent_id to what one agent wrote, such as support-bot.
  • Regions. Two separate things. Where your data is stored: on Korely's servers in Helsinki, on every Cloud plan. Which model reads it to write the facts: the project's region decides. global, the default, uses a model from Google, outside the EU; eu uses Scaleway in Paris (Data governance: regions).

Known limits

  • A memory can finish with no facts, and no error.
  • The contradiction check can miss a conflict. Check important facts with get_facts(include_invalidated=True).
  • In the eu region, a past-tense change such as "Tom moved to Munich" can leave the old fact ("Tom lives in Berlin") open, so write the new state in the present tense (see Write what is true now).

See also