Authored by Claude Opus 4.8 (Claude Code) as @neo-opus-grace, /lead-role. MC session f5432f03-d2b6-4bc0-a7b5-9fbdafe4b7b9.
Context
Operator directive (PRIO-1, 2026-06-04): raw memory must be a team commons — every swarm maintainer can read every other maintainer's raw memories ("TEAM default mode, allowing access for everyone"). Surfaced when new maintainer @neo-opus-vega reported query_raw_memories returning 0 across multiple queries (incl. a dead-generic term with hundreds of collection-level matches) while query_summaries returned results on the same embedder.
V-B-A (this session): the same dead-generic query_raw_memories run from an established identity (@neo-opus-grace) returns 15 results, all tagged @neo-opus-grace. The read-path + collection are healthy — the 0 is tenant scoping: raw memories are userId-tagged per author on write (MemoryService.mjs:302), and no current policy grants an agent read access to another agent's tagged memories.
The Problem
Tenant scope in MemoryService.queryMemories / listMemories resolves three policies:
private → {userId} (own only)
team → {userId: SHARED_USER_ID} (only records tagged shared)
legacy (current default) → no DB-where; JS post-filter keeps own + untagged + SHARED_USER_ID
Raw memories are author-tagged (each carries its author's userId), never SHARED_USER_ID. So:
- under
legacy, an agent sees own + untagged + shared — never a peer's tagged memories;
- under
team, an agent sees only SHARED_USER_ID-tagged records — which raw memories never are → ~0 for everyone.
There is no policy under which a swarm peer can read another peer's raw memories. team is effectively non-functional for raw memory. (Mirrors the summaries-team regression fixed for summaries only in #12450/#12470; raw memory never got the analogous fix.)
The Architectural Reality
- Write tag:
ai/services/memory-core/MemoryService.mjs:302 — if (userId) metadata.userId = userId on collection.add.
queryMemories team branch: MemoryService.mjs:490-492 — tenantScope = {userId: SHARED_USER_ID}.
listMemories team branch: MemoryService.mjs:~391-393 — same.
- Default policy:
ai/mcp/server/memory-core/config.template.mjs:357 — defaultPolicy: {env:'NEO_MEMORY_SHARING_DEFAULT_POLICY', default:'legacy', parse: parseMemorySharingPolicy} (enum accepts 'team').
- Summaries already return everyone under
team (untagged commons; #12470) — no summaries change needed.
- One Chroma collection per deployment (
collections.memory = neo-agent-memory); the collection is the deployment boundary, so "all records in the collection" = "everyone in this deployment."
The Fix
config.template.mjs:357 — default: 'legacy' → default: 'team' (+ regenerate overlay).
MemoryService.queryMemories team branch → tenantScope = null (no restrictive userId filter; the existing legacy-only post-filter at :519 does not run for team, so all vector results return).
MemoryService.listMemories team branch → tenantScope = null (where reduces to {sessionId}; legacy-only post-filter at :425 does not run for team).
Result ladder: private (own) ⊂ legacy (own+untagged+shared) ⊂ team (deployment-wide).
Contract Ledger
| Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Evidence |
memorySharing.defaultPolicy |
config.template.mjs:357 |
legacy → team (env NEO_MEMORY_SHARING_DEFAULT_POLICY still overrides) |
per-deployment env override to private/legacy |
parseMemorySharingPolicy enum already accepts team |
queryMemories team |
MemoryService.mjs:490 |
{userId:SHARED_USER_ID} → null (deployment-wide) |
private for strict isolation |
this-session V-B-A (15 own vs 0 cross-author) |
listMemories team |
MemoryService.mjs:~391 |
{userId:SHARED_USER_ID} → null |
private |
code read |
querySummaries/listSummaries team |
#12470 |
unchanged (already untagged-commons additive) |
— |
#12470 merged |
Decision Record impact
none — no ADR governs memorySharing.defaultPolicy today. This change sets a de-facto precedent (default → most-permissive); explicitly flagged for cross-family security review. Formalizing the sharing model in an ADR is a possible follow-up (out of scope here).
Acceptance Criteria
Out of Scope
- Summaries policy (already team-additive via
#12470).
- Per-org row-level isolation within a single collection (current model = one collection per deployment; SaaS forks set
defaultPolicy='private').
- Formalizing the sharing model in an ADR.
minTrustTier semantics (orthogonal; unchanged).
Avoided Traps
team = CORE_SWARM_USER_IDS-scoped ($in the swarm set) — REJECTED. CORE_SWARM_USER_IDS (RequestContextService.mjs:31) is stale on dev: ['neo-opus-ada','neo-gemini-pro','neo-gpt'] — missing both @neo-opus-grace and @neo-opus-vega. Scoping team to that set is a maintenance footgun (onboard a teammate, forget the set, they're silently invisible under team). No-filter is robust + matches "access for everyone." (The stale set also gates getActiveA2aParticipants-style checks at :102 — worth its own fix; tracked separately.)
- Env-var-only flip — REJECTED.
NEO_MEMORY_SHARING_DEFAULT_POLICY=team alone makes it worse: team semantics return ~0 until the code fix lands.
Related
#12450 / #12470 — summaries sibling (team made additive; raw memory never got the analogous fix → this ticket).
#12520 — adds @neo-opus-vega (the new maintainer whose 0-results surfaced this).
#12524 — resumeHarness routing (separate; the vega wake-loop fix).
Origin Session ID: f5432f03-d2b6-4bc0-a7b5-9fbdafe4b7b9
Retrieval Hint: "raw memory team policy deployment-wide tenant scoping"; "MemoryService queryMemories team SHARED_USER_ID"
Authored by Claude Opus 4.8 (Claude Code) as @neo-opus-grace, /lead-role. MC session
f5432f03-d2b6-4bc0-a7b5-9fbdafe4b7b9.Context
Operator directive (PRIO-1, 2026-06-04): raw memory must be a team commons — every swarm maintainer can read every other maintainer's raw memories ("TEAM default mode, allowing access for everyone"). Surfaced when new maintainer
@neo-opus-vegareportedquery_raw_memoriesreturning 0 across multiple queries (incl. a dead-generic term with hundreds of collection-level matches) whilequery_summariesreturned results on the same embedder.V-B-A (this session): the same dead-generic
query_raw_memoriesrun from an established identity (@neo-opus-grace) returns 15 results, all tagged@neo-opus-grace. The read-path + collection are healthy — the 0 is tenant scoping: raw memories areuserId-tagged per author on write (MemoryService.mjs:302), and no current policy grants an agent read access to another agent's tagged memories.The Problem
Tenant scope in
MemoryService.queryMemories/listMemoriesresolves three policies:private→{userId}(own only)team→{userId: SHARED_USER_ID}(only records taggedshared)legacy(current default) → no DB-where; JS post-filter keeps own + untagged +SHARED_USER_IDRaw memories are author-tagged (each carries its author's
userId), neverSHARED_USER_ID. So:legacy, an agent sees own + untagged + shared — never a peer's tagged memories;team, an agent sees onlySHARED_USER_ID-tagged records — which raw memories never are → ~0 for everyone.There is no policy under which a swarm peer can read another peer's raw memories.
teamis effectively non-functional for raw memory. (Mirrors the summaries-teamregression fixed for summaries only in#12450/#12470; raw memory never got the analogous fix.)The Architectural Reality
ai/services/memory-core/MemoryService.mjs:302—if (userId) metadata.userId = userIdoncollection.add.queryMemoriesteam branch:MemoryService.mjs:490-492—tenantScope = {userId: SHARED_USER_ID}.listMemoriesteam branch:MemoryService.mjs:~391-393— same.ai/mcp/server/memory-core/config.template.mjs:357—defaultPolicy: {env:'NEO_MEMORY_SHARING_DEFAULT_POLICY', default:'legacy', parse: parseMemorySharingPolicy}(enum accepts'team').team(untagged commons;#12470) — no summaries change needed.collections.memory=neo-agent-memory); the collection is the deployment boundary, so "all records in the collection" = "everyone in this deployment."The Fix
config.template.mjs:357—default: 'legacy'→default: 'team'(+ regenerate overlay).MemoryService.queryMemoriesteam branch →tenantScope = null(no restrictive userId filter; the existing legacy-only post-filter at:519does not run forteam, so all vector results return).MemoryService.listMemoriesteam branch →tenantScope = null(where reduces to{sessionId}; legacy-only post-filter at:425does not run forteam).Result ladder:
private(own) ⊂legacy(own+untagged+shared) ⊂team(deployment-wide).Contract Ledger
memorySharing.defaultPolicylegacy→team(envNEO_MEMORY_SHARING_DEFAULT_POLICYstill overrides)private/legacyparseMemorySharingPolicyenum already acceptsteamqueryMemoriesteam{userId:SHARED_USER_ID}→null(deployment-wide)privatefor strict isolationlistMemoriesteam{userId:SHARED_USER_ID}→nullprivatequerySummaries/listSummariesteam#12470#12470mergedDecision Record impact
none— no ADR governsmemorySharing.defaultPolicytoday. This change sets a de-facto precedent (default → most-permissive); explicitly flagged for cross-family security review. Formalizing the sharing model in an ADR is a possible follow-up (out of scope here).Acceptance Criteria
defaultPolicyresolves toteamby default (config + overlay regenerated).queryMemoriesunderteamreturns cross-author records (an agent retrieves a peer's raw memory by content). Unit-covered.listMemoriesunderteamreturns cross-author session records. Unit-covered.privatestill returns own-only;legacystill own+untagged+shared (no regression). Unit-covered.@neo-gpt) given the most-permissive-default posture change (per the#12470precedent).Out of Scope
#12470).defaultPolicy='private').minTrustTiersemantics (orthogonal; unchanged).Avoided Traps
team=CORE_SWARM_USER_IDS-scoped ($inthe swarm set) — REJECTED.CORE_SWARM_USER_IDS(RequestContextService.mjs:31) is stale ondev:['neo-opus-ada','neo-gemini-pro','neo-gpt']— missing both@neo-opus-graceand@neo-opus-vega. Scopingteamto that set is a maintenance footgun (onboard a teammate, forget the set, they're silently invisible underteam). No-filter is robust + matches "access for everyone." (The stale set also gatesgetActiveA2aParticipants-style checks at:102— worth its own fix; tracked separately.)NEO_MEMORY_SHARING_DEFAULT_POLICY=teamalone makes it worse:teamsemantics return ~0 until the code fix lands.Related
#12450/#12470— summaries sibling (team made additive; raw memory never got the analogous fix → this ticket).#12520— adds@neo-opus-vega(the new maintainer whose 0-results surfaced this).#12524— resumeHarness routing (separate; the vega wake-loop fix).Origin Session ID: f5432f03-d2b6-4bc0-a7b5-9fbdafe4b7b9 Retrieval Hint: "raw memory team policy deployment-wide tenant scoping"; "MemoryService queryMemories team SHARED_USER_ID"