Korely

A research agent that builds on every session

You're three weeks into researching "EU AI Act compliance for our SaaS" with an agent. A stateless agent opens session 5 by asking what you're researching, re-derives the obligations you already pinned down in session 2, and has no idea you overturned one of them after the legal review. You spend the first ten minutes re-grounding it.

With Korely in the loop, session 5 opens already knowing the four obligations you flagged, which two are still open, and which earlier conclusion was overturned and why. Each session's findings accumulate into typed facts; the contradiction judge can close the old answer instead of letting it pile up; and as_of replays what was true on any date (known_as_of: what the memory had learned by that date). 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 project, many sessions

A research thread is one user_id (the project), and each session is a run_id. Findings written under any session accumulate under the project, so get_context and get_facts see all of them; run_id is there when you want to search or list a single session's memories (search(..., run_id=...), get_all(run_id=...)).

from korely_memory import Korely
korely = Korely(api_key="kor_live_...", region="eu")
PROJECT = "proj-eu-ai-act"
SESSION = "session-2026-06-12" # one run_id per working session

1. Session start: load everything known so far

Before the first turn of a new session, assemble the project's state into a prompt-ready block. get_context returns the active facts most relevant to the query (up to ten) plus the most relevant memories, already fitted to a token budget, in one call. For every active fact, call get_facts or get_profile. No generative model runs on the read path; the only model call is the query embedding.

ctx = korely.get_context(
query="EU AI Act obligations, open questions, decisions so far",
user_id=PROJECT,
token_budget=1200,
)
# ctx.context -> a Markdown block to drop into your system prompt
# ctx.tokens -> how many tokens it used
# ctx.sources -> the fact and memory ids it drew from

Abridged, the block reads:

{
"context": "_The facts below are a compact profile of the user; the memories are the verbatim source of truth. ..._\n\n## Known facts\n- The user has_obligation human oversight (Art. 14) (since 2026-05-28)\n- The user open_question does the hiring module count as high-risk? (since 2026-05-28)\n\n## Relevant memories\n_A time in square brackets after a relative expression ... is the day it was said._\n- [2026-06-05] Legal review: the hiring module qualifies for the Annex III research exemption, so Article 6 high-risk registration does not apply.",
"tokens": 412,
"sources": ["fct_41c2", "fct_41c7", "mem_7c1a", "mem_2b40", "mem_9f08"]
}

The agent now opens the session already grounded. No "remind me what we're working on."

Session 5

You re-open the research thread

  • Three weeks in
  • Resolves to user_id "proj-eu-ai-act"

Korely

get_context(query=...)

  • Active facts + relevant findings, every session
  • Assembled, not re-generated

Turn 1

Agent is already grounded

  • Knows the 4 obligations + 2 open questions
  • Knows the overturned conclusion

2. During the session: search the back-catalogue

When the agent needs a specific past finding, search the project's memories. Scoped to the project, ranked by relevance, no model in the path beyond the query embedding.

hits = korely.search(
"annex III exemption hiring module",
user_id=PROJECT,
limit=5,
)
# each hit: {id, score, snippet, user_id, agent_id, metadata}

3. On a finding: write it down (any session)

When the agent reaches a conclusion, store it. Tag the session with run_id; it still accumulates under the project.

korely.add(
"Legal review: the hiring module qualifies for the Annex III research "
"exemption, so Article 6 high-risk registration does not apply.",
user_id=PROJECT,
run_id=SESSION,
timestamp="2026-06-05", # when the legal review concluded
)

Fact extraction runs server-side: this finding becomes typed (subject, predicate, object) triples a few seconds after the write returns, ready at the next get_facts / get_context.

4. When a finding overturns an earlier one

Session 2 concluded "Article 6 high-risk registration applies." The June 5 legal review written in step 3 contradicts it. You don't write supersede logic: the contradiction detector (a prefilter, then a model judge) looks for the earlier fact and, when it closes it, the old conclusion isn't deleted, it gets an invalid_at timestamp. The closure is a model's judgement and it can miss, most often when the new finding is stated as a consequence of another claim, as it is here. Then both facts stay open and both are served. A single-valued attribute that changes value (a status, a plan, an owner) is the case the judge closes most reliably.

facts = korely.get_facts(user_id=PROJECT)
# returns what is valid now. If the judge closed the Art. 6 "applies" fact, it is gone
# from this list. If it did not, both facts are here: read the memories in
# get_context, or pass include_invalidated=True to see what was closed.

5. Point in time: what was true back then?

This is the part a vector store can't do. Replay the project's facts as it stood on any date, before the legal review, say, for an audit trail or to understand how a conclusion changed.

# what was true before the June 5 legal review
before = korely.get_facts(user_id=PROJECT, as_of="2026-06-04")
# what the memory had learned by June 4 (ingestion time, not event time)
known =korely.get_facts(user_id=PROJECT, known_as_of="2026-06-04")
# the full point-in-time profile, grouped by family
snapshot = korely.get_profile(user_id=PROJECT, as_of="2026-06-04")

as_of="2026-06-04" returns the facts that were valid on that date, so the "Article 6 applies" conclusion is there. It shows a difference from the current read only when the judge closed that fact on June 5; if it stayed open, both reads return it. The history is intact; you're just choosing which moment to read from.

known_as_of is the other axis: when the memory learned a fact, not when the event happened. The findings in this walkthrough are written today with backdated timestamps, so known_as_of="2026-06-04" returns an empty list for them. It returns facts only for memories that were actually written on or before that date.

Why not dump the transcripts into a vector DB?

  • Staleness. A transcript store holds both "Article 6 applies" (session 2) and "Article 6 does not apply" (session 3), and you're trusting the LLM to pick the right one every turn. get_facts returns only what's valid now, and the judge closes many superseded facts for you. Closure is a model's judgement, so for a fact that matters, check with include_invalidated=True.
  • Token cost. Re-feeding five sessions of transcript is thousands of tokens every turn, growing forever. A get_context block stays under the token budget you set, 800 by default, however long you've been researching.
  • No point in time. A vector store has no concept of "what was true on June 4". Bi-temporal facts do.

One honest caveat: korely.add runs fact extraction asynchronously, so a finding's typed facts land a few seconds after the write returns, ready by the next session's get_context, not inside the same call. Write findings fire-and-forget during the session; read the assembled state at the start of the next one.

Where to go next

Temporal facts explains supersession and as_of under the hood; get context is the call in step 1; the memory-as-a-tool cookbook hands these same operations to the model as callable tools.