Skip to content

feat(memory): endUserId scoping for episodes + search#925

Merged
harshithmullapudi merged 1 commit into
mainfrom
chore/scratchpad-cleanup-and-slash-menu-polish
Jul 3, 2026
Merged

feat(memory): endUserId scoping for episodes + search#925
harshithmullapudi merged 1 commit into
mainfrom
chore/scratchpad-cleanup-and-slash-menu-polish

Conversation

@harshithmullapudi

Copy link
Copy Markdown
Member

Summary

Adds an optional endUserId field 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 Completions user and Anthropic Messages metadata.user_id, applied to memory graph scoping.

The follow-through:

  • IngestPOST /api/v1/add accepts optional endUserId (single string). Value flows through preprocessing → KnowledgeGraphService.addEpisodesaveEpisode (Neo4j Episode.endUserId) and → storeEpisodeEmbedding (Postgres episode_embeddings.endUserId). Session compaction inherits it from episodes[0] and stamps Document.endUserId.
  • SearchPOST /api/v1/search and POST /api/v2/search accept optional endUserIds: string[]. Threaded through SearchService.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.
  • Semantics — endUserId is nullable. Passing endUserIds: [...] hard-filters to matching episodes; owner-only (null) episodes are excluded. Passing multiple values ORs them.
  • Docs — new /docs/memory/end-users.mdx page, endUserIds added to memory_search in the memory reference.

Migration

20260701000000_episode_embedding_enduser_id/migration.sql adds nullable endUserId columns + (workspaceId, endUserId) indexes on episode_embeddings and Document. Existing rows stay null → existing search behavior unchanged.

What's in this branch

Two commits ahead of main:

  1. chore(scratchpad): drop daily widget panel; group slash widgets; corner-chip dismiss — pre-existing on this branch, unrelated to the endUserId work.
  2. feat(memory): add endUserId scoping to episodes + search — this change.

Consider whether to split before merge or ship together.

Known limitations (follow-up)

  • Voice aspects (aspectStore.server.ts — Directive / Preference / Habit / Belief / Goal) live in Postgres and don't yet carry endUserId; RecallResult.voiceAspects is not scoped by the filter. Would need a VoiceAspectNode.endUserId schema change.
  • MCP memory_search tool schema does NOT expose endUserIds (intentionally left off per current scope).
  • v1's replaceWithCompacts doesn't re-check CompactedSession.endUserId — safe today because sessions are per-conversation and share one endUserId at ingest, but noted in a comment.

Test plan

  • Ingest an episode with endUserId: "visitor:alice" via POST /api/v1/add; verify Neo4j Episode.endUserId and Postgres episode_embeddings.endUserId are populated.
  • Ingest a second episode with a different endUserId; ingest a third with no endUserId.
  • POST /api/v2/search with endUserIds: ["visitor:alice"] — verify only Alice's episodes / statements / facets come back.
  • Same call with endUserIds omitted — verify all three episodes surface (backwards-compat).
  • POST /api/v1/search with endUserIds: ["visitor:alice"] — verify BM25, vector, BFS, episode-graph paths all filter.
  • Run session compaction; verify Document.endUserId inherits from the session's episodes.
  • Verify handleTemporalFacets topic/entity/aspect counts respect endUserIds.

🤖 Generated with Claude Code

@mintlify

mintlify Bot commented Jul 1, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
tegon 🟢 Ready View Preview Jul 1, 2026, 11:12 AM

💡 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>
@harshithmullapudi harshithmullapudi force-pushed the chore/scratchpad-cleanup-and-slash-menu-polish branch from 50460fb to 880726c Compare July 1, 2026 11:15
@harshithmullapudi harshithmullapudi merged commit db55f3a into main Jul 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant