Korely

Core operations

Update a memory

Change the text of a saved memory. Korely runs the extraction again and updates its facts.

update() replaces the whole text. To save a new memory, use add().

Quick start

  • Python 3.9 or later and pip install korely-memory (checked with 0.1.28).
  • An API key (kor_live_...): get a free API key (Quickstart). Pass it as api_key, or set KORELY_API_KEY and call Korely().
  • Node.js 18 or later and npm install korely-memory (checked with 0.1.17): the code is under Example.

The code adds a memory for the end user customer-4812, then replaces its text.

from korely_memory import Korely
korely = Korely(api_key="kor_live_...")
mem = korely.add("Northwind Hosting costs 50 euro per month.", user_id="customer-4812")
new = korely.update(mem.id, content="Northwind Hosting costs 55 euro per month.")
print(new.status, new.facts) # processing []
  • It prints the status processing and no facts: like add(), update() replies before the extraction ends.
  • update() runs right after add(), before the first extraction ends; the facts come from the new text.

About ten seconds later, add these lines to the same script:

m = korely.get(mem.id)
print(m.status, [(f.predicate, f.object) for f in m.facts])

You see ready and one fact: 55 euro per month. Reading a memory shows only its open facts.

Check the facts after every update. Read the memory again. If no fact is open, update again with one plain sentence in the present tense.

Why: an update reads the text again, and the facts that the new text no longer states are closed. When the extraction finds no facts in the new text, the update closes all the old facts, so an update with the same text does not promise to leave the facts as they are. Korely reports no error, so only the facts show it. This happens:

  • with a text that states no facts, such as "Thanks, see you tomorrow.";
  • sometimes with a good text, if your key's project is in the eu region (the default is global).

If you see something else:

  • Still processing: wait and run the get() and print() lines again.
  • error: the extraction failed. The new text is stored; the old facts stay open. A new update puts the memory back in the queue and the extraction runs again on the new text.
  • NotFoundError (404): wrong id, or a memory of another project.
  • AuthenticationError (401): the key is missing, wrong, revoked or expired.

Terms

Beyond the Glossary, this page adds two terms.

  • Open fact: a fact that is still true, the opposite of a closed fact.
  • Version check: you send expected_updated_at, and Korely refuses the update with 409 stale_write if the memory changed since you read it.

Reference

How it works

flowchart LR
    A([update]) --> B[Replace text]
    B -.-> C[Extract facts]
    C -.-> D{Old fact}
    D -->|stated again| K[Stays open]
    D -->|contradicted| X[Closed]
    D -->|no longer stated| Y[Closed]
Dotted steps run after the reply.
  • A language model decides which old facts the new facts contradict.
  • Tip: write the new text as a current state ("costs 55 euro per month"), not as a story of a change.
  • A fact that another memory also states stays open, unless your update gives it a new value. Two memories say Northwind costs 50 euro; you update one to "costs 55 euro"; the 50 euro fact closes (invalidated_by is the 55 euro fact) and the other memory keeps its text.

Request

Endpoint: PATCH /v1/memories/{id}. SDK: korely.update(memory_id, *, content, expected_updated_at=None).

ParamTypeNotes
idstringRequired, in the URL: the id field of the add() response.
contentstringRequired. The new text, at most 16,000 characters. Spaces at the start and end are removed. Markdown is kept as plain text.
expected_updated_atstringOptional version check. Send the memory's updated_at as you read it. If it is null (never updated), send created_at.

Any other field, such as user_id, returns 422: an update cannot change what add() set. Delete the memory and add it again.

Example

The code updates with the version check, waits for the extraction, then reads the facts. entity in get_facts is the subject or object to look up (entity).

import time
from korely_memory import Korely, StaleWriteError
korely = Korely(api_key="kor_live_...")
mem = korely.add("Northwind Hosting costs 50 euro per month.", user_id="customer-4812")
try:
korely.update(mem.id, content="Northwind Hosting costs 55 euro per month.",
expected_updated_at=mem.updated_at or mem.created_at)
except StaleWriteError:
raise # 409: read the memory again, then retry
for _ in range(30):
if korely.get(mem.id).status != "processing":
break
time.sleep(2)
for f in korely.get_facts(entity="Northwind Hosting", user_id="customer-4812"):
print(f.predicate, f.object, f.valid_from)

What it prints:

  • One line: costs 55 euro per month, then the time of the update.
  • Open facts only, unless you pass include_invalidated=True.
  • If the loop ends on processing, read again later.

In Node.js, methods and the constructor key are camelCase (getFacts, apiKey), option names are snake_case (user_id, expected_updated_at). A camelCase option fails before the request.

Fields of a fact:

  • valid_from: when the fact became true (valid time): the timestamp of the original add if it had one, otherwise the date the text states, otherwise the time of the update. An update cannot change the timestamp (422).
  • invalid_at: when the fact stopped being true; null while it is open.
  • invalidated_by: the id of the fact that closed this one; null while the fact is open. Closed facts can also have null: for example when an update's new text states no facts. Use invalid_at to know if a fact is closed.

Response

{
"id": "mem_8f2c1a4d9b3e47a6b05e2c7d1f3a9e84",
"content": "Northwind Hosting costs 55 euro per month.",
"user_id": "customer-4812",
"agent_id": null,
"run_id": null,
"client": null,
"kind": "note",
"metadata": {},
"status": "processing",
"created_at": "2026-06-07T09:14:00.412345+00:00",
"updated_at": "2026-06-09T16:02:00.118201+00:00",
"facts": []
}
FieldTypeMeaning
user_id, agent_id, run_id, kind, metadataas set by add()Unchanged by an update (see Add a memory).
clientstring or nullThe label of the tool that added the memory, such as korely-python/0.1.28. The SDKs send it; cURL sends none unless you set the X-Korely-Client header.
statusstringAlways processing here.
updated_atstringThe time of this update. Send it as your next expected_updated_at.
factsarrayAlways empty here; read facts with get_facts.

Concurrency

Programs A and B read the same memory. A saves "55 euro", then B saves "60 euro". Without expected_updated_at, both succeed and A's text is lost, with no error.

  • With expected_updated_at, B gets 409 stale_write; the SDKs raise StaleWriteError.
  • After a 409, read the memory again (GET /v1/memories/{id}), check its text, and retry with the new updated_at.
  • After a timeout, your first update may have been saved, so a retry gets 409. Handle it the same way.
  • Copy the string you read. A value that is not a timestamp like 2026-06-07T09:14:00+00:00 returns 422.
  • In Node.js, do not rebuild the value with new Date(): it drops digits, and every call returns 409.

Errors

Check the code, not the message.

StatusCode stringWhen it occurs
401invalid_keyThe key is missing, wrong, unknown, revoked or expired.
403forbiddenThe key has no permission to write memories.
404not_foundWrong id, deleted, or in another project.
409stale_writeThe memory changed since you read it.
422invalid_requestcontent missing, blank or too long, an unknown field, or a bad expected_updated_at.
429rate_limit_exceededOver the rate limit. Wait the seconds in the Retry-After header.
429quota_exceededMonthly writes used up; see Quota.

Quota

  • Your plan sets your monthly writes (Pricing).
  • An update costs one write per 6,000 characters of new text, rounded up, so 3 at most.
  • An update refused with 404 or 409 costs nothing.
  • The same text sent twice costs twice.
  • Once an update would pass your monthly writes plus a 10% margin, Korely refuses it with quota_exceeded and saves nothing.
  • resets_at in the error says when the count restarts: the 1st of the month, UTC.
  • The rate limit counts every request of your account, refused ones too.

Details

  • Closed facts stay (get_facts returns them with include_invalidated=True) until an erasure of the memory, agent, end user or account (Forget a user).
  • The old text is not kept. Save a copy first if you need it.

Related