Concepts
Actors and Sessions
How actor_id and session_id control grouping and retrieval.
Events carry two identifiers that control how Memsy groups and retrieves memories: actor_id and session_id. Getting these right upfront makes search meaningfully better later.
actor_id — who
The actor is the entity the memory is about. In most applications this is the end user. It can also be an AI agent, a customer account, or any stable identity you want to attach memories to.
-
Pick an ID that is stable across sessions (your internal user UUID), not something that rotates (like a JWT).
-
Use the same
actor_idacross all event kinds for a given person —user_message,assistant_message,tool_resultall share the actor. -
Scope a search to one actor:
client.search("preferences", actor_id="user_42")
session_id — where in the conversation
The session is the immediate context window the event belongs to — a conversation, a thread, a job run.
- Start a new
session_idwhen the context logically resets (new chat, new task). - Keep the same
session_idfor every event that belongs to one conversational flow, including assistant replies and tool results.
Sessions scope retrieval too: pass session_id to search inside one conversation, or exclude_session_id to search every conversation but that one — see Conversation scoping. Memsy also uses sessions during extraction to understand what's in scope.
A complete example
from memsy import MemsyClient, EventPayload
client = MemsyClient(base_url="...", api_key="***")
client.ingest([
EventPayload(
actor_id="user_42", session_id="sess_abc",
kind="user_message",
content="My dog's name is Rex.",
),
EventPayload(
actor_id="user_42", session_id="sess_abc",
kind="assistant_message",
content="Nice — Rex is a great name.",
),
])
# Later, a different conversation
client.ingest([
EventPayload(
actor_id="user_42", session_id="sess_xyz",
kind="user_message",
content="Book a vet visit for tomorrow.",
),
])
# Both sessions contribute memories to user_42
results = client.search("pets", actor_id="user_42")Listing the actors in an org
You never register an actor — an actor_id exists because events arrived carrying it. To
find out which actors your org actually has, list them:
page = client.actors.list(sort="first_memory_desc", q="slack")
print(page.total) # distinct actors in the org
for actor in page.items:
print(actor.actor_id, actor.first_memory_at, actor.memory_count)
# One actor; raises MemsyAPIError (404) if it has no memories in this org
actor = client.actors.get("user_42")With no arguments you get the first 100 actors, ordered by most recent first memory. Refine with:
| Parameter | Values | Notes |
|---|---|---|
sort | first_memory_desc (default), first_memory_asc, last_memory_desc, actor_id_asc | Sorting by memory count is deliberately unavailable — see Counts can be short below |
q | any string | Case-insensitive substring match on actor_id |
limit, offset | 1–500, default 100 | Pages the deduped actor list, so total is the org's distinct-actor count |
org_id | omit it | Defaults to the org behind your API key. Supplying a different one returns 403 |
Nothing is stored. The server folds your org's memories on actor_id on every request,
so there is no actor record to write to — no create, no rename, no delete. Renaming would
mean rewriting every event and memory scope that carries the old id.
The timestamps measure the memories, not the actor. There is no actor creation event to
read, so both are drawn from the memories' created_at:
| Field | What it is | What it is not |
|---|---|---|
first_memory_at | min(created_at) — when the actor's oldest surviving memory was written | The actor's creation time. It moves forward when the oldest memory is reaped or decays away. |
last_memory_at | max(created_at) — when the actor's newest memory was written | A "last used" signal. Only a write advances it — reads never do, so an actor searched daily can still show a months-old value. |
Counts can be short. When truncated is true, the server hit its row-scan cap before
reading every memory in the org. The actors listed are real, but the list may be incomplete
and every memory_count is an under-count — treat the counts as unusable while it is
set. That is also why there is no sort by count: it would rank on a number known to be wrong.
New actors take a moment to appear. An actor whose events have not been extracted into
memories yet is not listed, and get returns 404 for it. After your first ingest, wait for
extraction to finish — see Async Processing.
Shared actor IDs vs. isolation
Memories are always isolated per organization (each API key belongs to one org). Within an org, memories are keyed by actor_id. If two users share an actor_id, they share memories — so don't reuse IDs across people.
Next
- How
ingest()returns before processing completes → Async Processing.

