A sales agent that connects the dots
You mention "Acme Corp" to your sales assistant. A stateless agent answers from whatever thread you happen to be in. It doesn't know that Marco from Acme is the champion you met in March, that the renewal slipped to Q3, or that you promised their CTO a security review, those live in three different meeting notes it never connected.
With Korely in the loop, one mention of Acme Corp surfaces the champion,
the renewal target and the budget stated with it, pulled from all your
notes, not the open thread. That works because memories become typed
(subject, predicate, object) facts (who, which relation, what): Marco Rossi
is_champion Acme Corp, Acme Corp targets_renewal_in
Q3, Acme Corp budget_approved approved. Ask for Acme and you
get the facts that name it; the deal is one step further, through Marco,
who owns it. A champion is the person at the customer who pushes
your deal internally. (The
predicate names are written by the extractor, a model, so yours will
differ.) This cookbook walks the pattern at the call level. Terms such as
entity,
fact and
as_of are in the
Glossary.
The snippets use the Python SDK (pip install korely-memory).
The same calls work over the
REST API and the
Node SDK.
The scoping: one rep, every account
A sales rep's book is one user_id, the end user the data is
about. Every account, contact, and deal lives in that namespace, so a fact
written from the Acme call and a fact written from the Globex call both land
where a single get_facts can connect them. Always pass
user_id: a read without it covers every end user of the
project.
from korely_memory import Korely
korely = Korely(api_key="kor_live_...", region="eu")
REP = "rep-jordan" # the sales rep this book of business belongs to1. After a call: drop the notes in
When a meeting ends, hand Korely the notes. Fact extraction runs server-side and turns the prose into typed edges that connect the people, the company, and the deal, no schema to define, no triples to hand-author.
korely.add( "Discovery call with Acme Corp. Marco Rossi (VP Eng) is the champion and " "owns the Enterprise renewal, now targeting Q3. Their CTO asked us to run a " "security review before they sign. Budget is approved.", user_id=REP, run_id="call-acme-2026-06-12", metadata={"account": "Acme Corp", "stage": "discovery"},){ "id": "mem_8d21", "content": "Discovery call with Acme Corp. Marco Rossi (VP Eng) is the champion...", "user_id": "rep-jordan", "agent_id": null, "run_id": "call-acme-2026-06-12", "metadata": {"account": "Acme Corp", "stage": "discovery"}, "client": "korely-python/0.1.28", "kind": "note", "status": "processing", "created_at": "2026-06-12T15:04:22+00:00", "updated_at": null, "facts": []}
Note facts: [] in that response, extraction is asynchronous, so
the typed triples land usually within about ten seconds after the write
returns. The next
section reads them once they're in.
2. Mention an account: pull everything tied to it
This is the move. get_facts(entity="Acme Corp") returns every
fact where Acme appears on either side of the triple, as a
subject (Acme Corp targets_renewal_in Q3) or as an object. The opposite
endpoint of each fact is a connected entity: the champion, the renewal
target, the budget. One call, one account, its direct neighborhood. The four
lines in the code comments are the facts a real run returned for the note
in step 1.
facts = korely.get_facts(entity="Acme Corp", user_id=REP)for f in facts: print(f.subject, f.predicate, f.object)# rep-jordan had_call_with Acme Corp# Marco Rossi is_champion Acme Corp# Acme Corp targets_renewal_in Q3# Acme Corp budget_approved approved entity matches the exact name (ignoring case), or a known alias,
on either side of a fact. It is not a substring or fuzzy match, so
"Acme" does not find "Acme Corp".
{ "facts": [ { "id": "fct_a1", "subject": "Marco Rossi", "predicate": "is_champion", "predicate_raw": "is_champion", "object": "Acme Corp", "subject_type": "person", "object_is_literal": false, "user_id": "rep-jordan", "agent_id": null, "invalid_at": null, "invalidated_by": null, "source_memory_id": "mem_8d21" }, { "id": "fct_a2", "subject": "Acme Corp", "predicate": "targets_renewal_in", "predicate_raw": "targets_renewal_in", "object": "Q3", "subject_type": "organization", "object_is_literal": true, "user_id": "rep-jordan", "agent_id": null, "invalid_at": null, "invalidated_by": null, "source_memory_id": "mem_8d21" } ], "total": 4}
The list above is shortened to two of the four facts, and each fact to
its main fields. The request from the CTO is not in it. The extractor
stored it under a different name, "Acme Corp CTO"
(requested_security_review), and entity matches
names exactly. This is the limit of a lookup by name: when one company
appears under two names, tell Korely they are the same with the
declare-name call, or read
the note itself with search or get_context.
Because entity matches the subject or object side,
the champion surfaces from the same query as the renewal target and the
budget. Nothing in the agent's open thread had to mention Marco for him to
come back when you asked about his company. The renewal Marco owns is one
hop further, in step 4.
In the chat
You mention "Acme Corp"
- No special prompt
- Resolves to user_id "rep-jordan"
Korely
get_facts(entity="Acme Corp")
- Every triple where Acme is subject OR object
- Across every note, not one thread
Surfaced
The whole neighborhood
- Marco (champion), the approved budget
- The Q3 renewal target
3. Write an explicit edge yourself
Sometimes you already know the connection and want it recorded exactly, not
inferred from prose. add_fact_triple writes the edge directly,
it skips extraction but still runs the server-side contradiction check (a
model call, so it can fail for a moment with 503 model_unavailable) and counts
as one write against your plan. A new edge that conflicts with an old one
can supersede it.
fact = korely.add_fact_triple( "Marco Rossi", "works_at", "Acme Corp", user_id=REP, subject_type="person", confidence=0.97,)# fact.invalidated -> ids of any earlier conflicting edge this one replaced{ "id": "fct_b1", "subject": "Marco Rossi", "predicate": "works_at", "object": "Acme Corp", "predicate_family": "work", "valid_from": "2026-06-12T15:09:40+00:00", "invalidated": [], "tense": "current", "invalid_at": null, "observation_count": 1, "user_id": "rep-jordan", "agent_id": null}
The note in step 1 never states where Marco works, so this write creates a
new fact: a fresh id, a valid_from equal to the write time, and
observation_count 1.
If Marco later moves to Globex, write Marco Rossi works_at
Globex and the contradiction detector can return the superseded edge's id
in invalidated, and the old employment fact gets an
invalid_at timestamp and stops being served. The history stays
intact. The check is a model's judgement, so read with
include_invalidated=True when it matters.
4. Pivot to a person
The same one-hop lookup walks the other way. Ask for a person and you get
every fact they touch, what they own, who they champion. To follow the graph one more step, call get_facts again on
a neighbor you found.
marco = korely.get_facts(entity="Marco Rossi", user_id=REP)# Marco Rossi owns Enterprise renewal# Marco Rossi is_champion Acme Corp# walk one hop further: from the deal Marco ownsdeal = korely.get_facts(entity="Enterprise renewal", user_id=REP)# only the edges that name "Enterprise renewal": in this run, the owns edge above.# "Q3" hangs on "Acme Corp", so the hop through the deal name does not find it.Each hop is a lookup in the facts table with no generative model on the read path. You're traversing typed edges one at a time, choosing where to go next from what came back.
5. Point in time: who was on the account back then?
Because facts have start and end dates, you can ask the graph as it stood on any date. Useful for a deal post-mortem, who the champion was before a contact moved on, what the renewal target was last quarter.
# the account's connections as they stood on 30 June, before Marco moved to Globex (step 3)# Note: a fact starts on the day its note was written. If you wrote the notes today,# a date in June finds nothing: use a date after your own writes, or pass timestamp=# to add() with the day the call really happened.before = korely.get_facts(entity="Acme Corp", user_id=REP, as_of="2026-06-30")
# include superseded edges to see the full history, each with its invalid_athistory = korely.get_facts(entity="Acme Corp", user_id=REP, include_invalidated=True) as_of="2026-06-30" returns the edges that were valid on that
date, so Marco's Acme edge is still there even if he has since moved;
include_invalidated=True adds the edges that have since been
superseded, each with its invalid_at, so you can see how the
account map changed.
Why this beats a flat note store
- One query, the whole account. A vector store returns the
notes that mention "Acme" by similarity.
get_facts(entity="Acme Corp")returns every typed edge Acme touches, across every note it was stated in, not just the notes that rank closest to the word "Acme". - Connections are first-class. Memories become
(subject, predicate, object)facts, so "who champions Acme" and "when Acme plans to renew" are the same lookup from different sides of the triple, not a re-read of every transcript and a hope the model joins them. - On every plan. The entity view is part of every Korely plan, including the free one.
One honest caveat: get_facts(entity=...)
matches the subject or object side of a triple, so connections
surface through shared typed facts, it is a one-hop lookup over the facts
table, not a server-side multi-hop graph-traversal API (there is no
get_related on the agents SDK; you walk further by calling
get_facts again on a neighbor). And
korely.add extracts those facts asynchronously, so the typed
edges from a fresh note land a few seconds after the write returns, ready
at the next get_facts, not inside the same call. When you need
an edge recorded instantly and exactly, write it with
add_fact_triple.
Where to go next
Temporal facts explains
supersession, as_of, and the contradiction check under the hood;
get context assembles a
prompt-ready block when you'd rather hand the model the account state than
raw facts; and the
multi-session
research cookbook applies the same typed-fact pattern to a long-running
research thread.