Korely

Core operations

Update a memory

Replace a memory's content and re-extract its facts in one call.

When you send a PATCH request, Korely replaces the stored content, then runs the full extraction pipeline on the new text: entity detection, typed fact extraction, and the two-stage contradiction check. Facts the new text restates stay active; facts it contradicts are superseded by the incoming facts; facts it no longer states are closed at the time of the edit, unless another memory also states them. On the hosted service this runs right after the call returns: the response carries status: "processing" and an empty facts list, and the new facts land a few seconds later. An update counts against your monthly write quota like an add, one write per 6,000 characters of the new text, because it runs extraction again.

Call this whenever an agent learns that previously recorded information is wrong or outdated, for example, a customer corrects a preference mid-session, or a support case is re-categorised after triage. You do not need to delete the memory and re-add it: update it in place and Korely handles the contradiction resolution automatically.

flowchart LR
  A([PATCH /v1/memories/mem_8f2c1a]) --> B[Replace content]
  B --> C[Re-run extraction]
  C --> D{Contradiction check}
  D -->|supersedes old fact| E[Invalidate fct_b91e]
  D -->|no conflict| F[Keep existing facts]
  E --> G([Facts current, status ready])
  F --> G
Update pipeline: the new content replaces the stored text, the call answers processing, then extraction runs and any contradicted facts are invalidated.

Request

Endpoint: PATCH /v1/memories/{id}. SDK: korely.update(memory_id, *, content, expected_updated_at=None).

ParamTypeNotes
id string Required. The memory ID in the URL path (e.g. mem_8f2c1a).
content string Required. The new plain text or Markdown that fully replaces the existing content. Extraction runs on this text.
expected_updated_at ISO 8601 string Optional. Optimistic concurrency guard. Pass the updated_at value you last read, or created_at if updated_at is null, exactly as the API returned it. Compared to the microsecond, at the write itself: a different or unparseable value returns 409 stale_write and the write is rejected. Omit to skip the check.

Any other field is refused with 422 invalid_request: sending user_id, agent_id, run_id or metadata in a PATCH is an error, not a no-op.

Example

from korely_memory import Korely
korely = Korely(api_key="kor_live_...", region="eu")
# Basic update, new content replaces the old one; facts are re-extracted.
result = korely.update(
"mem_8f2c1a",
content="Northwind Hosting costs 55 euro per month after the storage add-on.",
)
print(result.status) # processing
print(result.facts) # [], the re-extracted facts land a few seconds later
# Return values are dataclasses, use attribute access (result.status,
# result.facts), never result["facts"].
# Later: read the current facts back
facts = korely.get_facts(entity="Northwind Hosting", user_id="customer-4812")
print(facts[0].object) # 55 euro per month (newest first)
# With optimistic concurrency: pass the updated_at you last saw.
# A 409 means someone else wrote to this memory since you read it.
result = korely.update(
"mem_8f2c1a",
content="Northwind Hosting costs 55 euro per month after the storage add-on.",
expected_updated_at="2026-06-07T09:14:00.412345+00:00",
)

Response

{
"id": "mem_8f2c1a",
"content": "Northwind Hosting costs 55 euro per month after the storage add-on.",
"agent_id": "infra-bot",
"user_id": "customer-4812",
"run_id": null,
"metadata": { "source": "slack" },
"status": "processing",
"created_at": "2026-06-07T09:14:00.412345+00:00",
"updated_at": "2026-06-09T16:02:00.118201+00:00",
"facts": []
}

Once extraction has run, GET /v1/memories/mem_8f2c1a returns the memory with status ready and the facts true now. Facts the edit closed are not in it: each has invalid_at set and invalidated_by naming the new fact (null when the new text just stopped saying it). They still exist in the store: read them with GET /v1/facts?include_invalidated=true (or as_of) and in GET /v1/memories/mem_8f2c1a/history. Each closure also fires a fact.invalidated webhook.

Response fields

FieldTypeMeaning
id string The memory's permanent identifier. Never changes across updates.
content string The new content as stored after this write.
agent_id string or null The agent namespace this memory belongs to.
user_id string or null The end-user scope. Unchanged by an update.
metadata object The metadata last written via add(). Not modified by update().
created_at ISO 8601 string Original creation timestamp. Never changes.
updated_at ISO 8601 string Timestamp of this write. Use this as the next expected_updated_at if you plan to chain updates.
status string processing while the new content is being extracted, then ready (or error).
facts array Empty while status is processing, which is what the hosted service answers. Each item, once present, includes id, subject, predicate, object, predicate_family, valid_from, invalidated (the array of fact IDs this fact supersedes), tense, invalid_at and observation_count.
facts[].invalidated array of strings IDs of facts from the previous version that this new fact supersedes. Empty when no contradiction was detected.

Errors

StatusCode stringCause
401 invalid_key The Authorization header is missing, malformed, or the key has been revoked.
403 forbidden The key lacks the memories:write scope.
404 not_found The id is malformed, no memory with that id exists in this key's project, or it was previously deleted.
409 stale_write expected_updated_at was provided but does not match the server's current updated_at (or created_at, before the first edit). Another write happened since you last read this memory. Fetch the latest version and retry.
422 invalid_request Request body failed validation, most commonly content is missing or empty, or the body has a field other than content and expected_updated_at. The body is the flat {"code":"invalid_request","message":"content: Field required"} envelope.
429 rate_limit_exceeded Too many requests in the current minute, hour or day for your plan. The response carries Retry-After (integer seconds), X-RateLimit-Limit and X-RateLimit-Remaining: 0.
429 quota_exceeded The monthly write quota is spent. An update counts one write per 6,000 characters of its new text, so past the quota it is refused and nothing changes. No Retry-After: the quota resets on the 1st of the month (UTC).
503 writes_paused Only when extraction runs inside the call, not on the hosted service: the service's daily model budget is spent, nothing changed, retry after Retry-After (00:00 UTC).

Notes

  • Not idempotent. Each call to PATCH /v1/memories/{id} re-runs extraction and updates updated_at. Sending the same content twice re-validates facts each time; the history keeps one updated event, with the time of the last edit. Use expected_updated_at to guard against unintended repeated writes in retry loops.
  • Scoping is inherited, not settable. The user_id, agent_id, and run_id set at creation cannot be changed by an update. If you need to move a memory to a different scope, delete it and add a new one.
  • Metadata is not updated here. The metadata field is write-once from add(). If you need to attach new metadata, delete and re-add the memory.
  • Write quota. An update counts against your monthly write quota like add() and each item of a batch import, one write per 6,000 characters of its text, because it runs extraction again; a direct fact write is one write. Like every /v1 call, it also counts toward the per-minute, per-hour and per-day rate limits of your plan.
  • Fact history is preserved. Superseded facts are never hard-deleted. They remain visible in GET /v1/memories/{id}/history and in facts queries when include_invalidated=true is set. This lets you audit how knowledge evolved over time.
  • Contradiction detection is automatic. You do not need to locate and delete old facts manually. Pass the corrected content: the two-stage contradiction pipeline identifies and invalidates any conflicting facts within the same user scope, and the facts the new text no longer states are closed.

Related

  • Add a memory, write new content and run the full extraction pipeline from scratch.
  • Delete a memory, soft-delete one memory and close the facts only it stated.
  • Search memories, retrieve the memories most relevant to a query before deciding whether to update.
  • API reference, full endpoint contract including GET /v1/memories/{id}/history for auditing past versions.