contextkitDocs

Using memory

Memory operations

Add, search, read, update, and delete memories, and see how each one changed.

Every example on this page uses a memory built as in the Quickstart. All methods are on Memory; AsyncMemory has the same methods as coroutines.

add

result = memory.add(messages, user_id="alice", metadata={"source": "chat"})

messages can be a string, a single {"role": ..., "content": ...} dict, or a list of them. At least one of user_id, agent_id, or run_id is required.

Argument Default What it does
user_id, agent_id, run_id None Who the memories belong to. See Scoping.
metadata None Extra fields stored with each memory and usable as filters.
infer True Extract facts with the LLM. False stores each message's content as-is.
memory_type None "procedural_memory" (with an agent_id) stores a summary of the agent's steps instead of facts.
prompt None The system prompt for procedural memory.
1 · facts2 · nearest in scope3 · decision4 · writtenEats fish, no meatextracted by the LLM0: Is vegetarian1: Likes spicy foodUPDATE 0same topic, new facta3f1… updatedhistory: old → new0 is mapped back to a3f1…Allergic to peanutsextracted by the LLMnothing closeADDnew fact4f1c… addedhistory: ADDalso: DELETE (contradicted), NONE (known)infer=Falseeach messagestored as writtenone memory eachchunked if longno LLM call
add decides per fact. Existing memories are shown to the LLM as 0, 1, 2… instead of their real ids, so it cannot invent one; the ids are mapped back before anything is written. orange: an LLM call · blue: a scope or policy check · green: what is stored or returned

With infer=True, the result lists each change the LLM decided on:

{"results": [
    {"id": "4f1c…", "memory": "Allergic to peanuts", "event": "ADD"},
    {"id": "a3f1…", "memory": "Eats fish, no other meat", "event": "UPDATE",
     "previous_memory": "Is vegetarian"},
]}

event is ADD, UPDATE, or DELETE. Facts the LLM judged already known (NONE) are not listed. With a graph store configured, the result also has relations, with the entities that were added and removed.

With infer=False, nothing goes to the LLM: each non-system message becomes one memory. Use it for documents, logs, or text you have already distilled. Long texts can be chunked.

hits = memory.search("dietary restrictions", user_id="alice", limit=5, threshold=0.3)
Argument Default What it does
query required What to look for. It is embedded with the configured embedder.
user_id, agent_id, run_id None At least one is required. Only that scope's memories are searched.
limit 100 The most results to return.
filters None Metadata equality filters, such as {"source": "chat"}.
threshold None Drop results scoring below this.
use_tcmgm, time_window, include_causal off Fused graph and timeline search. See Graph memory.
merge_chunks, chunk_context_window from config Rejoin chunks of one document. See Chunking.

Each result looks like this:

{
    "id": "4f1c…",
    "memory": "Allergic to peanuts",
    "score": 0.82,
    "hash": "…",
    "metadata": {"source": "chat"},
    "created_at": "2026-10-01T09:14:03.511-07:00",
    "updated_at": None,
    "user_id": "alice",
}

metadata holds the metadata you passed to add. The scoping ids, and actor_id and role when present, are promoted to top-level keys. With a graph store configured, search also returns relations: [{"source": ..., "relationship": ..., "destination": ...}].

get and get_all

one = memory.get("4f1c…")                         # a single memory, or None
everything = memory.get_all(user_id="alice", limit=200)
for item in everything["results"]:
    print(item["memory"])

get_all takes the same scoping ids and filters as search, plus limit (default 100), and returns {"results": [...]} without scores.

update

memory.update("4f1c…", "Allergic to peanuts and tree nuts")

Replaces the memory's text, re-embeds it, and records an UPDATE in its history. Returns {"message": "Memory updated successfully!"}.

delete and delete_all

memory.delete("4f1c…")
memory.delete_all(user_id="alice")

delete_all needs at least one scoping id. With Qdrant it removes up to 100 memories per call, so loop until get_all comes back empty when a scope holds more.

history

for row in memory.history("a3f1…"):
    print(row["event"], row["old_memory"], "→", row["new_memory"], row["created_at"])

Every add, update, and delete writes a row, oldest first. Each row has id, memory_id, old_memory, new_memory, event (ADD, UPDATE, or DELETE), created_at, updated_at, is_deleted, actor_id, and role.

reset

memory.reset()

Deletes every memory in the vector store collection and clears the history database. There is no undo.

Procedural memory

For agents that should remember how they did something rather than facts about a user, pass the agent's run as messages with memory_type="procedural_memory" and an agent_id:

memory.add(agent_steps, agent_id="travel-booker", memory_type="procedural_memory")

The LLM writes a summary of the steps and their outcomes, stored as one memory with metadata["memory_type"] = "procedural_memory". Without an agent_id, the call is a normal add.

Async

AsyncMemory mirrors Memory. Note that from_config is a coroutine too:

import asyncio
from outhad_contextkit import AsyncMemory


async def main():
    memory = await AsyncMemory.from_config({
        "vector_store": {"provider": "qdrant", "config": {"path": "./memory", "on_disk": True}},
    })
    await memory.add("I prefer window seats", user_id="alice")
    hits = await memory.search("seat preference", user_id="alice")
    print(hits["results"])


asyncio.run(main())

AsyncMemory.search takes the graph-memory arguments (use_tcmgm, time_window, include_causal) from 0.1.4; in earlier releases, use Memory for those.

Full signatures

The API reference lists every argument of every method in 0.1.4, with its docstring.

Edit this page on GitHub