Korely

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 requirements 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: Acme Corp has_champion Marco Rossi, Acme Corp requires a security review, Acme Corp has_budget approved. Ask for Acme and you get its direct edges; the deal is one hop away, through Marco. This cookbook walks the pattern at the call level.

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.

from korely_memory import Korely
korely = Korely(api_key="kor_live_...", region="eu")
REP = "rep-jordan" # the sales rep this book of business belongs to

1. 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 a few 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 requires …) or as an object. The opposite endpoint of each fact is a connected entity: the champion, the requirements, the budget. One call, one account, its direct neighborhood.

facts = korely.get_facts(entity="Acme Corp", user_id=REP)
for f in facts:
print(f.subject, f.predicate, f.object)
# Acme Corp has_champion Marco Rossi
# Acme Corp requires security review
# Acme Corp has_budget approved

entity matches the exact name, or a known alias, on either side of a fact, not a substring.

{
"facts": [
{
"id": "fct_a1",
"subject": "Acme Corp",
"predicate": "has_champion",
"predicate_raw": "has_champion",
"object": "Marco Rossi",
"subject_type": "organization",
"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": "requires",
"predicate_raw": "requires",
"object": "security review",
"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": 3
}

The list above is shortened to two of the three facts, and each fact to its main fields.

Because entity matches the subject or object side, the champion surfaces from the same query as the requirement Acme stated. 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 security review Acme requires

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, so a new edge that conflicts with an old one supersedes it cleanly.

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 returns the superseded edge's id in invalidated, the old employment fact gets an invalid_at timestamp and stops being served. The history stays intact; the current answer stays correct.

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, their role, 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 has_role VP Eng
# walk one hop further: from the deal Marco owns
deal = korely.get_facts(entity="Enterprise renewal", user_id=REP)
# Enterprise renewal targets Q3

Each hop is a deterministic SQL lookup against the facts table, no model call, no embedding 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 are bi-temporal, 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)
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_at
history = 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 "what Acme requires" 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.