feat(memory): endUserId scoping for episodes + search#925
Merged
harshithmullapudi merged 1 commit intoJul 3, 2026
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Adds an optional endUserId field to Episode (Neo4j), EpisodeEmbedding (Postgres), and Document (compacted-session table) so callers can identify the human counterparty an episode is about — the visitor, customer, or contact on the other side of the workspace owner. Mirrors the OpenAI/Anthropic API `user` field, applied to memory graph scoping. Ingest surface: POST /api/v1/add and POST /api/v2/search accept an optional endUserId (single, on ingest) and endUserIds (array, on search). The ingest value flows through preprocessing, KnowledgeGraph, storeEpisodeEmbedding, and session compaction so it lands on the Episode node, the episode_embeddings row, and the Document row. Search filter: endUserIds is threaded through both v1 and v2 paths. Every graph-provider method that returns episodes / statements / documents / facets now accepts endUserIds and hard-filters at the Cypher / SQL WHERE layer. Vector search (episode namespace) pushes the filter to pgvector. When endUserIds is omitted, behavior is unchanged — no scoping. Also documents the new concept at docs/memory/end-users.mdx and adds endUserIds to memory reference. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
50460fb to
880726c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an optional
endUserIdfield to episodes so callers can identify the human counterparty an episode is about — the visitor, customer, contact, or student on the other side of the workspace owner. Same shape as OpenAI Chat Completionsuserand Anthropic Messagesmetadata.user_id, applied to memory graph scoping.The follow-through:
POST /api/v1/addaccepts optionalendUserId(single string). Value flows through preprocessing →KnowledgeGraphService.addEpisode→saveEpisode(Neo4jEpisode.endUserId) and →storeEpisodeEmbedding(Postgresepisode_embeddings.endUserId). Session compaction inherits it fromepisodes[0]and stampsDocument.endUserId.POST /api/v1/searchandPOST /api/v2/searchaccept optionalendUserIds: string[]. Threaded throughSearchService.search(v1),searchV2/handleAspectQuery/handleEntityLookup/handleTemporal/handleTemporalFacets/handleExploratory/handleRelationship(v2), and every graph provider method + pgvector's episode-namespace WHERE clause. When omitted, behavior is unchanged.endUserIds: [...]hard-filters to matching episodes; owner-only (null) episodes are excluded. Passing multiple values ORs them./docs/memory/end-users.mdxpage,endUserIdsadded tomemory_searchin the memory reference.Migration
20260701000000_episode_embedding_enduser_id/migration.sqladds nullableendUserIdcolumns +(workspaceId, endUserId)indexes onepisode_embeddingsandDocument. Existing rows stay null → existing search behavior unchanged.What's in this branch
Two commits ahead of
main:chore(scratchpad): drop daily widget panel; group slash widgets; corner-chip dismiss— pre-existing on this branch, unrelated to the endUserId work.feat(memory): add endUserId scoping to episodes + search— this change.Consider whether to split before merge or ship together.
Known limitations (follow-up)
aspectStore.server.ts— Directive / Preference / Habit / Belief / Goal) live in Postgres and don't yet carryendUserId;RecallResult.voiceAspectsis not scoped by the filter. Would need aVoiceAspectNode.endUserIdschema change.memory_searchtool schema does NOT exposeendUserIds(intentionally left off per current scope).replaceWithCompactsdoesn't re-checkCompactedSession.endUserId— safe today because sessions are per-conversation and share oneendUserIdat ingest, but noted in a comment.Test plan
endUserId: "visitor:alice"viaPOST /api/v1/add; verify Neo4jEpisode.endUserIdand Postgresepisode_embeddings.endUserIdare populated.endUserId; ingest a third with noendUserId.POST /api/v2/searchwithendUserIds: ["visitor:alice"]— verify only Alice's episodes / statements / facets come back.endUserIdsomitted — verify all three episodes surface (backwards-compat).POST /api/v1/searchwithendUserIds: ["visitor:alice"]— verify BM25, vector, BFS, episode-graph paths all filter.Document.endUserIdinherits from the session's episodes.handleTemporalFacetstopic/entity/aspect counts respectendUserIds.🤖 Generated with Claude Code