LearnNewsExamplesServices
Frontmatter
id11680
titlegetNeighbors omits semanticVectorId; pre_brief_session hydration inert
stateClosed
labels
bugai
assigneesneo-gpt
createdAtMay 20, 2026, 2:40 PM
updatedAtMay 20, 2026, 3:54 PM
githubUrlhttps://github.com/neomjs/neo/issues/11680
authorneo-opus-ada
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 20, 2026, 3:54 PM

getNeighbors omits semanticVectorId; pre_brief_session hydration inert

neo-opus-ada
neo-opus-ada commented on May 20, 2026, 2:40 PM

Context

Surfaced 2026-05-20 during the OQ3 convergence on Discussion #11676 (Memory Core contention / pending-memory proposal). While verifying whether a deferred-embedding "pending memory" would degrade pre_brief_session, a V-B-A read of the Memory Core read path found that pre_brief_session's episodic-context hydration is already inert today — for every memory, not just hypothetical pending ones — because the graph-read it depends on does not carry the pointer it hydrates from.

This is a pre-existing defect orthogonal to #11676; it is filed separately so #11676's scope is not broadened (per @neo-gpt's fold-in guard on that Discussion).

The Problem

MemoryService.preBriefSession is documented to load "high-weight semantic relationships" and return a structured brief with episodicContext per neighbor. For each neighbor it does:

if (neighbor.semanticVectorId) {
    const result = await collection.get({ids: [neighbor.semanticVectorId], include: ['documents']});
    if (result.documents?.length > 0) episodicContext = result.documents[0];
}

The neighbors come from GraphService.getNeighbors({id}). But getNeighbors builds each neighbor object as {id, type, name, description, relationship, weight, source, target} — with no semanticVectorId field. So neighbor.semanticVectorId is always undefined, the if is never entered, and episodicContext is always null. pre_brief_session silently returns a topology-only brief — the episodic-hydration code is dead.

The defect is silent: no error, no log — the brief just never contains episodic content. An agent calling pre_brief_session to "instantly contextualize" against an Epic gets the structural neighbor list but none of the compressed episodic memory the tool's JSDoc promises.

The Architectural Reality

  • GraphService.getNeighborsai/services/memory-core/GraphService.mjs:626. The neighbor-object construction (:649) omits semanticVectorId.
  • GraphService.getContextFrontier — same file, :712. Its strategicNeighbors construction (:754) does include semanticVectorId: node.properties?.semanticVectorId. The sibling graph-read already does it right — MemoryService.getContextFrontier hydrates correctly; only getNeighbors' consumer (preBriefSession) is broken.
  • MemoryService.preBriefSessionai/services/memory-core/MemoryService.mjs:565; the inert guard is at :600.
  • getNeighbors also backs the get_neighbors MCP tool, so the missing field is an agent-facing output gap as well.
  • The memory node carries semanticVectorId in node.properties (MemoryService.addMemory:258 sets it on upsertNode).

Whether getNeighbors ever carried semanticVectorId (a regression) or never did (a latent never-worked defect) is unknown without git blame; the fix is identical either way.

The Fix

In GraphService.getNeighbors (ai/services/memory-core/GraphService.mjs:649), add semanticVectorId to the pushed neighbor object, reading from node.properties?.semanticVectorId — mirroring getContextFrontier:754:

results.push({
    id              : node.id,
    type            : node.label,
    name            : node.properties?.name,
    description     : node.properties?.description,
    semanticVectorId: node.properties?.semanticVectorId,
    relationship    : e.type,
    weight          : e.properties?.weight || 1.0,
    source          : e.source,
    target          : e.target
});

Purely additive — existing consumers that do not read semanticVectorId are unaffected; preBriefSession's existing guard begins to fire.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
GraphService.getNeighbors returned neighbor objects (and the get_neighbors MCP tool output) This ticket; getContextFrontier:754 as the sibling precedent Each neighbor object gains semanticVectorId (from node.properties?.semanticVectorId). Additive — no existing field changes. When the node has no semanticVectorId the field is undefined — identical to today's behavior for every neighbor; consumers guarding with if (neighbor.semanticVectorId) skip as before. getNeighbors JSDoc updated to document the field on the neighbor shape. Unit test: a getNeighbors neighbor whose node has semanticVectorId surfaces it; integration test: preBriefSession hydrates episodicContext for a vectored neighbor.

Acceptance Criteria

  • GraphService.getNeighbors includes semanticVectorId (from node.properties?.semanticVectorId) in each returned neighbor object.
  • getNeighbors JSDoc documents the semanticVectorId field on the neighbor shape.
  • A unit test asserts a neighbor whose node carries semanticVectorId surfaces it through getNeighbors.
  • An integration test asserts MemoryService.preBriefSession hydrates episodicContext for a neighbor with a vectored node (the previously-dead :600 guard now fires).
  • RLS preserved — semanticVectorId is exposed only for neighbors already passing the existing isRlsVisible filter; no new leak surface.

Out of Scope

  • Discussion #11676's pending-memory proposal — this ticket fixes a pre-existing defect that #11676's OQ3 surfaced; it is not part of #11676's graduation.
  • Any change to getContextFrontier — it already carries semanticVectorId correctly.
  • Re-shaping pre_brief_session's contract beyond making the existing hydration functional.

Avoided Traps

  • Declaring pre_brief_session topology-only and deleting the hydration code — rejected. The collection.get hydration block + the JSDoc ("loads its high-weight semantic relationships") show the episodic brief is intended behavior; the fix restores intent rather than amputating an agent-facing feature.

Related

  • Discussion #11676 — Memory Core contention proposal; OQ3 convergence surfaced this defect (comment DC_kwDODSospM4BAz7y).
  • getContextFrontier (same file) — the correct sibling precedent.

Origin Session ID

c4505aed-b48d-4aed-ba11-a1db410744df

Handoff Retrieval Hints

  • query_raw_memories: "getNeighbors omits semanticVectorId pre_brief_session episodic hydration inert"
  • Code anchors: GraphService.mjs getNeighbors (:626/:649) vs getContextFrontier (:712/:754); MemoryService.mjs preBriefSession (:565/:600).
tobiu referenced in commit d2ab62c - "fix(memory-core): expose neighbor semantic vector ids (#11680) (#11681) on May 20, 2026, 3:54 PM
tobiu closed this issue on May 20, 2026, 3:54 PM