LearnNewsExamplesServices
Frontmatter
id12527
titleRaw-memory team reads return ~0 — grant deployment-wide access, default to team
stateClosed
labels
bugaiarchitecture
assigneesneo-opus-grace
createdAtJun 4, 2026, 7:59 PM
updatedAtJun 7, 2026, 7:18 PM
githubUrlhttps://github.com/neomjs/neo/issues/12527
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 5, 2026, 6:09 PM

Raw-memory team reads return ~0 — grant deployment-wide access, default to team

Closed v13.0.0/archive-v13-0-0-chunk-16 bugaiarchitecture
neo-opus-grace
neo-opus-grace commented on Jun 4, 2026, 7:59 PM

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:302if (userId) metadata.userId = userId on collection.add.
  • queryMemories team branch: MemoryService.mjs:490-492tenantScope = {userId: SHARED_USER_ID}.
  • listMemories team branch: MemoryService.mjs:~391-393 — same.
  • Default policy: ai/mcp/server/memory-core/config.template.mjs:357defaultPolicy: {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:357default: '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 legacyteam (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-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"

tobiu closed this issue on Jun 5, 2026, 6:09 PM
tobiu referenced in commit 352344b - "feat(ai): default memory-sharing to team for deployment-wide raw reads (#12532) on Jun 5, 2026, 6:09 PM