Core operations
Add a memory
Store content and run the full write pipeline — embeddings, entity graph, typed facts, and contradiction detection — in a single call.
When you call add(), Korely stores the raw content, generates
a document embedding, extracts named entities into the graph, and
runs typed bi-temporal fact extraction with a two-stage contradiction check.
Any fact that conflicts with an existing one is marked superseded rather than
deleted, so the full history stays queryable. Fact extraction runs
asynchronously: the response may already carry the facts from this write, or
the facts array may arrive empty and populate moments later. Read
them back with get_context or
get_facts() once the pipeline settles.
Call add() after any turn where the user reveals something worth
remembering: a preference, a decision, a fact about themselves or their
environment. Through the SDK you can also hand it the full message list from a
chat session — it is joined into one block and the pipeline extracts what is
worth keeping from the whole conversation.
flowchart LR
A([add content]) --> B[Store memory]
B --> C[Embed memory]
C --> D[Entity graph]
D --> E[Fact extraction]
E --> F{Contradiction?}
F -- yes --> G[Supersede old fact]
F -- no --> H[Record new fact]
G --> I([Return memory + facts])
H --> I Request
Endpoint: POST /v1/memories. SDK: korely.add(content, *, user_id=None, agent_id=None, run_id=None, metadata=None).
| Param | Type | Notes |
|---|---|---|
content | string | Required. Plain text or Markdown — the REST body takes a string (max 16,000 chars). The Python and Node SDKs additionally accept a list of chat messages ([{role, content}]) and join them into one text block before sending. The pipeline extracts facts from the resulting text. |
user_id | string | Optional but recommended. Your end user's identifier (e.g. "customer-4812"). Scopes the memory to one person your agent serves. End users are unlimited on every plan. |
agent_id | string | Optional. Namespaces the memory to one of your apps. Omit it to use the key's default namespace. Each distinct agent_id counts toward your plan's agent limit; a new one past the limit returns 403 agent_cap_exceeded. |
run_id | string | Optional. Sub-scope for one agent run or chat session. Useful when you want to isolate a single conversation from the user's broader memory. |
metadata | object | Optional. Arbitrary key-value pairs returned verbatim on read; not interpreted by the pipeline. Use it for source tags, version labels, or any application data you want to round-trip. |
Example
from korely_memory import Korely
korely = Korely(api_key="kor_live_...")
# Store a plain-text memory scoped to one end userresult = korely.add( "Northwind Hosting costs 50 euro per month since the June upgrade.", user_id="customer-4812", agent_id="infra-bot", metadata={"source": "slack", "channel": "#billing"},)
# Returns are dataclasses — use attribute access (not result["id"])print(result.id) # mem_8f2c1aprint(result.facts[0].predicate) # costsprint(result.facts[0].object) # 50 euro per month
# You can also pass a chat message list — the pipeline extracts facts from the threadkorely.add( [ {"role": "user", "content": "I just upgraded to the Advanced plan."}, {"role": "assistant", "content": "Great, I have updated your profile."}, ], user_id="customer-4812", agent_id="infra-bot",)Response
{ "id": "mem_8f2c1a", "content": "Northwind Hosting costs 50 euro per month since the June upgrade.", "user_id": "customer-4812", "agent_id": "infra-bot", "run_id": null, "metadata": { "source": "slack", "channel": "#billing" }, "created_at": "2026-06-15T09:14:00Z", "updated_at": "2026-06-15T09:14:00Z", "facts": [ { "id": "fct_b91e", "subject": "Northwind Hosting", "predicate": "costs", "object": "50 euro per month", "predicate_family": "financial", "valid_from": "2026-06-15T09:14:00Z", "invalidated": ["fct_a774"] } ]}Response fields
| Field | Type | Description |
|---|---|---|
id | string | Unique memory ID (prefix mem_). Use this to retrieve, update, or delete the memory later. |
content | string | The raw text that was stored, exactly as submitted. |
user_id | string or null | The end-user scope passed in the request, or null if none was provided. |
agent_id | string or null | The agent namespace passed in the request, or null if the key's default namespace was used. |
run_id | string or null | The session scope passed in the request, or null if none was provided. |
metadata | object | The arbitrary key-value pairs passed in the request, returned verbatim. Empty object if none were set. |
created_at | ISO 8601 string | UTC timestamp of when the memory was first stored. |
updated_at | ISO 8601 string | UTC timestamp of the last write to this memory. Equal to created_at on a fresh add. |
facts | array | Typed facts extracted from this write. Extraction is asynchronous, so this array may be empty on the immediate response and populate moments later — or empty permanently if the content carries no extractable facts. |
facts[].id | string | Unique fact ID (prefix fct_). Appears in the sources of get_context() and in the invalidated arrays of later facts that supersede it. |
facts[].subject | string | The entity this fact is about (e.g. a product, person, or organisation name). |
facts[].predicate | string | The relationship or attribute, normalized to a canonical verb (e.g. costs, works_at, likes). When you read facts back, the original surface verb is preserved in predicate_raw. |
facts[].object | string | The value of the relationship (e.g. 50 euro per month). |
facts[].predicate_family | string | A coarse bucket for the predicate (e.g. financial, people, places, preferences). Predicates outside the known taxonomy map to other. Filter on this on the facts endpoint to group related facts. |
facts[].valid_from | ISO 8601 string | UTC timestamp when this fact became true (bi-temporal valid_time axis). Set to the write timestamp unless the content explicitly states a past or future date. |
facts[].invalidated | array of strings | IDs of earlier facts that this write superseded via the two-stage contradiction check. Empty array if no contradiction was detected. Superseded facts are never deleted — they stay queryable, and a read of those facts shows their invalid_at timestamp set. |
Each fact carries a normalized predicate and a
predicate_family bucket (some predicates fall into
other rather than a specialised family). When the pipeline
detects a contradiction with an earlier fact for the same subject and scope,
the older fact's id appears in the new fact's invalidated array.
The old fact is never deleted — read it back through
get_facts with
include_invalidated=true and you will see its invalid_at
timestamp set. This invalidate-don't-delete model, with
valid_from on every fact, is what lets you reconstruct any past
state with as_of.
Errors
Every error response is the same flat envelope —
{"code": "<slug>", "message": "<text>"}. There is no
error or detail field; the slug in the table below is
the code value.
| Status | Code string | When it occurs |
|---|---|---|
401 | invalid_key | The Authorization header is missing, malformed, or the key has been revoked. |
403 | agent_cap_exceeded | The agent_id in the request is a brand-new name that would push your total agent count past your plan's limit (2 on Hobby, 10 on Developer, 100 on Team, 500 on Scale). Reuse an existing agent_id or upgrade your plan. |
422 | invalid_request | Request validation failed — for example, content is missing or not a string or array, or a field is the wrong type. The body is the flat {"code","message"} envelope, with the offending field named in the message (e.g. "content: Field required"). |
429 | quota_exceeded | Your account's monthly write quota has been exhausted. The message states the limit (e.g. "Monthly memory write limit reached (1000). Upgrade to add more."). The write-quota 429 carries no Retry-After header — quota resets on your billing cycle date. (A separate rate-limit 429 does carry a Retry-After header in seconds.) |
Notes
- Not idempotent. Each call to
add()creates a new memory object. If you call it twice with identical content, two separate memories are stored and facts may be duplicated. Deduplicate on the caller side before writing, or use update to replace an existing memory in place. - Scoping is additive.
user_id,agent_id, andrun_idare independent filter axes. A memory written with all three is only returned by searches that include all three matching values. Omitting a scope on read broadens the search to everything the key can see. - End users are unlimited. Any number of distinct
user_idvalues is allowed on every plan. The per-plan limit applies only toagent_idnamespaces (distinct apps you register). - Write quota and rate limits. Each successful
add()call counts as one write against your monthly quota. Once the quota is exhausted the API returns429 quota_exceeded(with noRetry-Afterheader — the quota resets on your billing cycle date). Bursty traffic can separately hit a per-second rate limit, which returns429with aRetry-Afterheader in seconds. - Fact extraction is pipeline-driven. The number of facts returned varies with content length and density. Very short strings (under one sentence) may return an empty
factsarray. Contradiction detection only runs when the same subject-predicate pair already exists for the sameuser_idscope.
Related
- Get context — the primary recall path. Assembles the active typed facts plus relevant memories for a user into one ready-to-inject block. This is the differentiator: structured, contradiction-resolved recall, not just nearest-neighbour snippets.
- Search memories — secondary recall. Semantic vector search (cosine similarity) returns the closest raw memory snippets for a query.
- Update a memory — replace a memory's content and re-run fact extraction, with optional optimistic concurrency.
- API reference — full contract for all
/v1endpoints.