Context
Operator input: the named GitHub accounts for the current swarm are relatively new. Most historical Memory Core items predate those IDs and were produced by older Gemini-era runs over roughly 1-1.5 years. The swarm decision was to store those historical records as shared, not as one current agent's private tenant data.
Current symptom: @neo-gpt has restored Memory Core substrate available, but get_all_summaries({limit: 5}) returns zero summaries even though Memory Core health reports hundreds of summaries.
Verify-Before-Assert Footprint
Empirical checks run before filing:
- Memory Core healthcheck:
memories.count = 10391, summaries.count = 980.
- Graph SQLite count:
Nodes = 24266, Edges = 19454, total node+edge artifacts = 43720.
get_all_summaries({limit: 1}): total = 0, summaries = [] for @neo-gpt.
- Direct Chroma SQLite metadata inspection for
neo-agent-sessions:
total = 980
with userId = 46
userId=shared = 0
userId=neo-gpt = 0
- missing
userId = 934
userId distribution: only neo-gemini-3-1-pro = 46
participatingAgents contains GPT in 33 summaries, but current @neo-gpt visibility filter sees 0.
Problem
Summary visibility is identity-filtered too narrowly after restore / historical migration:
SummaryService.listSummaries() filters identity-bound reads to userId = current OR userId = shared.
SummaryService.querySummaries() applies the same current OR shared tenant filter to Chroma queries.
- The restored historical summary collection has no
shared summary rows and no neo-gpt summary rows.
- 934 restored summary rows lack
userId entirely, which makes them invisible to any read path that adds a userId where-clause.
- 24 GPT-participating sample summaries are tagged
neo-gemini-3-1-pro, which points at summarizer-owned metadata rather than participant-visible or swarm-shared semantics.
This creates a mismatch: the system reports 980 restored summaries, but the @neo-gpt read path returns zero, even for summaries where participatingAgents includes GPT.
Code Anchors
ai/services/memory-core/SummaryService.mjs
listSummaries() adds userId=current OR shared when a request identity exists.
querySummaries() builds the same tenant filter for Chroma summary queries.
ai/services/memory-core/SessionService.mjs
summarizeSession() tags generated summary metadata with RequestContextService.getUserId() when present. This records the summarizer identity, not necessarily summary visibility semantics.
ai/mcp/server/shared/services/RequestContextService.mjs
SHARED_USER_ID = 'shared' is the existing shared-commons sentinel.
ai/scripts/backfillChromaSharedUserId.mjs
- Existing one-shot migration concept for legacy Chroma records missing
userId, but the post-restore state shows the summary collection is still effectively unbackfilled from @neo-gpt's point of view.
Contract Ledger Matrix
| Surface |
Current contract |
Broken case |
Target contract |
get_all_summaries |
Return summaries visible to current identity or shared |
Restored legacy summaries missing userId are invisible |
Historical legacy summaries agreed as shared are visible to all three core swarm identities |
query_summaries |
Chroma where filter uses current identity or shared |
No shared rows exist after restore, so GPT gets zero |
Same visibility semantics as get_all_summaries |
| Summary metadata writes |
Generated summary gets current request userId |
Multi-agent session summarized by Gemini becomes Gemini-only even if GPT participated |
Core-swarm shared summaries use shared/participant-safe visibility, not accidental summarizer ownership |
| Restore/backfill tooling |
Legacy Chroma records can be tagged shared by script |
Post-restore canonical data still has 934 summary rows without userId |
Restore or post-restore validation detects and repairs missing summary userId metadata |
Acceptance Criteria
- Add a regression test or fixture that reproduces the current state: summary collection contains restored rows without
userId; identity-bound @neo-gpt reads do not return zero after the fix.
- Ensure historical summary rows that predate current GitHub identities and lack
userId are treated as shared by an idempotent migration/backfill or by a documented restore hook.
- Ensure
get_all_summaries({limit: 5}) for @neo-gpt returns non-empty results after the migration/fix on the restored canonical data.
- Ensure
query_summaries(...) follows the same visibility semantics as get_all_summaries.
- Add coverage for the summarizer-vs-participant ambiguity: a summary generated by one core swarm identity for a multi-agent session must not become private to only the summarizer if the intended contract is shared swarm context.
- Add a diagnostic surface to dry-run/healthcheck/restore validation that reports summary visibility counters: total, missing
userId, shared, per-agent userId, and rows with participatingAgents that are not visible to that participant.
- Do not solve this by removing tenant filtering globally. The fix must preserve tenant isolation for non-shared records and only widen visibility through the existing
shared contract or a documented core-swarm participant contract.
Out of Scope
- A broad privacy model redesign for all Memory Core tenants.
- Destructive re-restore of Chroma or graph data.
- Raw-memory visibility changes unless the same diagnostic proves raw memories have the same restored historical
userId mismatch.
Related
- #10016 - Multi-Tenant Identity & Data Privacy epic.
- #10556 / PR #10567 - original legacy Chroma
userId backfill and additive tenant read filter.
- #10813 - summary boot-context stale/empty follow-up.
- #11151 - restore hardening context that made this restored-state regression observable.
Handoff Retrieval Hints
summary userId shared Chroma visibility get_all_summaries zero
participatingAgents neo-gpt hidden summaries
backfillChromaSharedUserId restored summaries missing userId
Origin session: 22713fa8-23d2-4b31-918b-6e2f48d69c06
Context
Operator input: the named GitHub accounts for the current swarm are relatively new. Most historical Memory Core items predate those IDs and were produced by older Gemini-era runs over roughly 1-1.5 years. The swarm decision was to store those historical records as
shared, not as one current agent's private tenant data.Current symptom:
@neo-gpthas restored Memory Core substrate available, butget_all_summaries({limit: 5})returns zero summaries even though Memory Core health reports hundreds of summaries.Verify-Before-Assert Footprint
Empirical checks run before filing:
memories.count = 10391,summaries.count = 980.Nodes = 24266,Edges = 19454, total node+edge artifacts =43720.get_all_summaries({limit: 1}):total = 0,summaries = []for@neo-gpt.neo-agent-sessions:total = 980with userId = 46userId=shared = 0userId=neo-gpt = 0userId = 934userIddistribution: onlyneo-gemini-3-1-pro = 46participatingAgentscontains GPT in 33 summaries, but current@neo-gptvisibility filter sees0.Problem
Summary visibility is identity-filtered too narrowly after restore / historical migration:
SummaryService.listSummaries()filters identity-bound reads touserId = current OR userId = shared.SummaryService.querySummaries()applies the samecurrent OR sharedtenant filter to Chroma queries.sharedsummary rows and noneo-gptsummary rows.userIdentirely, which makes them invisible to any read path that adds auserIdwhere-clause.neo-gemini-3-1-pro, which points at summarizer-owned metadata rather than participant-visible or swarm-shared semantics.This creates a mismatch: the system reports 980 restored summaries, but the
@neo-gptread path returns zero, even for summaries whereparticipatingAgentsincludes GPT.Code Anchors
ai/services/memory-core/SummaryService.mjslistSummaries()addsuserId=current OR sharedwhen a request identity exists.querySummaries()builds the same tenant filter for Chroma summary queries.ai/services/memory-core/SessionService.mjssummarizeSession()tags generated summary metadata withRequestContextService.getUserId()when present. This records the summarizer identity, not necessarily summary visibility semantics.ai/mcp/server/shared/services/RequestContextService.mjsSHARED_USER_ID = 'shared'is the existing shared-commons sentinel.ai/scripts/backfillChromaSharedUserId.mjsuserId, but the post-restore state shows the summary collection is still effectively unbackfilled from@neo-gpt's point of view.Contract Ledger Matrix
get_all_summariesshareduserIdare invisiblequery_summarieswherefilter uses current identity orsharedsharedrows exist after restore, so GPT gets zeroget_all_summariesuserIduserIduserIdmetadataAcceptance Criteria
userId; identity-bound@neo-gptreads do not return zero after the fix.userIdare treated assharedby an idempotent migration/backfill or by a documented restore hook.get_all_summaries({limit: 5})for@neo-gptreturns non-empty results after the migration/fix on the restored canonical data.query_summaries(...)follows the same visibility semantics asget_all_summaries.userId,shared, per-agentuserId, and rows withparticipatingAgentsthat are not visible to that participant.sharedcontract or a documented core-swarm participant contract.Out of Scope
userIdmismatch.Related
userIdbackfill and additive tenant read filter.Handoff Retrieval Hints
summary userId shared Chroma visibility get_all_summaries zeroparticipatingAgents neo-gpt hidden summariesbackfillChromaSharedUserId restored summaries missing userIdOrigin session:
22713fa8-23d2-4b31-918b-6e2f48d69c06