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_id across all event kinds for a given person — user_message, assistant_message, tool_result all 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_id when the context logically resets (new chat, new task).
  • Keep the same session_id for 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:

ParameterValuesNotes
sortfirst_memory_desc (default), first_memory_asc, last_memory_desc, actor_id_ascSorting by memory count is deliberately unavailable — see Counts can be short below
qany stringCase-insensitive substring match on actor_id
limit, offset1–500, default 100Pages the deduped actor list, so total is the org's distinct-actor count
org_idomit itDefaults 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:

FieldWhat it isWhat it is not
first_memory_atmin(created_at) — when the actor's oldest surviving memory was writtenThe actor's creation time. It moves forward when the oldest memory is reaped or decays away.
last_memory_atmax(created_at) — when the actor's newest memory was writtenA "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