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 asapi_key, or setKORELY_API_KEYand callKorely(). - 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 Korelykorely = 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
processingand no facts: likeadd(),update()replies before the extraction ends. update()runs right afteradd(), 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 a text that changed does not promise to leave the facts as they are. (Since 1.0.9 an update with exactly the text the memory already has changes nothing.) Korely reports no error, so only the facts show it. This happens:
If you see something else:
- Still
processing: wait and run theget()andprint()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 with409 stale_writeif 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] - 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_byis 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).
| Param | Type | Notes |
|---|---|---|
id | string | Required, in the URL: the id field of the add() response. |
content | string | Required. The new text, at most 16,000 characters. Spaces at the start and end are removed. Markdown is kept as plain text. |
expected_updated_at | string | Optional 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 timefrom 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 originaladdif 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;nullwhile it is open.invalidated_by: the id of the fact that closed this one;nullwhile the fact is open. Closed facts can also havenull: for example when an update's new text states no facts. Useinvalid_atto 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": []}| Field | Type | Meaning |
|---|---|---|
user_id, agent_id, run_id, kind, metadata | as set by add() | Unchanged by an update (see Add a memory). |
client | string or null | The 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. |
status | string | Always processing here. |
updated_at | string | The time of this update. Send it as your next expected_updated_at. |
facts | array | Always 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 gets409 stale_write; the SDKs raiseStaleWriteError. - After a 409, read the memory again (
GET /v1/memories/{id}), check its text, and retry with the newupdated_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:00returns 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.
| Status | Code string | When it occurs |
|---|---|---|
401 | invalid_key | The key is missing, wrong, unknown, revoked or expired. |
403 | forbidden | The key has no permission to write memories. |
404 | not_found | Wrong id, deleted, or in another project. |
409 | stale_write | The memory changed since you read it. |
422 | invalid_request | content missing, blank or too long, an unknown field, or a bad expected_updated_at. |
429 | rate_limit_exceeded | Over the rate limit. Wait the seconds in the Retry-After header. |
429 | quota_exceeded | Monthly 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. Since 1.0.9 the second one does not read the memory again (no model is called), but it still counts as a write against your monthly writes.
- Once an update would pass your monthly writes plus a 10% margin, Korely refuses it with
quota_exceededand saves nothing. resets_atin 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_factsreturns them withinclude_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
- Temporal facts: how facts open and close over time.