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.getNeighbors — ai/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.preBriefSession — ai/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
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).
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 thatpre_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.preBriefSessionis documented to load "high-weight semantic relationships" and return a structured brief withepisodicContextper 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}). ButgetNeighborsbuilds each neighbor object as{id, type, name, description, relationship, weight, source, target}— with nosemanticVectorIdfield. Soneighbor.semanticVectorIdis alwaysundefined, theifis never entered, andepisodicContextis alwaysnull.pre_brief_sessionsilently 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_sessionto "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.getNeighbors—ai/services/memory-core/GraphService.mjs:626. The neighbor-object construction (:649) omitssemanticVectorId.GraphService.getContextFrontier— same file,:712. ItsstrategicNeighborsconstruction (:754) does includesemanticVectorId: node.properties?.semanticVectorId. The sibling graph-read already does it right —MemoryService.getContextFrontierhydrates correctly; onlygetNeighbors' consumer (preBriefSession) is broken.MemoryService.preBriefSession—ai/services/memory-core/MemoryService.mjs:565; the inert guard is at:600.getNeighborsalso backs theget_neighborsMCP tool, so the missing field is an agent-facing output gap as well.semanticVectorIdinnode.properties(MemoryService.addMemory:258sets it onupsertNode).Whether
getNeighborsever carriedsemanticVectorId(a regression) or never did (a latent never-worked defect) is unknown withoutgit blame; the fix is identical either way.The Fix
In
GraphService.getNeighbors(ai/services/memory-core/GraphService.mjs:649), addsemanticVectorIdto the pushed neighbor object, reading fromnode.properties?.semanticVectorId— mirroringgetContextFrontier: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
semanticVectorIdare unaffected;preBriefSession's existing guard begins to fire.Contract Ledger Matrix
GraphService.getNeighborsreturned neighbor objects (and theget_neighborsMCP tool output)getContextFrontier:754as the sibling precedentsemanticVectorId(fromnode.properties?.semanticVectorId). Additive — no existing field changes.semanticVectorIdthe field isundefined— identical to today's behavior for every neighbor; consumers guarding withif (neighbor.semanticVectorId)skip as before.getNeighborsJSDoc updated to document the field on the neighbor shape.getNeighborsneighbor whose node hassemanticVectorIdsurfaces it; integration test:preBriefSessionhydratesepisodicContextfor a vectored neighbor.Acceptance Criteria
GraphService.getNeighborsincludessemanticVectorId(fromnode.properties?.semanticVectorId) in each returned neighbor object.getNeighborsJSDoc documents thesemanticVectorIdfield on the neighbor shape.semanticVectorIdsurfaces it throughgetNeighbors.MemoryService.preBriefSessionhydratesepisodicContextfor a neighbor with a vectored node (the previously-dead:600guard now fires).semanticVectorIdis exposed only for neighbors already passing the existingisRlsVisiblefilter; no new leak surface.Out of Scope
getContextFrontier— it already carriessemanticVectorIdcorrectly.pre_brief_session's contract beyond making the existing hydration functional.Avoided Traps
pre_brief_sessiontopology-only and deleting the hydration code — rejected. Thecollection.gethydration 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
getContextFrontier(same file) — the correct sibling precedent.Origin Session ID
c4505aed-b48d-4aed-ba11-a1db410744dfHandoff Retrieval Hints
query_raw_memories: "getNeighbors omits semanticVectorId pre_brief_session episodic hydration inert"GraphService.mjsgetNeighbors(:626/:649) vsgetContextFrontier(:712/:754);MemoryService.mjspreBriefSession(:565/:600).