Korely

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.

PATCH /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.

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.

FieldTypeRequiredDescription
subjectstringOptionalCorrected subject. 1-200 characters.
predicatestringOptionalCorrected predicate; Korely writes it in its standard form (works at becomes works_at). 1-200 characters.
objectstringOptionalCorrected object. 1-500 characters.

Any other field is refused with 422 invalid_request.

Example request

Terminal window
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.

FieldTypeDescription
idstringPublic 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).
subjectstringThe subject of the new fact: the one you sent, else the old one.
predicatestringThe predicate of the new fact, in its stored form (a corrected predicate is normalized like a written one).
objectstringThe object of the new fact: the one you sent, else the old one.
predicate_familystring · nullThe predicate family of the new fact.
valid_fromstring · nullISO 8601, when the new fact begins: the moment of the correction, or the start of the old fact when that is still ahead.
invalidatedarray<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.
tensestringWhat the fact says about time: current, past or planned. A correction gives current.
invalid_atstring · nullISO 8601, when the new fact stopped being true, or null while it is open.
observation_countintegerHow many times the fact has been stated. 1 for a new fact.
user_id / agent_idstring · nullThe 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

StatusCodeCause
401invalid_keyMissing or invalid kor_live_ key.
403forbiddenThe key lacks the memories:write scope.
404not_foundNo 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.
409fact_not_currentThe 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.
413payload_too_largeThe 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.
422invalid_requestThe 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.
429quota_exceededThe month's writes are used up (Korely allows 10% over your plan first). Nothing changes. detail carries limit, used and resets_at.
429rate_limit_exceededPer-minute, per-hour or per-day request limit. Carries Retry-After.
408request_timeoutThe body did not arrive whole within 30 seconds. Nothing changes; send it again.
503unavailableThe 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.
503model_unavailableThe model that resolves the relation, or the contradiction check, gave no usable answer. Nothing was written; retry.
503service_pausedThe 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 answers 200 with the same id and invalidated: [], 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 answers 429 quota_exceeded and nothing changes. A 404 or a 409 costs nothing; with the month's quota spent, a 429 comes 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=true and in any as_of read before the correction.
  • Webhook and audit. Each fact in invalidated sends a fact.invalidated webhook, and the correction writes one audit row.