Korely

Concepts

Temporal facts

Every fact has two dates: when it became true and when it stopped. When a new fact contradicts an old one, Korely closes the old one: it sets an end date on it and keeps it.

Korely turns what your agents save into facts. A fact has three parts: a subject, a relation (called predicate in the API) and an object, such as customer-4812 / subscribes_to / Business plan.

Terms

The Glossary defines the shared words. This page adds four.

  • Judge: a language model inside Korely that checks each write for contradictions with older facts.
  • valid_from: the date a fact became true.
  • invalid_at: the date it stopped; empty while it is true. The two make the valid time.
  • known_as_of: a read parameter for what Korely knew on a date.

Quick example

You need Python 3.9 or later and pip install korely-memory (checked with SDK 0.1.28). Get a key at Get a free API key (Quickstart), then run export KORELY_API_KEY=kor_live_....

The code writes two plans for the end user customer-4812. The customer is both the fact's subject and the end user it belongs to (user_id); they can differ. It writes Starter from 1 May 2026, then Business from 1 June 2026, and reads both back.

from korely_memory import Korely
korely = Korely() # reads KORELY_API_KEY from the environment
korely.add_fact_triple("customer-4812", "subscribes_to", "Starter plan", user_id="customer-4812", valid_from="2026-05-01")
new = korely.add_fact_triple("customer-4812", "subscribes_to", "Business plan", user_id="customer-4812", valid_from="2026-06-01")
print(new.invalidated)
for f in korely.get_facts(user_id="customer-4812", include_invalidated=True):
print(f.object, f.valid_from, f.invalid_at)

You see this (ids are longer):

['fct_ad36...']
Business plan 2026-06-01T00:00:00+00:00 None
Starter plan 2026-05-01T00:00:00+00:00 2026-06-01T00:00:00+00:00
  • First line. new.invalidated holds the id of the Starter plan fact: the judge read the new plan as a replacement.
  • Second line. The Business plan is current: invalid_at is empty.
  • Third line. The Starter plan closes on 2026-06-01, when the Business plan began.

Without include_invalidated=True, you get only the Business plan. More filters: Get facts, the API reference.

If invalidated is empty, the judge saw no contradiction: close the old fact yourself with forget_fact. If a write fails with 503 model_unavailable (the model could not be reached), nothing was written: try again.

To run it again, use a new id in place of customer-4812.

How a new fact closes an old one

Korely checks whether a new fact contradicts an old one. If it finds a contradiction, it closes the old fact with a date instead of deleting it.

At each write, Korely takes two steps.

  1. Find possible conflicts. Korely looks at the open facts of the same end user, whichever agent wrote them. It takes:
    • the same subject and relation: costs 40 and costs 50;
    • the same subject and object with another relation: likes tea and dislikes tea;
    • the same relation with another subject, only when you pass user_id: "the user" and "Maria" can be one person, so a new fact can replace one written under another name. Without user_id this check is skipped. Since 1.0.12 it runs only between facts read from memories (see below).
    Up to 10 facts nearest in meaning, found as in a search, are checked too (for a fact you write yourself, since 1.0.12, only facts about the same subject: see below).
    Since 1.0.12, a fact you write yourself is compared only with facts about the same subject. For a fact written with POST /v1/facts (the Python SDK's add_fact_triple), or a correction that gives its subject (Correct a fact has the full rule), the judge sees only the earlier facts with the same subject, and the facts that link the same two things in the other direction. So a fact you write about one deploy no longer closes a fact about another deploy. "Maria" and the end user's user_id count as one subject only after you declare the name, with the same agent_id. It works the other way too: a fact read from a memory is compared with a fact you wrote only when both have the same subject, or link the same two things in the other direction. Declaring a name later does not check again the facts already written: two values written before it both stay true, and a new value written later is checked against both. Two facts that the extraction reads from memories are still compared with every check above.
    Since 1.0.13, a fact read from an agent's own memory is compared only with facts about the same subject. This is a memory written with an agent_id and no user_id, such as a team's shared memory or an agent's own notes. The judge reads the earlier facts about the same subject, as Korely names it after the aliases of the agent that writes, and the facts that link the same two things in the other direction. Until 1.0.12 it also read the ten facts nearest in meaning, whatever their subject, and it sometimes closed facts that were both true ("the SDK repository has license MIT" closed by "the license is currently Apache 2.0"). A memory with a user_id is judged as before.
    What this costs. Two spellings of one thing ("Cloud" and "Korely Cloud") are two subjects, so a new value of one no longer closes the old value of the other, and a right closure of that kind is lost with the wrong ones. To make them one subject, declare one as an alias of the other with POST /admin/aliases, in the namespace of the agent that writes, with its agent_id. An alias belongs to one agent: to make two spellings one subject for several agents, declare it for each of them. Every alias counts against the account's 1,000 declared names. Facts from different agents of one project are still compared when they name the subject the same way. A fact written with neither id and an agent's fact are compared with each other only when they are about the same subject, in whichever order they arrive, so a change told by one of them under another name leaves the other's value open. Facts written with no ids are compared among themselves as before. An agent_id that is empty or only whitespace is no agent: that memory is judged as one with no ids.
    What is not measured. How many closures the new rule adds or loses with a real judge has not been measured. Facts already written, and those already closed, stay as they are. A fact closed wrongly stays readable with include_invalidated=true; write it again with POST /v1/facts to make it true again.
  2. Judge. The judge reads the new fact next to these candidates and decides which ones it contradicts. Two plans for one subscription cannot both be true. "Likes coffee" and "likes tea" can.

The old fact ends when the new one begins (the moment of the write, if it has no date).

The judge can be wrong. It can miss a contradiction or close a wrong fact. It may not read a change told in the past tense, such as "Tom moved to Munich", as a replacement, so the earlier fact can stay open. Check with get_facts and fix it with correct_fact or forget_fact.

Where valid_from comes from

add() saves free text (a memory) and Korely extracts its facts. add_fact_triple, used above, saves one fact already in its three parts.

Korely uses the first that exists: the date you pass (valid_from in add_fact_triple, timestamp in add()); a date with a year in the text ("Since June 2026" becomes 2026-06-01); the moment of the write.

When several memories carry the same event time, for example dates with no hour, Korely takes them in this order: the event time, then the moment its store received each one, then the id. So "moved to Paris" saved after "lives in Rome" is judged after it, and the newer fact stays open. The limit: memories saved in the same database transaction tie on the received moment, and the id decides. This holds since 1.0.0.

Add a memory, from text to facts, Write a fact, the API reference.

Read the past: as_of and known_as_of

  • as_of: what was true on a date, as Korely knows it today. It reads valid_from and invalid_at.
  • known_as_of: what Korely knew on a date. It reads created_at, when Korely learned the fact, and closed_known_at, when Korely learned that it had ended.

The answers differ when a change is told late. Korely knows customer-4812 lives in Milan. On 5 October an agent learns of a move to Turin on 1 September. It saves the present-tense note "customer-4812 lives in Turin" with add(), passing the date of the move in the timestamp parameter: timestamp="2026-09-01".

The Turin fact has created_at 5 October, when Korely learned it. The Milan fact ends on 1 September (invalid_at), but its closed_known_at is 5 October: Korely learned of the end only then.

  • as_of="2026-09-20" returns Turin: on 20 September the customer already lived there.
  • known_as_of="2026-09-20" returns Milan: on 20 September Korely did not know of the move yet.

as_of on the Quick example

as_of returns the facts true on that date, each with the end date it has today. A date without a time means midnight UTC.

Run this after the Quick example, in the same shell, for what was true on 15 May.

Terminal window
curl -G https://api.korely.ai/v1/facts \
-H "Authorization: Bearer $KORELY_API_KEY" \
--data-urlencode "user_id=customer-4812" \
--data-urlencode "as_of=2026-05-15"
# 200 OK (some fields left out)
# {"facts": [{"object": "Starter plan",
# "valid_from": "2026-05-01T00:00:00+00:00",
# "invalid_at": "2026-06-01T00:00:00+00:00"}], "total": 1}

The Starter plan was true on 15 May, so it returns with its end date, 1 June.

Correct or forget a fact

  • correct_fact is for a current fact with a wrong value: the new value counts from now on, the old fact is closed.
  • forget_fact closes a fact with no replacement; its optional at is the date it stopped being true.
  • delete_all(user_id=...) erases every memory and fact of one end user, in the project of your API key. Database backups made before the call still hold the data.

Deleting the memory that closed a fact can open the fact again, as a new fact (Delete a memory says when). Human in the loop: how end users read, correct and forget their facts.

Related