Korely

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
The write pipeline triggered by add(). All stages run on Korely infrastructure.

Request

Endpoint: POST /v1/memories. SDK: korely.add(content, *, user_id=None, agent_id=None, run_id=None, metadata=None).

ParamTypeNotes
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 user
result = 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_8f2c1a
print(result.facts[0].predicate) # costs
print(result.facts[0].object) # 50 euro per month
# You can also pass a chat message list — the pipeline extracts facts from the thread
korely.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

FieldTypeDescription
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.

StatusCode stringWhen 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, and run_id are 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_id values is allowed on every plan. The per-plan limit applies only to agent_id namespaces (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 returns 429 quota_exceeded (with no Retry-After header — the quota resets on your billing cycle date). Bursty traffic can separately hit a per-second rate limit, which returns 429 with a Retry-After header 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 facts array. Contradiction detection only runs when the same subject-predicate pair already exists for the same user_id scope.

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 /v1 endpoints.