Core memory operations
Update a memory
Replace a memory's content. Korely re-runs fact extraction on the new text: changed values supersede prior facts, restated facts dedupe, and facts the new text no longer states are closed, so your typed knowledge stays in sync.
/v1/memories/{memory_id}
SDK: korely.update(memory_id, content=...). The update path
re-runs the intelligence, embeddings and typed-fact extraction with
bi-temporal contradiction checking, on the new content. Facts the new text
restates stay active (deduped); facts it contradicts are superseded; facts it
no longer states are closed at the time of the edit, unless another memory
also states them. You never write supersede logic.
Authentication
HTTP header, required: Authorization: Bearer kor_live_.... The key must carry the memories:write scope.
Path parameter
| Parameter | Type | Required | Description |
|---|---|---|---|
memory_id | string | Required | The public memory id (mem_) to update. A malformed id returns 404. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Required | New content. 1-16,000 characters. Stripped and rejected if blank or whitespace-only (422). |
expected_updated_at | string | Optional | Optimistic concurrency token: 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 is not an ISO 8601 instant, the empty string included, is a 422. Leave the field out for an edit without the check. Defaults to null (no concurrency 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
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. By default
(WRITE_ASYNC=true, on the Cloud and on Self-hosted) extraction
runs after the call returns, so the response carries status processing and an empty facts list; the facts
re-extracted from the new content follow a few seconds later. A
Self-hosted install with WRITE_ASYNC=false 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": []}| Field | Type | Description |
|---|---|---|
id | string | The public memory id (mem_). |
content | string | The updated content, echoed back. |
user_id | string · null | The end user this memory belongs to (null if not scoped). |
agent_id | string · null | The agent namespace (null if not scoped). |
run_id | string · null | The run namespace (null if not scoped). |
client | string · null | The 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. |
kind | string | note, or session for the log of a work session (the kind of the write). |
metadata | object | The metadata dict stored with the memory. |
created_at | string · null | ISO 8601 timestamp. |
updated_at | string · null | ISO 8601 timestamp. |
status | string | processing while the new facts are being extracted, then ready (or error). By default extraction runs after the call returns, as on POST /v1/memories: the edit answers processing and the facts follow. |
facts | array | Typed (subject, predicate, object) facts re-extracted from the new content, when extraction ran inside the call, in the write shape of Add a memory; empty while status is processing (read them later with Get memory or Get facts). Changed values supersede prior facts via the contradiction pipeline; restated facts are deduped. |
Errors
| Status | Code | Cause |
|---|---|---|
401 | invalid_key | Missing or invalid kor_live_ key, Invalid or missing API key: ..., then what is wrong. |
429 | quota_exceeded | An edit extracts again and counts the writes of its new text (one per 6,000 characters): past the monthly write quota it is refused and nothing changes. |
403 | forbidden | Key lacks the memories:write scope, API key missing required scope(s): memories:write. |
404 | not_found | Malformed memory_id, or no such memory in this key's project (including when checked against expected_updated_at), Memory not found. |
409 | stale_write | expected_updated_at was provided but doesn't match the current record, expected_updated_at does not match the current record. |
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 gets a bare 413 first, without this code. |
422 | invalid_request | content is empty or whitespace-only, or its length is outside 1-16,000 characters; or expected_updated_at is not an ISO 8601 instant; or the body has a field other than content and expected_updated_at. |
429 | rate_limit_exceeded | Rate limit exceeded, returned with a Retry-After header. |
Notes
- It's a write. Update requires the
memories:writescope and does not meter the read query quota. - Facts stay in sync. The PATCH re-runs fact extraction on the new content: changed values supersede prior facts (their
invalid_atis set), restated facts are deduped, and a fact the new text no longer states is closed at the time of the edit, unless another memory also states it. - Optimistic concurrency.
expected_updated_atis compared to the microsecond, with no tolerance: two edits made from the same read, even in the same second, cannot both win; the second gets409 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 JavaScriptDate, for example) is another version. An unparseable or empty value is refused with422 invalid_request, and nothing changes. - Webhooks. The PATCH emits no
memory.created. When re-extraction runs, each fact it closes firesfact.invalidated:invalidated_byis the new fact, ornullwithreason: "memory_updated"for a fact the new text no longer states.
Related
- Update a memory, guide, the narrative walkthrough with context.
- Add a memory, store a new memory.
- Get facts, read back the re-extracted typed facts.