---
id: 11680
title: getNeighbors omits semanticVectorId; pre_brief_session hydration inert
state: CLOSED
labels:
  - bug
  - ai
assignees:
  - neo-gpt
createdAt: '2026-05-20T12:40:27Z'
updatedAt: '2026-05-20T13:54:41Z'
githubUrl: 'https://github.com/neomjs/neo/issues/11680'
author: neo-opus-ada
commentsCount: 0
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-05-20T13:54:41Z'
---
# getNeighbors omits semanticVectorId; pre_brief_session hydration inert

## Context

Surfaced 2026-05-20 during the OQ3 convergence on [Discussion #11676](https://github.com/neomjs/neo/discussions/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:

```js
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`:

```js
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](https://github.com/neomjs/neo/discussions/11676) — Memory Core contention proposal; OQ3 convergence surfaced this defect ([comment DC_kwDODSospM4BAz7y](https://github.com/neomjs/neo/discussions/11676#discussioncomment-16989938)).
- `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`).

## Timeline

- 2026-05-20T12:40:28Z @neo-opus-ada added the `bug` label
- 2026-05-20T12:40:28Z @neo-opus-ada added the `ai` label
- 2026-05-20T12:45:06Z @neo-gpt assigned to @neo-gpt
- 2026-05-20T12:49:04Z @neo-gpt cross-referenced by PR #11681
- 2026-05-20T13:15:11Z @neo-gpt referenced in commit `a0f96df` - "fix(memory-core): declare get_neighbors vector output (#11680)"
- 2026-05-20T13:25:40Z @neo-gpt cross-referenced by #11682
- 2026-05-20T13:54:41Z @tobiu referenced in commit `d2ab62c` - "fix(memory-core): expose neighbor semantic vector ids (#11680) (#11681)

* fix(memory-core): expose neighbor semantic vector ids (#11680)

* fix(memory-core): declare get_neighbors vector output (#11680)

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-20T13:54:41Z @tobiu closed this issue
- 2026-05-20T14:33:16Z @neo-opus-ada cross-referenced by #11626
- 2026-05-20T15:31:19Z @neo-gpt cross-referenced by #11687
- 2026-05-20T17:24:26Z @neo-gpt cross-referenced by PR #11689

