Korely

Core memory operations

Update a memory

Replace a memory's text. Korely extracts the facts again from the new text: a changed value closes the old fact, a restated fact stays open, and a fact the new text no longer states is closed.

PATCH /v1/memories/{memory_id}

SDK: korely.update(memory_id, content=...). The edit replaces the whole text of one memory and runs the extraction again on it, with the same contradiction check as a new write. Three things can happen to an open fact of the memory: the new text restates it (it stays open), the new text gives the same subject and relation another value (it is closed, and its invalidated_by names the new fact), or the new text no longer states it (it is closed at the time of the edit, unless another memory also states it). You write no code to close old facts. The guide shows the call with a runnable example.

Authentication

HTTP header, required: Authorization: Bearer kor_live_.... The key must carry the memories:write permission.

Path parameter

ParameterTypeRequiredDescription
memory_idstringRequiredThe memory id (mem_ and 32 hex characters) to update, as Add a memory returned it. A malformed id returns 404.

Request body

FieldTypeRequiredDescription
contentstringRequiredNew content. 1-16,000 characters. Stripped and rejected if blank or whitespace-only (422).
expected_updated_atstringOptionalVersion check: the updated_at you last read, or created_at if updated_at is null, sent back exactly as the API returned it. The edit is written only if the memory is still at that version, to the microsecond, when it is written; otherwise 409 stale_write and nothing changes. A value that cannot be read as a date, the empty string included, is a 422. A date with no time of day, or a time cut to the millisecond, is another version: 409. Leave the field out for an edit without the check.

Any other field is refused with 422 invalid_request (a memory's user_id, agent_id, run_id and metadata do not change with an edit). Field names are snake_case.

Example request

Terminal window
curl -X PATCH https://api.korely.ai/v1/memories/mem_8f2c1a \
-H "Authorization: Bearer kor_live_..." \
-H "Content-Type: application/json" \
-d '{
"content": "Giulia now prefers in-person standups.",
"expected_updated_at": "2026-03-01T09:14:22.418093+00:00"
}'

Response

200 OK. The updated memory. On the Cloud the extraction runs after the reply, so the response always carries status processing and an empty facts list; the facts follow about ten seconds later. A Self-hosted install started with WRITE_ASYNC=false (an environment variable of the server) extracts inside the call and returns them.

{
"id": "mem_8f2c1a",
"content": "Giulia now prefers in-person standups.",
"user_id": "customer-giulia-4812",
"agent_id": "onboarding-bot",
"run_id": null,
"client": null,
"kind": "note",
"metadata": {"source": "slack"},
"status": "processing",
"created_at": "2026-03-01T09:14:22.418093+00:00",
"updated_at": "2026-03-02T11:02:10.183112+00:00",
"facts": []
}
FieldTypeDescription
idstringThe public memory id (mem_).
contentstringThe updated content, echoed back.
user_idstring · nullThe end user this memory belongs to (null if none).
agent_idstring · nullThe agent (agent_id; null if none).
run_idstring · nullThe run (run_id; null if none).
clientstring · nullThe tool that wrote it, from the X-Korely-Client header of the write or the name an MCP client gave when it connected; a label, not a namespace.
kindstringnote, or session for the log of a work session (the kind of the write).
metadataobjectThe metadata dict stored with the memory.
created_atstring · nullISO 8601 timestamp of the first write. It never changes.
updated_atstring · nullISO 8601 timestamp of this edit. It changes on every edit, even when the text is the same.
statusstringThe memory's status: processing while the new facts are being extracted, then ready (or error, see When extraction fails). The edit answers processing as POST /v1/memories does.
factsarrayAlways [] on the Cloud. A Self-hosted install with WRITE_ASYNC=false fills it with the facts of the new text, in the write shape of Add a memory. Read the facts later with Get a memory or Get facts; a fact the edit closed shows invalid_at and, when a new fact replaced it, invalidated_by.

Errors

StatusCodeCause
401invalid_keyMissing or invalid kor_live_ key, Invalid or missing API key: ..., then what is wrong.
429quota_exceededAn edit extracts again and counts the writes of its new text (one per 6,000 characters, at least one): past the monthly write quota it is refused and nothing changes. An edit that ends in 404 or 409 counts nothing.
403forbiddenThe key lacks the memories:write permission; message names it.
404not_foundMalformed memory_id, or no such memory in this key's project (including when checked against expected_updated_at), Memory not found.
409stale_writeexpected_updated_at was provided but doesn't match the current record, expected_updated_at does not match the current record.
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 gets a bare 413 first, without this code.
422invalid_requestcontent is empty or whitespace-only, or its length is outside 1-16,000 characters; or expected_updated_at cannot be read as an ISO 8601 date or time; or the body has a field other than content and expected_updated_at.
429rate_limit_exceededPast the rate limit. Wait as long as Retry-After says.
408request_timeoutThe body did not arrive whole within 30 seconds. Nothing changes; send it again.
500internal_errorThe server failed on this request; the message holds no detail. Read the memory again before you retry.
503unavailableThe database did not answer, or another change to the same records held this request more than 5 seconds. Nothing changes. Retry after the seconds in Retry-After.

Notes

  • It's a write. Update requires the memories:write scope and does not meter the read query quota.
  • On a memory in error. An edit puts the memory back in the queue, and the extraction runs again on the new text. If it fails again, the memory is in error again and its old facts stay open.
  • A new value closes the old fact. If the new text gives a fact a new value, the old fact is closed (invalid_at is set, invalidated_by is the new fact), even when another memory still states the old value. A fact the new text no longer states is closed at the time of the edit, unless another memory also states it. Write the new text as a current state (“costs 55 euro per month”), not as a story of a change.
  • An empty extraction closes the facts. If the model finds no fact in the new text, the edit closes every fact of the memory. The memory is still ready and no error is reported, so read the facts after an edit. Since 1.0.9, an edit with the text the memory already has, once extracted, changes nothing: no model is called, no fact is written or closed, and the reply is the memory as it is. It still counts as a write against your monthly writes. Facts that such an edit closed before 1.0.9 stay closed. A text that changed by only a comma is read again, and if that read finds no fact it closes the facts, because Korely cannot tell it from an edit that took the value away.
  • When the facts start. A new fact's valid_from is the timestamp of the original add if it had one, otherwise a date the text states, otherwise the time of the edit. The edit cannot change the timestamp.
  • History. Memory history keeps one updated event: the last edit.
  • Optimistic concurrency. expected_updated_at is compared to the microsecond, with no tolerance: two edits made from the same read, even in the same second, cannot both win; the second gets 409 stale_write. The check holds at the write itself, so an edit that lands between your request's check and its write is not overwritten either. Send back the string you read: a value rounded to the millisecond (a JavaScript Date, for example) is another version. A value that cannot be read, or an empty one, is refused with 422 invalid_request, and nothing changes; a date with no time of day is another version and gets the 409.
  • Webhooks. The PATCH emits no memory.created. When re-extraction runs, each fact it closes fires fact.invalidated: invalidated_by is the new fact, or null with reason: "memory_updated" for a fact the new text no longer states.

Related