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 can open already knowing the
obligations you flagged, which questions are still open, and which earlier
conclusion was overturned and why, as long as each was written down. 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 words
end user,
fact,
as_of and
context block 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 project, many sessions
A research thread is one user_id (here the research project;
it is the "end user" whose memory this is), 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 query is embedded on
Korely's own server.
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- hiring module qualifies_for Annex III research exemption (since 2026-06-05)\n\n## Relevant memories\n_A time in square brackets after a relative expression ... is the day it was said._\n- [2026-05-28] Session 2 conclusion: Article 6 high-risk registration applies to our hiring module.\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": 344, "sources": ["fct_...", "mem_...", "mem_..."]}This is real output for two memories written with backdated timestamps (the first on 2026-05-28, the second on 2026-06-05), with the ids shortened. Only the second memory produced a fact. Both memories appear under Relevant memories, each with its date, so the model sees the old conclusion next to the newer one and the dates tell it which is current. A fact makes the current answer easy to read; the dated memory text stays the evidence.
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 open facts
- Sees both dated conclusions
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. In the real run above, the
earlier conclusion produced no fact to close. When a fact is open next to
the one that overturns it, 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
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". Facts with start and end dates 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 (usually after about ten seconds) for 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.