Context & facts
Correct a fact
A correction writes a new fact and closes the old one. It is not an edit: the old row keeps its dates and gains a pointer to the new one, so as_of before the correction still returns what you believed then.
/v1/facts/{fact_id}
SDK: korely.correct_fact(fact_id, object=...). A store that
edits a row in place cannot answer "what did we think in June", and
answering that is the point of keeping history.
Use it on a fact that is wrong now. The new value counts
from the moment of the correction, not from earlier. To say what was true
earlier, use the valid_from field of
Write a fact. To end a fact with no
replacement, use Close a fact.
A correction asks a language model to check the new fact against the open
facts of the same end user (the contradiction check).
If no model answers, you get 503 and nothing is written: send the request again.
Since 1.0.12 the check depends on who chose the subject. If the correction gives subject, or corrects a fact you wrote (with Write a fact, or with an earlier correction that gave subject or corrected a fact you wrote), it is compared only with the facts about that subject, as a written fact is. If it gives only predicate or object of a fact read from a memory, it keeps the subject the extraction wrote and is compared like an extracted fact, as every correction was up to 1.0.11. A predicate you send in such a correction is treated like an extracted one too: Korely may group it under a relation it already uses with the same meaning, so predicate can come back with that relation's name (predicate_raw keeps yours). To have a correction compared only with its own subject, and to keep your predicate as you wrote it, give subject.
A correction can be undone by an edit. The new fact keeps the memory of the old fact as its source. If someone edits that memory, Korely reads its new text again and the old value can come back. If someone deletes it, the corrected fact is closed too.
Before you run an example: install the SDK (pip install korely-memory,
checked with 0.1.28; npm install korely-memory, checked with 0.1.17) and set your key.
fct_a1 in the examples stands for the id of a current fact of yours: read one with
Get facts, or take the id a
Write a fact call returned.
Authentication
HTTP header, required: Authorization: Bearer kor_live_.... The key must carry the memories:write scope.
Request body
At least one of the three. Anything you leave out is carried over from the fact you are correcting.
| Field | Type | Required | Description |
|---|---|---|---|
subject | string | Optional | Corrected subject. 1-200 characters. |
predicate | string | Optional | Corrected predicate; Korely writes it in its standard form (works at becomes works_at). 1-200 characters. |
object | string | Optional | Corrected object. 1-500 characters. |
Any other field is refused with 422 invalid_request.
Example request
curl -X PATCH https://api.korely.ai/v1/facts/fct_a1 \ -H "Authorization: Bearer kor_live_..." \ -H "Content-Type: application/json" \ -d '{"object": "Basic plan"}'Response
200 OK. The new fact, with the id of the one it superseded.
{ "id": "fct_a2", "subject": "customer-giulia-4812", "predicate": "subscribes_to", "object": "Basic plan", "predicate_family": "financial", "valid_from": "2026-06-20T09:14:02+00:00", "invalidated": ["fct_a1"], "tense": "current", "invalid_at": null, "observation_count": 1, "user_id": "customer-giulia-4812", "agent_id": "support-bot"}
The write shape of Write a fact:
invalidated lists the corrected fact first, then any other fact the
contradiction check closed. The old fact is closed at the
moment the new one begins, and a later read shows it with
invalidated_by set to the new id.
| Field | Type | Description |
|---|---|---|
id | string | Public id of the new fact (fct_ prefix). It differs from the id you sent, unless the body restated the fact as it is (see Notes). |
subject | string | The subject of the new fact: the one you sent, else the old one. |
predicate | string | The predicate of the new fact, in its stored form (a corrected predicate is normalized like a written one). |
object | string | The object of the new fact: the one you sent, else the old one. |
predicate_family | string · null | The predicate family of the new fact. |
valid_from | string · null | ISO 8601, when the new fact begins: the moment of the correction, or the start of the old fact when that is still ahead. |
invalidated | array<string> | Ids of the facts this correction closed: the corrected fact first, then any other the check closed. Empty when the body restated the fact as it is. |
tense | string | What the fact says about time: current, past or planned. A correction gives current. |
invalid_at | string · null | ISO 8601, when the new fact stopped being true, or null while it is open. |
observation_count | integer | How many times the fact has been stated. 1 for a new fact. |
user_id / agent_id | string · null | The scope of the old fact, which the new one keeps. |
Only a current fact is corrected
Correct the fact that holds now. A fact that is history (superseded by a
newer one, forgotten, or ended) answers 409 fact_not_current.
current_fact_id is the fact to correct instead, so a client
retries on it without reading the message:
{ "detail": {"code": "fact_not_current", "message": "This fact is no longer current, so it cannot be corrected. It was superseded by fct_a2: correct that one instead.", "current_fact_id": "fct_a2"}, "code": "fact_not_current", "message": "This fact is no longer current, so it cannot be corrected. It was superseded by fct_a2: correct that one instead.", "current_fact_id": "fct_a2"}
When no fact holds now, current_fact_id is null and
the message says to write the value that holds now with POST /v1/facts. To
change what was true in the past, write what happened with its dates
there; a correction never closes the value in force because of an old row.
Errors
| Status | Code | Cause |
|---|---|---|
401 | invalid_key | Missing or invalid kor_live_ key. |
403 | forbidden | The key lacks the memories:write scope. |
404 | not_found | No fact with that id: the id is malformed or unknown, or the fact is in another project (No fact with that id.). No write is counted. If the month's writes are already used up, a well-formed unknown id gets the 429 first. |
409 | fact_not_current | The fact is no longer current: superseded, forgotten or ended. current_fact_id is the current fact, null when there is none. Nothing is written and no write is counted. If the month's writes are already used up, the 429 comes first. |
413 | payload_too_large | The body is larger than 2,097,152 bytes. Refused before it is read; nothing changes. On the Cloud, a body over 1 MB is refused first with a plain 413 that has no code. |
422 | invalid_request | The body is empty or every field is blank (Give at least one of subject, predicate or object.), a field is over its length limit, or a field is unknown. |
429 | quota_exceeded | The month's writes are used up (Korely allows 10% over your plan first). Nothing changes. detail carries limit, used and resets_at. |
429 | rate_limit_exceeded | Per-minute, per-hour or per-day request limit. Carries Retry-After. |
408 | request_timeout | The body did not arrive whole within 30 seconds. Nothing changes; send it again. |
503 | unavailable | The key could not be checked because the database did not answer, or the request waited past one of the store's deadlines. Nothing was written; retry after the seconds in Retry-After. |
503 | model_unavailable | The model that resolves the relation, or the contradiction check, gave no usable answer. Nothing was written; retry. |
503 | service_paused | The service itself does not call the models now (for example a spending limit, or a problem at the model provider). Carries Retry-After: 3600: a retry within the hour cannot succeed. Nothing was written. |
Notes
- An empty body is refused with
422. A body that restates the open fact as it is answers200with the sameidandinvalidated: [], and still uses one write. - It counts as one write. A correction uses one write of your monthly quota, like any
PATCH; past the quota it answers429 quota_exceededand nothing changes. A404or a409costs nothing; with the month's quota spent, a429comes first. - Scoped to your key's project. A fact in another project answers
404, the same answer as an id that never existed. - Both stay readable. The old fact comes back with
include_invalidated=trueand in anyas_ofread before the correction. - Webhook and audit. Each fact in
invalidatedsends afact.invalidatedwebhook, and the correction writes one audit row.