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 session1. 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 fromAbridged, 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 reviewbefore = 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 familysnapshot = 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_factsreturns 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 withinclude_invalidated=True. - Token cost. Re-feeding five sessions of transcript is
thousands of tokens every turn, growing forever. A
get_contextblock 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.