Korely

LlamaIndex

LlamaIndex is a Python framework for building agents. Its agents work through tools: plain Python functions that you wrap as a FunctionTool and attach to a FunctionAgent. This page shows three small tools that call the Korely Python SDK. Once they are attached, the model decides when to recall, save or look something up. The agent keeps no state between runs; the memory lives in Korely.

What the tools do

Korely reads each memory you save and writes facts from it: short statements such as "Luca upgraded to Pro". When a newer fact replaces an older one, the older one is closed. The recall tool uses get_context, which returns a context block with the facts that are true now, plus the best-matching memories. The model gets current knowledge instead of a pile of snippets.

Install

You need the Korely SDK, LlamaIndex core and one LLM package (here OpenAI). The examples were run with korely-memory 0.1.28, llama-index-core 0.14.25 and llama-index-llms-openai 0.8.3 on Python 3.12, with a fake model server, so no model provider was called.

Terminal window
pip install korely-memory llama-index-core llama-index-llms-openai

You also need a Korely API key. Copy it from API Keys in the dashboard at agent.korely.ai and export it, together with your OpenAI key:

Terminal window
export KORELY_API_KEY="kor_live_..."
export OPENAI_API_KEY="sk-..."

Define the memory tools

A LlamaIndex tool is a typed Python function with a docstring. The function name becomes the tool name, and the docstring is what the model reads to decide when to call it, so write the docstring for the model. Wrap each function with FunctionTool.from_defaults(fn=...). There are three tools: recall, save and a targeted search.

memory_tools.py
from korely_memory import Korely
from llama_index.core.tools import FunctionTool
korely = Korely() # reads KORELY_API_KEY from the environment
def recall_memory(query: str) -> str:
"""Recall settled facts and relevant memories about the user.
Reach for this FIRST, before answering anything about the user.
Returns an assembled, prompt-ready block of the user's active
bi-temporal facts, not raw rows.
"""
ctx = korely.get_context(query=query, token_budget=800)
return ctx.context
def save_memory(content: str) -> str:
"""Save something worth remembering about the user.
Pass a single, self-contained statement. Korely extracts the
typed facts from it; you do not write the facts yourself.
"""
memory = korely.add(content)
return f"Saved memory {memory.id}."
def search_memory(query: str) -> str:
"""Find the exact memory that mentioned something specific.
Use this when you need the original wording, not settled facts.
Pass a keyword-style query of 1 to 5 words.
"""
hits = korely.search(query, limit=5)
if not hits:
return "No matching memories."
return "\n".join(f"- ({h.score:.2f}) {h.snippet}" for h in hits)
recall_tool = FunctionTool.from_defaults(fn=recall_memory)
save_tool = FunctionTool.from_defaults(fn=save_memory)
search_tool = FunctionTool.from_defaults(fn=search_memory)

Three tools, three jobs. recall_memory is the one the model should use by default: it returns the current facts already assembled. search_memory finds the exact note that mentioned something, by meaning. save_memory writes. A read does not call a language model on Korely's side (your model does the reasoning), so every plan allows at least 100 times more queries than writes per month.

These tools do not pass a user_id. Reads then cover every end user of the project, which is fine for a personal assistant and wrong for a product with many users. For a product, bind the user inside the functions, for example korely.get_context(query=query, user_id=user_id) and korely.add(content, user_id=user_id), with a user_id that comes from your login and not from the model.

Attach the tools to an agent

Register the tools on a FunctionAgent (for models with a native tool-calling API) and run it. .run() is async, so call it with await inside an async function. The system prompt tells the model to recall before it answers.

agent.py
import asyncio
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.llms.openai import OpenAI
from memory_tools import recall_tool, save_tool, search_tool
agent = FunctionAgent(
tools=[recall_tool, save_tool, search_tool],
llm=OpenAI(model="gpt-4o-mini"),
system_prompt=(
"You are a helpful assistant with long-term memory. "
"Recall memory before answering questions about the user, "
"and save any durable new fact the user tells you."
),
)
async def main():
# First turn: the user states a fact worth keeping.
r1 = await agent.run(
user_msg="I just upgraded to the Pro plan, and I prefer async standups."
)
print(r1)
# Later turn (even a fresh process): the agent recalls it.
r2 = await agent.run(user_msg="Which plan am I on, and how do I like standups?")
print(r2)
asyncio.run(main())

On the first turn the model calls save_memory and Korely extracts the facts. On the second turn it calls recall_memory, and the model answers "Pro plan, async standups" without you passing any state between the calls. The text of the answers depends on your model.

Plain functions also work. The tools list accepts FunctionTool objects or bare Python functions, which LlamaIndex wraps for you. So tools=[recall_memory, save_memory, search_memory] works too. Use from_defaults when you want to set the name or the description yourself.

ReAct agents and several agents

FunctionAgent needs a model with a tool-calling API. For any other model, use ReActAgent. It takes the same tools and the same .run(), and drives the tools through the prompt:

from llama_index.core.agent.workflow import ReActAgent
from llama_index.llms.openai import OpenAI
agent = ReActAgent(
tools=[recall_tool, save_tool, search_tool],
llm=OpenAI(model="gpt-4o-mini"),
system_prompt="Recall memory before answering about the user.",
)
# response = await agent.run(user_msg="What do you remember about me?")

To combine several agents, put them in an AgentWorkflow and attach the memory tools to the agent that should own recall and saving:

from llama_index.core.agent.workflow import AgentWorkflow
workflow = AgentWorkflow(agents=[agent], root_agent=agent.name)
# response = await workflow.run(user_msg="Catch me up on what you know.")

Facts arrive after the write. add() returns when the memory is stored. The facts are written by extraction, usually about ten seconds later. A recall_memory call right after a save_memory in the same turn may not see the new fact, so do not save and recall the same thing inside one .run(). By the next user message the fact is normally there. To check a write, call korely.events() (it wraps GET /v1/events). The memory itself can be found by search_memory at once.

A FunctionAgent forgets between runs. Two separate .run() calls do not share the conversation (we checked this with a fake model). That is why the durable knowledge goes into Korely: it comes back through recall_memory on the next turn, in the next session, or in a new process.

Where to go next

  • Memory as a tool: the full pattern for giving an agent recall, save and search, with prompt tips and scoping by user_id.
  • Get context: how the recall tool builds its block, with the token_budget and source controls.

Something not working? Email [email protected] with your llama-index-core and korely-memory versions and the error output. We read every message.