Korely

A patient assistant that never forgets the chart

Picture a physiotherapy clinic with an intake assistant in the booking flow. Patient Marco opens the chat before his fourth visit. With a stateless bot, Marco gets the same intake questionnaire every single time: "Any allergies? Any current medication? Where does it hurt?" He answered all of this in January, February, and March. With a Korely-backed bot, the assistant opens with what it already knows: the rotator cuff strain from January, the penicillin allergy, the medication change in March, the session note that he responded well to exercise B. The chat starts where the last visit ended, because the developer wired in the four calls on this page.

Read this before anything else. This cookbook shows the memory mechanics. It is not a medical device, and Korely makes no claim that an application built on it meets any health-care regulation. The regulatory review of your application is yours. Health data is allowed in a memory when you have a lawful basis for it under Article 9 GDPR and have signed our Data Processing Agreement (write to [email protected]); we recommend the Europe region for it. Korely gives you these data-control primitives: erasing one patient in one call, facts with start and end dates, and a log of who wrote and read what (ids and counts, never content). Memories and facts are stored on our server in Helsinki. To extract facts, the text of each memory goes to the language model of the project's region: gpt-oss-120b on Scaleway, in Paris, in the eu region, or Google's Gemini API in global, which may process it outside the EU (see Data governance and Security). Questions about a health deployment: [email protected].

The scoping model in one sentence

One clinic app = one agent_id ("intake-assistant"). Every patient = one end user, that is one user_id ("patient-marco", or better, your internal patient identifier, so that no name sits in the id). The words fact, memory and agent_id are in the Glossary. End users are unlimited on every tier. 10,000 patients is still one agent_id with 10,000 user_id values. Filters are additive: both together return only what this app knows about this patient.

Call 1: intake opens, load the chart context

When Marco opens the chat, before the LLM says a word, your backend pulls his health facts:

from korely_memory import Korely
korely = Korely(api_key="kor_live_...")
facts = korely.get_facts(
user_id="patient-marco",
predicate_family="health",
)

The response is a flat list of the typed facts Korely extracted from prior sessions. A fact is live when invalid_at is null; superseded facts carry the date they were invalidated (see Call 3). Each fact below is shortened to its main fields:

{
"facts": [
{"id": "fct_19ce", "subject": "patient-marco", "predicate": "allergic_to",
"predicate_raw": "allergic_to", "object": "penicillin",
"predicate_family": "health", "confidence": 0.95,
"valid_from": "2026-01-12T09:30:00+00:00", "invalid_at": null,
"invalidated_by": null, "source_memory_id": "mem_4a1c"},
{"id": "fct_22b0", "subject": "patient-marco", "predicate": "diagnosed_with",
"predicate_raw": "diagnosed_with", "object": "rotator cuff strain (right shoulder)",
"predicate_family": "health", "confidence": 0.92,
"valid_from": "2026-01-12T09:30:00+00:00", "invalid_at": null,
"invalidated_by": null, "source_memory_id": "mem_4a1c"},
{"id": "fct_4f81", "subject": "patient-marco", "predicate": "takes_medication",
"predicate_raw": "takes_medication", "object": "naproxen 250 mg as needed",
"predicate_family": "health", "confidence": 0.9,
"valid_from": "2026-03-02T10:05:00+00:00", "invalid_at": null,
"invalidated_by": null, "source_memory_id": "mem_91f3"}
],
"total": 3
}

These facts go straight into the system prompt. A fact read uses no generative model, so it is cheap enough to run on every chat open. The JSON above is illustrative: the extractor is a model, so predicates and objects differ from run to run. Do not treat extracted facts as the clinical record. They are a model's reading of text, and they can be missing or wrong. Keep your own record as the source of truth, and show the clinician the memory text beside each fact. The health family is one of ten predicate families. You don't define a schema: the extractor writes each relation as a short snake_case predicate, and a model files it under one of nine families the first time that predicate appears for an end user (the answer is reused for every later fact with that predicate). The tenth family, identity, is assigned by the schema to renaming predicates such as renamed_to and also_known_as. Typical predicates look like these (the names are examples, not a fixed list):

FamilyExample predicates
preferenceslikes, dislikes, prefers
peopleknows, married_to, works_with
placeslives_in, visited
workworks_at, role_is
ownershipowns, uses
healthallergic_to, diagnosed_with, takes_medication
financialpays_for, subscribed_to
eventsattended, scheduled
identityrenamed_to, also_known_as
othercatch-all typed edges

Call 2: during the session, write the note

After the visit, the assistant (or the therapist's dictation flow) stores what happened:

memory = korely.add(
content="Responded well to exercise B, pain 3/10 (was 6/10)",
user_id="patient-marco",
agent_id="intake-assistant",
)

The raw note is stored and searchable as-is. The write path is where the intelligence runs: embeddings, entity extraction, typed-fact extraction with contradiction checking and start and end dates for facts. On the Cloud all of that is part of the write your plan counts, with no model charges on top. Extraction usually finishes within about ten seconds. Anything that fits a typed predicate becomes a fact of Marco's. The note itself is always stored and searchable. Whether a sentence also becomes a fact is the extractor's decision: allergies, conditions and medication usually do, and a real run also turned "pain 3/10 (was 6/10)" into pain_level facts, the old value closed.

Call 3: the treatment history, never silently lost

In March, Marco's dosage changed. A naive store would overwrite the old value, and a clinic cannot afford "we don't know what he was taking in February." Korely facts have a start and an end date: a contradiction doesn't delete the old fact, it invalidates it with a date. Pull the full chain:

history = korely.get_facts(
user_id="patient-marco",
include_invalidated=True,
)
PredicateValueValid fromInvalidated
takes_medication naproxen 500 mg twice daily 2026-01-12 2026-03-02
takes_medication naproxen 250 mg as needed 2026-03-02 still valid
allergic_to penicillin 2026-01-12 still valid
diagnosed_with rotator cuff strain (right shoulder) 2026-01-12 still valid

The table is illustrative: closing the old dosage is a model's judgement, so check the history with include_invalidated=True rather than assuming it. In a real run of a single note ("Since 2 March 2026 he takes naproxen 250 mg as needed, down from naproxen 500 mg twice daily"), the new dosage was open with valid_from 2026-03-02, taken from the text, and the old dosage was closed with a past tense. The note gave no date for the old one, so it started on the day the note was written. Contradiction detection is two-stage and runs on every write: a broad candidate search over the older facts about the same subject (the same or a related relation, the same two entities, the nearest facts by meaning), then, only when that search finds candidates, a model judges whether the new fact replaces one before anything is invalidated. By the time your agent reads, the conflict is already resolved. Temporal facts covers the mechanism in depth.

Point-in-time queries

Because every fact carries valid_from and invalid_at, the API exposes as_of: "what was true at the March visit?" To reconstruct what the system knew at the time, add known_as_of: "what did the system know on March 1st?" It leaves out the facts and corrections the store learned after that date. Facts written from a note start on the day the note was written, unless the text states a date or you pass timestamp= to add(), so a date before your first write returns nothing.

march_view = korely.get_facts(
user_id="patient-marco",
predicate_family="health",
as_of="2026-03-01",
)
# -> the health facts valid on that date, as the store knows them today
# (takes_medication: naproxen 500 mg twice daily)
known_then = korely.get_facts(
user_id="patient-marco",
predicate_family="health",
known_as_of="2026-03-01",
)
# -> the health facts as the store knew them on March 1st

Who controls the data

Two deployment shapes, two control models:

  • Clinic scenario (this cookbook): control stays API-side, in your hands. A patient asks you to erase everything about them? One call erases every memory and fact this project holds for that user_id, in every agent namespace. Encrypted backups keep a copy for up to 35 days. Check the id you send: an id that matches nothing also answers 200, with zero counts.
Terminal window
curl -X DELETE https://api.korely.ai/v1/users/patient-marco/memories \
-H "Authorization: Bearer kor_live_..."
# 200 OK
{"user_id": "patient-marco", "memories_deleted": 14, "facts_deleted": 6,
"erasure": "permanent", "audit_id": "aud_77c2"}
  • Patient-facing control: give the patient a screen that lists get_profile and calls correct_fact or forget_fact (human in the loop): they fix a wrong dosage or forget a fact themselves. No support ticket.

The whole loop

Visit 1: January

Intake session

  • korely.add("Responded well to exercise B...")
  • user_id: patient-marco
  • agent_id: intake-assistant

Korely

Typed facts with dates

  • allergic_to: penicillin
  • diagnosed_with: rotator cuff strain
  • takes_medication: 500 mg → 250 mg (chain kept)

Visit 2, and every visit after

Greeting with context

  • get_facts(predicate_family="health") on chat open
  • "Welcome back Marco. How is the shoulder since we switched to exercise B?"
  • No generative model on the read

The health family, the struck-through superseded dosage, the dates: the behavior this cookbook builds on. If you're building something health-adjacent, talk to us before you ship it ([email protected]). Our data-control primitives end where your regulatory review begins. For now Korely is available as Cloud; installing it on your own machines is not available at the moment (pricing).