---
id: 12527
title: 'Raw-memory team reads return ~0 — grant deployment-wide access, default to team'
state: CLOSED
labels:
  - bug
  - ai
  - architecture
assignees:
  - neo-claude-opus
createdAt: '2026-06-04T17:59:57Z'
updatedAt: '2026-06-07T17:18:51Z'
githubUrl: 'https://github.com/neomjs/neo/issues/12527'
author: neo-claude-opus
commentsCount: 0
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-06-05T16:09:46Z'
---
# Raw-memory team reads return ~0 — grant deployment-wide access, default to team

Authored by Claude Opus 4.8 (Claude Code) as @neo-claude-opus, /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-claude-opus`) returns **15 results, all tagged `@neo-claude-opus`**. 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
1. `config.template.mjs:357` — `default: 'legacy'` → `default: 'team'` (+ regenerate overlay).
2. `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).
3. `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
- [ ] `defaultPolicy` resolves to `team` by default (config + overlay regenerated).
- [ ] `queryMemories` under `team` returns cross-author records (an agent retrieves a peer's raw memory by content). Unit-covered.
- [ ] `listMemories` under `team` returns cross-author session records. Unit-covered.
- [ ] `private` still returns own-only; `legacy` still own+untagged+shared (no regression). Unit-covered.
- [ ] Cross-family security review (`@neo-gpt`) given the most-permissive-default posture change (per the `#12470` precedent).

## 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-claude-opus` 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"

## Timeline

- 2026-06-04T17:59:58Z @neo-claude-opus assigned to @neo-claude-opus
- 2026-06-04T17:59:59Z @neo-claude-opus added the `bug` label
- 2026-06-04T17:59:59Z @neo-claude-opus added the `ai` label
- 2026-06-04T17:59:59Z @neo-claude-opus added the `architecture` label
- 2026-06-04T18:38:47Z @neo-claude-opus referenced in commit `87af152` - "feat(ai): default memory-sharing to team for deployment-wide raw reads (#12527)

Raw memory becomes a team commons per operator direction: every maintainer reads every maintainer's raw memories (transparent swarm introspection).

- memorySharing.defaultPolicy: legacy -> team
- queryMemories + listMemories team branch: tenantScope=null (deployment-wide, no restrictive userId filter); the legacy-only post-filter does not run for team
- private/legacy unchanged (isolation stays opt-in); summaries already deployment-wide under team (#12470)
- Tenant-isolation spec: team tests assert cross-author visibility, legacy/private pinned via explicit policy; 16/16 green under team default"
- 2026-06-04T18:40:21Z @neo-claude-opus cross-referenced by PR #12532
- 2026-06-04T19:35:36Z @neo-claude-opus cross-referenced by #12536
- 2026-06-04T23:08:26Z @neo-claude-opus cross-referenced by PR #12539
- 2026-06-05T14:11:47Z @neo-claude-opus referenced in commit `e0c073d` - "fix(memory-core): listMemories memorySharing override for team-read symmetry (#12527)

listMemories read its policy only from the ambient AiConfig defaultPolicy, unlike queryMemories which already accepts a memorySharing override. Under the new team default this left listMemories untestable without mutating the shared AiConfig singleton (ADR-0019 safety-critical antipattern) and asymmetric with queryMemories.

Add the optional memorySharing param (mirrors queryMemories) so team / deployment-wide reads are explicit and unit-testable; existing callers are unchanged (param defaults to the ambient policy).

config.template.spec: assert defaultPolicy now resolves to team. MemoryService.TenantIsolation.spec: drive listMemories team reads via the explicit override instead of relying on (or mutating) the ambient singleton. Verified: target specs 42 passed; both touched specs green in isolation.

Authored by neo-claude-opus (Claude Opus)."
- 2026-06-05T14:24:57Z @neo-claude-opus referenced in commit `b220028` - "test(memory-core): cross-tenant integration covers team-default + private isolation (#12527)

The MC cross-tenant isolation integration test asserted isolation under the (now team) default and broke once raw reads went deployment-wide. Restructured to the split-coverage shape: pin the leak guard to explicit memorySharing='private' (the contract multi-tenant forks rely on), and add a positive team-default assertion (alice reads bob's same-session record, deployment-wide — the operator PRIO-1 behavior).

query_raw_memories already threads memorySharing (toolService binds it straight to queryMemories; openapi exposes it), so this is test-only. Verified: syntax-checked; logic mirrors the unit private/team coverage. Docker integration runs on CI.

Authored by neo-claude-opus (Claude Opus)."
- 2026-06-05T14:41:24Z @neo-claude-opus referenced in commit `cc56bd0` - "test(memory-core): pin OIDC + team/private integration isolation to explicit private (#12527)

Two more integration isolation tests asserted cross-tenant boundaries under the (now team) default and broke deployment-wide, same class as CrossTenantIsolation. OidcAuth: alice/bob queries pin memorySharing='private' (OIDC-tenant isolation under the enforcing policy). TeamPrivateRetrieval: the 3 private-boundary raw-memory queries pin 'private'; shared-commons queries keep the default (team => shared visible); summary assertions already hold (summaries-team is additive).

Also removed pre-existing decay-prone ticket-refs from TeamPrivateRetrieval comments to satisfy the whole-file ticket-archaeology hook (#10010, #10011, #11057, #10330, #10789); behavioral descriptions preserved, refs preserved here in history. CrossTenantIsolation fix already CI-verified green; this completes the integration suite for the team default.

Authored by neo-claude-opus (Claude Opus)."
- 2026-06-05T16:09:46Z @tobiu closed this issue
- 2026-06-05T16:09:46Z @tobiu referenced in commit `352344b` - "feat(ai): default memory-sharing to team for deployment-wide raw reads (#12532)

* feat(ai): default memory-sharing to team for deployment-wide raw reads (#12527)

Raw memory becomes a team commons per operator direction: every maintainer reads every maintainer's raw memories (transparent swarm introspection).

- memorySharing.defaultPolicy: legacy -> team
- queryMemories + listMemories team branch: tenantScope=null (deployment-wide, no restrictive userId filter); the legacy-only post-filter does not run for team
- private/legacy unchanged (isolation stays opt-in); summaries already deployment-wide under team (#12470)
- Tenant-isolation spec: team tests assert cross-author visibility, legacy/private pinned via explicit policy; 16/16 green under team default

* fix(memory-core): listMemories memorySharing override for team-read symmetry (#12527)

listMemories read its policy only from the ambient AiConfig defaultPolicy, unlike queryMemories which already accepts a memorySharing override. Under the new team default this left listMemories untestable without mutating the shared AiConfig singleton (ADR-0019 safety-critical antipattern) and asymmetric with queryMemories.

Add the optional memorySharing param (mirrors queryMemories) so team / deployment-wide reads are explicit and unit-testable; existing callers are unchanged (param defaults to the ambient policy).

config.template.spec: assert defaultPolicy now resolves to team. MemoryService.TenantIsolation.spec: drive listMemories team reads via the explicit override instead of relying on (or mutating) the ambient singleton. Verified: target specs 42 passed; both touched specs green in isolation.

Authored by neo-claude-opus (Claude Opus).

* test(memory-core): cross-tenant integration covers team-default + private isolation (#12527)

The MC cross-tenant isolation integration test asserted isolation under the (now team) default and broke once raw reads went deployment-wide. Restructured to the split-coverage shape: pin the leak guard to explicit memorySharing='private' (the contract multi-tenant forks rely on), and add a positive team-default assertion (alice reads bob's same-session record, deployment-wide — the operator PRIO-1 behavior).

query_raw_memories already threads memorySharing (toolService binds it straight to queryMemories; openapi exposes it), so this is test-only. Verified: syntax-checked; logic mirrors the unit private/team coverage. Docker integration runs on CI.

Authored by neo-claude-opus (Claude Opus).

* test(memory-core): pin OIDC + team/private integration isolation to explicit private (#12527)

Two more integration isolation tests asserted cross-tenant boundaries under the (now team) default and broke deployment-wide, same class as CrossTenantIsolation. OidcAuth: alice/bob queries pin memorySharing='private' (OIDC-tenant isolation under the enforcing policy). TeamPrivateRetrieval: the 3 private-boundary raw-memory queries pin 'private'; shared-commons queries keep the default (team => shared visible); summary assertions already hold (summaries-team is additive).

Also removed pre-existing decay-prone ticket-refs from TeamPrivateRetrieval comments to satisfy the whole-file ticket-archaeology hook (#10010, #10011, #11057, #10330, #10789); behavioral descriptions preserved, refs preserved here in history. CrossTenantIsolation fix already CI-verified green; this completes the integration suite for the team default.

Authored by neo-claude-opus (Claude Opus).

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"

