Once a memory is ingested and the job reaches succeeded, it’s immediately queryable. There are three read paths:
search — vector-ranked retrieval, scoped by the ids you pass. Use when you have a natural-language query.
list — paginated browse by scope, no query. Use for “all of Alice’s memories”.
recall — convenience over search that combines a user’s own memories and a shared group’s into one ready-to-inject prompt (see below).
Vector search
const results = await client.memories.search({
query: 'what does the user like to eat?',
user_id: 'alice',
limit: 10,
});
for (const m of results.data) {
console.log(m.score?.toFixed(2), '·', m.text);
}
query is required (non-empty). The server embeds it and ranks by cosine similarity; data is a list of Memory rows with .score populated.
Scoping a search
There is no filter DSL. Search is “scope by what you pass”: supply any combination of the axes below and each one AND-narrows the result. An omitted axis is unconstrained, and at least one axis is required — an unscoped search returns 422.
| Axis | Scopes to |
|---|
user_id | one user’s memories |
group_ids | memories tagged to any of these groups (cross-user) |
agent_id | one agent |
app_id | one app |
// Alice's memories only
await client.memories.search({ query, user_id: 'alice' });
// Alice's memories, narrowed to one agent
await client.memories.search({ query, user_id: 'alice', agent_id: 'planner' });
Everything you pass is AND’d together; your org is always implicit (from the API key).
user_id is required on ingest but optional on search — omit it and pass group_ids to read a whole group across users (next section).
Reading shared memories (groups)
Memories tagged to a group at ingest are visible to anyone who searches that group. group_ids matches any-of — a row tagged to any of the requested groups qualifies.
// The whole Tokyo trip, across every member — omit user_id:
await client.memories.search({ query, group_ids: ['grp_tokyo2026'] });
// Alice's OWN slice of the trip (intersection: user_id AND group):
await client.memories.search({ query, user_id: 'alice', group_ids: ['grp_tokyo2026'] });
Because scoping is AND-everything, { user_id, group_ids } is an intersection (Alice’s rows that are also tagged to the trip), not a union. For “Alice’s own memories plus the trip’s shared memories” in one call, use recall.
Personal and shared with recall
recall runs the personal and shared scopes in parallel, dedupes, and returns a single context block — the combined read that AND-scoping can’t express in one search.
const { prompt, memories } = await client.memories.recall({
query: 'what should we plan for dinner on the trip?',
pools: [
{ user_id: 'alice' }, // her dietary prefs
{ group_ids: ['grp_tokyo2026'] }, // the trip's shared restaurant picks
],
});
// inject `prompt` into your agent's context
prompt is sectioned by Personal + each group’s name, with shared lines attributed to their author (you: / <user_id>:). It resolves group names automatically and won’t bleed in the user’s other groups. Full options in the SDK reference and the Groups guide.
Search modes: retrieve vs compose
mode controls whether the server’s LLM context-selection pass runs:
mode | data | context | Use when |
|---|
compose (default) | the agent-selected subset | assembled markdown block | you want a ready-to-inject context prompt |
retrieve | the raw vector-ranked rows | null | you want pure results to process yourself — cheaper, no LLM |
// Raw rows, no LLM pass:
const raw = await client.memories.search({ query, user_id: 'alice', mode: 'retrieve' });
// Assembled context for a prompt (default):
const composed = await client.memories.search({ query, user_id: 'alice', mode: 'compose' });
console.log(composed.context); // "Relevant memories about the user:\n- ..."
search and list return has_more and next_cursor. The SDK’s list is an async iterator that paginates automatically:
for await (const memory of client.memories.list({ user_id: 'alice', limit: 50 })) {
// …each memory across all pages
}
For cursor-level control use listPage:
const page = await client.memories.listPage({ user_id: 'alice', limit: 50, cursor });
list accepts the same scope keys plus type (fact | artifact | episode) and order. Cursors are tenant-scoped — a cursor from one (org, key) pair can’t be used by another.
Retrieving a single memory
const memory = await client.memories.get('5b0d0f7d-d502-45d5-847d-e19512b5c517');
Returns the full row — including details.full_content for artifacts, which search and list omit by default (it can be large). 404s if the id doesn’t exist or was deleted.
See also
- Ingesting memories — what gets stored, and tagging to groups
- Groups — register groups and share memory across users
- API Reference → Memories → Search — every field, generated from the live spec