Korely

Cookbook: a coaching app that tracks the journey

The scenario: a leadership-coaching platform where coachees talk to an AI coach between their human sessions. The human coach meets Anna once a month. The AI coach is there the other twenty-nine days. The value of coaching is the longitudinal arc: the goal set in January, the relapse in March, the breakthrough in May. A stateless LLM restarts at zero on every chat. In this product, memory isn't a feature you bolt on, it is the product. This cookbook walks through the four calls that make it work, and why bi-temporal facts fit a coaching journey better than either a vector store or an overwrite-style memory.

Pick your surface. The snippets below use the Python SDK. Every call has a REST twin (GET /v1/facts, POST /v1/memories/search, POST /v1/memories). Same data model on both; see surfaces.

The shape of the journey

Six months of Anna, as the memory layer sees it:

January · Kickoff

Goal set

  • Fact: Anna wants to improve public speaking · valid_from 2026-01-12

Korely

Fact stored, graph linked

  • Typed triple · subject: Anna

March · Check-in

The relapse

  • "I bailed on the demo again." Recorded as a dated fact; the January goal stays active

May · Breakthrough

Goal achieved, superseded

  • January fact invalidated (invalid_at 2026-05-04) · next goal: lead the EMEA pitch

Note what does not happen in May: the January goal is not deleted. It is invalidated, closed with a date, and the chain stays queryable. That distinction is the rest of this page.

Opening a session

Before the AI coach says a word, pull the longitudinal context. Scope the read to Anna with user_id to get every typed fact known about her — goals, milestones, and commitments. The predicate family is a coarse taxonomy, so narrow with it only when a predicate maps to a real family (e.g. preferences for how Anna works and takes feedback); otherwise filter by the person and read the whole arc.

korely sdk python
from korely_memory import Korely

korely = Korely(api_key="kor_live_...")

# Goals, milestones, commitments — every active fact about Anna
goals = korely.get_facts(
    user_id="coachee-anna",          # your end user's identifier
)

# Working style, feedback preferences
style = korely.get_facts(
    user_id="coachee-anna",
    predicate_family="preferences",
)

At the March check-in, goals comes back with the January commitment still active:

{
"facts": [
{
"subject": "Anna",
"predicate": "committed_to",
"predicate_raw": "committed_to",
"object": "improving public speaking",
"predicate_family": "other",
"valid_from": "2026-01-12T09:30:00Z",
"invalid_at": null,
"invalidated_by": null,
"source_memory_id": "mem_3a91c0"
}
],
"total": 1
}

Fact reads are deterministic SQL lookups. No generative model composes output on the read path, so the call typically returns in under 50 ms and session open feels instant. user_id is a free-form string identifying your end user; end users are unlimited on every tier.

Python and Node.js SDKs are available now; see SDK reference →

Goal evolution is bi-temporal gold

Goals don't get edited in coaching. They get achieved, abandoned, or replaced, and the replacement is the story. Korely models this with bi-temporal validity: every fact carries valid_from and invalid_at, and a two-stage contradiction check closes the old fact when a new one supersedes it. The default read returns only what is true now. Pass include_invalidated=True and you get the whole journey:

journey = korely.get_facts(
user_id="coachee-anna",
include_invalidated=True, # the arc, not just the present
)

The chain that comes back is Anna's progress report:

Factvalid_frominvalid_atStatus
Anna wants to improve public speaking 2026-01-12 2026-05-04 Superseded — achieved
Anna delivered the Q1 all-hands talk 2026-05-04 Active
Anna wants to lead the EMEA expansion pitch 2026-05-04 Active

No "generate a progress narrative" prompt hallucinating dates. The data structure is the report; the LLM only adds prose around it.

Sessions vs. the arc

Two scopes, two jobs. run_id is one conversation; it keeps a single session coherent. user_id is the person; it accumulates the cross-session arc that survives when the chat closes.

korely.add(
session_transcript,
user_id="coachee-anna", # the arc (cross-session)
agent_id="exec-coach", # your app's namespace
run_id="session-2026-03-14", # this conversation
)

The session layer is disposable; the user layer is the asset. Salient session facts get promoted into the user-scoped graph. That is how March's "I bailed on the demo" becomes a dated fact in May. The write path is where the intelligence runs: embeddings, entity extraction, and typed-fact extraction with contradiction checking, about a tenth of a cent per memory, all included. Write fire-and-forget during the chat; extraction runs server-side and is asynchronous, so the freshly extracted facts may not appear on the immediate response, but they are in place for the next session open.

The weekly recap

Every Sunday the app sends Anna a "since we started" recap. The trap is generating it from vibes: summarized chat history produces warm, vague, dateless prose. Ground it instead:

# The moat: the dated, typed facts are the backbone of the recap
facts = korely.get_facts(
user_id="coachee-anna",
include_invalidated=True,
)
# Secondary: a few raw memory snippets for colour and context
hits = korely.search(
"commitments",
user_id="coachee-anna",
limit=10,
)
for h in hits:
print(h.id, h.score, h.snippet)
# Both go into the recap prompt: the LLM writes connective tissue,
# every claim has a fact with a date behind it.

"On January 12 you committed to improving your public speaking. On May 4 you delivered the all-hands talk." Every line is checkable against the table above. That is a coaching product, not a chatbot with a nice system prompt.

Why supersede beats overwrite

Progress is a sequence of superseded facts. That sentence is the whole design argument. The two failure modes it avoids:

  • Overwrite-style memory keeps only the present. The arc, the thing the coachee is paying for, is destroyed on every update.
  • Append-everything memory keeps the past but can't tell it from the present: the coach retrieves the January goal in June and congratulates Anna on something she finished in May.

Bi-temporal validity is exactly the data structure of a coaching journey: current state on the default read path, full history one parameter away, an explicit supersede edge connecting them.

The coachee can see all of this. Korely's Memory Panel lets the end user view, correct, and delete the facts held about them, built-in forget flows your users control. In coaching, that's a selling point, not a checkbox: "here is everything your coach remembers, and you hold the eraser."

Scaling to multiple coaches

Add programs, say an executive coach and a fitness coach serving the same person. agent_id is the per-program namespace; user_id stays the person. Filters are additive (AND):

# Just the exec-coaching program's view of Anna
korely.get_facts(user_id="coachee-anna", agent_id="exec-coach")
# Just the fitness program
korely.get_facts(user_id="coachee-anna", agent_id="fitness-coach")
# The whole person, across programs
korely.get_facts(user_id="coachee-anna")

The canonical sizing rule applies: one program serving 10,000 coachees is one agent_id and 10,000 user_id values, not 10,000 agents. End users are unlimited on every tier; what is metered is memories written and queries made. See pricing for quotas. Coaching is the use case that made us build the temporal layer.

Where to go next