Korely

Core memory operations

Add a memory

Store a memory. Korely extracts typed facts and resolves contradictions server-side; you just send the text and a scope.

POST /v1/memories

SDK: korely.add(content, ...). Korely saves the memory and replies at once. A model then extracts typed facts from the text and checks them against the facts already stored (contradiction). You never write supersede logic. The guide walks through the same call with a runnable example.

Authentication

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

Request body

FieldTypeRequiredDescription
contentstringRequiredThe memory text. 1-16,000 characters; leading and trailing whitespace is removed, and a blank text is refused.
user_idstringOptionalThe end user this memory belongs to. 1 to 255 characters, no /, no control characters, not . or .., not starting with erased:. Omit it for the shared namespace; an empty string is refused.
agent_idstringOptionalYour application's name for the agent that writes (agent_id). Same rule as user_id. Omit it for the default namespace, which never counts toward your agent cap. Since 1.0.12, the model that extracts the facts treats a memory with an agent_id and no user_id as the agent's own memory, not as a person's (see who the memory is about). If such a memory is about one person, pass that person's user_id with it.
run_idstringOptionalOne session or conversation (run_id). 1 to 255 characters; an empty string is refused, omit the field instead.
metadataobjectOptionalYour own labels (metadata): JSON stored with the memory and returned on reads. At most 8,192 bytes once serialized, with finite numbers only: NaN and Infinity are refused.
timestampstringOptionalISO 8601 date or datetime of the event (a date is midnight UTC, no offset means UTC, omitted or blank means now). Sets valid_from (valid time) on the facts extracted from this memory. Use it when you load old memories, so the timeline stays correct. A timestamp later than now is read as now: for a fact that begins later, write it with POST /v1/facts and its own valid_from.
kindstringOptionalWhat the text is (kind). note, the default: what somebody said or decided. session: the log of a work session, such as what a coding assistant's hooks save. A read can leave the sessions out, their facts with them, with exclude_sessions (see Work sessions).

Any other field is refused with 422 invalid_request (for example userId: Extra inputs are not permitted): field names are snake_case.

Example request

Terminal window
curl -X POST https://api.korely.ai/v1/memories \
-H "Authorization: Bearer kor_live_..." \
-H "Content-Type: application/json" \
-d '{
"content": "User prefers email follow-ups, not phone",
"user_id": "customer-giulia-4812",
"agent_id": "support-bot"
}'

Response

201 Created. The stored memory, with status processing and an empty facts list: the facts arrive a few seconds later (see the note below).

{
"id": "mem_8f2c1aab4d7e4f0c9a1b2c3d4e5f6a7b",
"content": "User prefers email follow-ups, not phone",
"user_id": "customer-giulia-4812",
"agent_id": "support-bot",
"run_id": null,
"client": null,
"kind": "note",
"metadata": {},
"status": "processing",
"created_at": "2026-06-17T10:22:00.412345+00:00",
"updated_at": null,
"facts": []
}
FieldTypeDescription
idstringThe memory id, e.g. mem_8f2c1aab4d7e4f0c9a1b2c3d4e5f6a7b.
contentstringThe text you stored, echoed back.
user_id / agent_id / run_idstring · nullThe scope you sent, echoed back (null if omitted).
clientstring · nullThe tool that wrote it, from the X-Korely-Client header (the SDKs send their own name). A label, not a namespace.
kindstringnote, or session for the log of a work session: the kind of the write.
metadataobjectThe metadata you sent ({} if omitted).
statusstringThe memory's status: processing while the facts are being extracted, then ready (or error, see When extraction fails). ready can come with no facts, when the text states none. Read it later with Get memory.
created_atstringISO 8601 timestamp of the write.
updated_atstring · nullnull until the memory is edited with Update a memory; then the ISO 8601 timestamp of the last edit.
factsarrayAlways [] on the Cloud. Only a Self-hosted install started with WRITE_ASYNC=false extracts inside the call and fills it. Each fact then has id, subject, predicate, object, predicate_family (string · null), valid_from (string · null), tense, invalid_at (string · null), observation_count (above 1 when the write restated a fact the store already held), user_id, agent_id and invalidated, the fct_ ids of the facts this one closed. invalidated exists only in this reply: a later read shows the same link as invalidated_by on the closed fact.

Facts arrive after the reply. The add returns as soon as the memory is stored, with facts: []. The facts land about ten seconds later. Do not wait for them in the same request: read them on the next turn with Get facts or Get context, or check status with Get a memory.

Work sessions

Terminal window
curl -X POST https://api.korely.ai/v1/memories \
-H "Authorization: Bearer kor_live_..." \
-H "Content-Type: application/json" \
-d '{
"content": "Session: moved the billing service to Postgres 16, tests pass, rollback script in ops/.",
"user_id": "dev-team",
"kind": "session"
}'

A memory with kind: "session" is stored, embedded and read into facts like any other. The difference is on the read side: pass exclude_sessions=true to Search memories, List memories, Get facts, Get profile or Get context, and the work sessions drop out, with the facts that only they state. A coding agent can keep its session logs in the same memory as the team's decisions, and a read that wants what was said and decided gets only that. The SDKs take kind from Python 0.1.26 and Node 0.1.16, and hand it back on every memory they return (Memory.kind) from Python 0.1.27 and Node 0.1.17.

Errors

StatusCodeCause
401invalid_keyMissing or invalid Authorization header.
403forbiddenThe key lacks the memories:write scope, or it is confined to some end users and the user_id is outside its list (or no user_id is given and the key does not name exactly one end user to default to).
413payload_too_largeThe body is larger than 2,097,152 bytes. Refused before it is read; nothing is written or counted. On the Cloud a body over 1 MB gets a bare 413 first, without this code.
422invalid_requestValidation failed: content missing, not a string, blank or over 16,000 chars; user_id or agent_id breaking the id rule; run_id empty or over 255; metadata over 8,192 bytes or holding NaN or Infinity; timestamp not ISO 8601; a kind other than note or session; malformed JSON; an unknown field.
429quota_exceededMonthly writes used up, 10% grace included; nothing is written. detail carries limit, used and resets_at (00:00 UTC on the 1st). No Retry-After: read resets_at, top up the month on a paid plan, or upgrade.
429agent_cap_exceededA new agent_id beyond your plan's agent cap. Nothing is written or counted: reuse one from List agents, delete one, or upgrade. detail carries limit and used.
429rate_limit_exceededPer-minute, per-hour or per-day request limit (rate limit). Carries Retry-After.
408request_timeoutThe body did not arrive whole within 30 seconds. Nothing was written; send it again.
500internal_errorThe server failed on this request; the message holds no detail. Check with List memories whether the memory was stored before you retry.
503unavailableA dependency did not answer, for example the database, or another change to the same records held this request more than 5 seconds. Nothing was written. Wait the seconds in Retry-After, then retry.

Notes

  • Who the memory is about (since 1.0.12). It depends on the ids you pass.
    • With a user_id, with or without an agent_id: the memory belongs to that end user, and "user" in its facts means that person.
    • With an agent_id and no user_id: the memory is the agent's own, such as a team's shared memory or an agent's own notes. Korely tells the model that no end user owns the memory and gives it three rules. First, "I" in the text is the agent that wrote it. Second, the subject of a procedure, a rule or a decision is the thing it describes ("Deploys run on Fridays" is about deploys). Third, a procedure, a rule in force, a habit or the result of something done that still holds (an approval, a revocation) is current from the date in the text, and past only when the text says it ended. Then Korely itself, not the model, drops any fact whose subject or object is user unless the text contains the English word "user" as a whole word ("The user prefers TypeScript" keeps it; "user_id" or "users" does not count). Up to 1.0.11 such a memory was read as an end user's, so a procedure could come back as user follows_procedure deploy. A confined key limited to one end user fills in that user_id when you leave it out, so its memories stay that person's.
    • With neither id, for example one person using the MCP or an SDK without ids: the memory is stored in the shared namespace, as before, and its facts are read as if an end user named "user" had said it, as in 1.0.11.
    If the memories you write with an agent_id are about one person, pass that person's user_id too. Moving to 1.0.12 does not change facts already written. A memory written or edited on 1.0.12, or still waiting for its facts when your install or the Cloud moves to 1.0.12, follows the new rules: editing an old memory that has an agent_id and no user_id can change its facts. This changes what the model is asked; the tests use a fake model with fixed answers, and how often a real model now gets the subject and the tense right is not measured yet.
  • One write per 6,000 characters. An add of up to 6,000 characters counts one write against your monthly write quota, however many facts it produces. A longer one counts one write per 6,000 characters, rounded up: 16,000 characters, the most a memory can hold, count 3. The engine may make more extraction calls than that (its windows overlap); you pay only the writes. A memory that does not fit in the writes left this month is refused whole with 429 quota_exceeded.
  • Contradictions are checked automatically. A model compares each new fact with the open facts it could conflict with and closes the ones the new fact replaces (a new address closes the old one; a second hobby does not). A closed fact is not deleted and stays readable with as_of. Write a change as a state (“costs 55 euro per month”), not as a story (“the price went up”): a story can leave the old fact open, most often when the project's region is eu.
  • No idempotency. A retried write can store the memory twice and count two writes. An Idempotency-Key header is ignored.
  • Messages, too. The SDKs accept a list of chat messages as content and join them into one block before sending.

Related