feat: per-sender session isolation (by Wren)#264
Open
conoremclaughlin wants to merge 7 commits intomainfrom
Open
feat: per-sender session isolation (by Wren)#264conoremclaughlin wants to merge 7 commits intomainfrom
conoremclaughlin wants to merge 7 commits intomainfrom
Conversation
c9a8b6f to
d87695c
Compare
Add contact_id as a first-class dimension on sessions and memories so external senders (Telegram/WhatsApp/Discord) get isolated conversation history and memory per contact. Migration: nullable contact_id FK on sessions, memories, memory_history with composite indexes and updated archive triggers. Contact auto-resolution: findOrCreateByPlatformId and findOrCreateGroupContact on ContactsRepository, registered in DataComposer. Session layer: contact-scoped session lookups in findByUserAndAgent and findByThreadKey. getOrCreateSession passes contactId through. Memory scoping: remember() writes contact_id, recall() and getKnowledgeMemories() filter by contactId. Semantic recall post-filters since RPC functions don't support the column yet. Gateway: messageHandler resolves contact from sender identity on external channels before building SessionRequest. Context: context-builder passes contactId to memory loading. RequestContextData includes contactId for tool context. All changes backward compatible — NULL contact_id = owner/system. Co-Authored-By: Wren <noreply@anthropic.com>
…or handling 18 new tests covering: - Contact auto-resolution (findOrCreateByPlatformId, findOrCreateGroupContact) - Duplicate create race condition handling - Contact-scoped session lookup/creation - Session contactId mapping (DB ↔ domain) - Contact-scoped memory remember/recall - Owner session (null contactId) behavior Fix: Supabase errors are plain objects, not Error instances. The race condition catch now checks err.message and err.code directly. Co-Authored-By: Wren <noreply@anthropic.com>
… through start_session CLI: - --contact-id <uuid>: direct contact scoping for per-sender isolation - --sender <platform:id>: resolves via resolve_contact MCP tool (graceful fallback) - contactId passed to start_session for contact-scoped session creation API: - start_session schema accepts contactId - startSession() writes contact_id to DB - getActiveSession() filters by contact_id when provided - Fix duplicate session_transcript_archives type from rebase Co-Authored-By: Wren <noreply@anthropic.com>
d87695c to
1adf386
Compare
API endpoint resolves platform:id → contact with optional auto-create. sb chat --sender now calls the API instead of a non-existent MCP tool. Co-Authored-By: Wren <noreply@anthropic.com>
5 integration tests verifying: - --contact-id passes contactId to start_session - Different contacts create separate sessions - Owner sessions (no flag) have no contactId - --sender resolves via /api/admin/contacts/resolve - User messages flow through backend with contact-scoped session Co-Authored-By: Wren <noreply@anthropic.com>
…thread isolation Blocker 1: remember/recall now auto-inherit contactId from session context. - resolveContactId() checks params → request context → session context - handleStartSession writes contactId to session context after creation - SBs no longer need to manually thread contactId through every tool call Blocker 2: threadKey lookup is now fully contact-isolated. - findByThreadKey passes contactId through in session-service - getActiveSessionByThreadKey in memory-repository filters by contact_id - handleStartSession passes contactId to threadKey lookup Updated existing tests to expect the new 5th arg on findByThreadKey/ getActiveSessionByThreadKey calls. Co-Authored-By: Wren <noreply@anthropic.com>
…ollision Lumen's final blocker: owner/system requests could still match contact-scoped sessions because the non-threaded lookup path only passed contactId when truthy. Fixes: - session-service: always passes contactId (including undefined) so the repo's 'contactId in options' check triggers IS NULL filtering - memory-repository: getActiveSession and getActiveSessionByThreadKey always filter by contact — truthy = eq, falsy = IS NULL - Updated test: split 'owner session' test into backward-compat (no options) vs explicit NULL filtering (contactId: undefined) - New test: verifies IS NULL filter for owner session path Co-Authored-By: Wren <noreply@anthropic.com>
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
contact_idas a first-class dimension on sessions and memories so external senders (Telegram/WhatsApp/Discord) get isolated conversation history and memory per contact. This is the foundation for shared SBs — agents that serve multiple external people independently.contact_idFK on sessions, memories, memory_history with composite indexes and updated archive triggersfindOrCreateByPlatformIdandfindOrCreateGroupContact— auto-creates contacts from incoming channel messagesremember()writescontact_id,recall()andgetKnowledgeMemories()filter by itcontactIdto memory loading,RequestContextDataincludes itAll changes backward compatible —
NULL contact_id= owner/system/inter-agent (existing behavior unchanged).Spec
pcp://specs/per-sender-session-isolation(v2) — reviewed by Myra and Lumen.Key design decisions:
memory_modelon agent_identities), not a per-memory attributetype: 'group'), DMs are individual contacts (type: 'external')Test plan
remember()in sender session → verifyrecall()from other sender doesn't see itcontact_id = NULL🤖 Generated with Claude Code