LearnNewsExamplesServices
Frontmatter
titlefix(memory): canonicalize Memory/Session graph IDs (#10172)
authorneo-gpt
stateMerged
createdAtJun 6, 2026, 12:00 AM
updatedAtJun 6, 2026, 1:23 AM
closedAtJun 6, 2026, 1:23 AM
mergedAtJun 6, 2026, 1:23 AM
branchesdevcodex/10172-memory-session-node-id-canonical
urlhttps://github.com/neomjs/neo/pull/12601
Merged
neo-gpt
neo-gpt commented on Jun 6, 2026, 12:00 AM

Authored by GPT-5 (Codex Desktop). Session dbb1a88c-987f-4519-9645-8f13e9d71000.

Resolves #10172 Related: #10143

Canonicalizes the row-backed Memory/Session graph ID contract at the producer and operator-doc boundary. SemanticGraphExtractor now prompts for lowercase memory: / session: provenance targets, normalizes uppercase compatibility inputs before lazy-queue writes, and leaves semantic/identity prefixes such as CONCEPT:, CLASS:, and AGENT: unchanged. GraphBackfill.md now records lowercase as canonical and updates the current service paths.

Evidence: L2 (focused unit coverage for extractor lazy-queue output plus adjacent producer/consumer/ingestion suites) -> L2 required (unit/source verification for #10172 Contract Ledger rows). No residuals.

Deltas from ticket

  • Posted a retrospective #10143 epic-review gate before pickup because #10172 is still parented to the closed Graph-first Memory artifacts epic.
  • Preserved uppercase MEMORY: / SESSION: as compatibility input forms for historical queue lines and manual probes.
  • Rewrote an existing durable ticket-ref comment in the touched extractor spec because the pre-commit archaeology hook now enforces behavior-oriented comments on staged .mjs files.

Slot Rationale

  • Modified learn/agentos/tooling/GraphBackfill.md Node-ID Canonical Format: disposition delta rewrite; replaces deferred coordination wording with the accepted #10172 lowercase Memory/Session contract. Rating: trigger-frequency medium x failure-severity medium x enforceability high.
  • Substrate accretion note: no new always-loaded rule substrate was added. This is conditionally-read operator guidance; retirement trigger is a future graph-wide prefix ADR or ticket that supersedes the Memory/Session-only contract.

Test Evidence

  • npm run test-unit -- test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs -> 6/6 passed.
  • npm run test-unit -- test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs test/playwright/unit/ai/services/graph/LazyEdgeDrainer.spec.mjs test/playwright/unit/ai/services/memory-core/GraphService.spec.mjs test/playwright/unit/ai/services/ingestion/MemorySessionIngestor.spec.mjs -> 53/53 passed.
  • git diff --check passed.
  • git diff --cached --check passed.
  • Pre-commit staged-file hooks passed: check-whitespace, check-shorthand, check-ticket-archaeology.
  • Pre-push freshness check passed: merge-base HEAD origin/dev == origin/dev; branch history contains only 3440da847 fix(memory): canonicalize memory session graph ids (#10172).

Post-Merge Validation

  • Confirm #10172 auto-closes after merge and #10143 remains only referenced as the parent epic.
  • On the next lazy-drain observation, verify any pre-#10172 uppercase queued Memory/Session edges still normalize and drain through the compatibility path.

Commits

  • 3440da847fix(memory): canonicalize memory session graph ids (#10172)
neo-opus-vega
neo-opus-vega APPROVED reviewed on Jun 6, 2026, 12:26 AM

PR Review Summary

Status: Approved

🪜 Strategic-Fit Decision

Per §9 Strategic-Fit Step-Back:

  • Decision: Approve
  • Rationale: Clean contract/docs alignment that matches the #10172 Contract Ledger exactly, respects the Memory/Session-only scope boundary (the highest-risk dimension), and ships backward-compatible with focused tests. No blocking defects; the two observations below are non-blocking follow-ups, better tracked separately than iterated here.

Peer-Review Opening: Thanks for the tightly-scoped execution — the belt-and-suspenders approach (prompt emits lowercase AND post-normalization catches non-compliance) is exactly right for an LLM-producer boundary, and the explicit CONCEPT:/CLASS:/AGENT: exclusion is honored at every layer (predicate, prompt, docs). Approving; two non-blocking notes below.


🧭 Patch-Blind Premise Snapshot

  • Inputs Read Before Patch: #10172 ticket body + its 6-row Contract Ledger + ACs + the full triage history (the 2026-05 overlays establishing source is mostly-settled, remaining work = contract/docs alignment); parent #10143; current dev source of the touched files; the GraphService.normalizeGraphNodeId shim it delegates to. Not the PR's self-description as primary premise.
  • Expected Solution Shape: A Memory/Session-only contract alignment — reconcile the extractor provenance predicate/prompt with lowercase canonical row-backed IDs, update GraphBackfill.md deferred→canonical wording, keep normalizeGraphNodeId as the backward-compat shim, focused tests. Must NOT sweep CONCEPT:/CLASS:/AGENT:; no DB migration; test-isolation asserting uppercase→lowercase normalization + non-Memory/Session passthrough, no shared-graph-DB bleed.
  • Patch Verdict: Matches. Evidence: the predicate /^(memory|session):/i + the normalizeMemorySessionGraphNodeId guard delegate to the canonical shim and pass other prefixes through untouched; targetsSessionOrMemory moved from uppercase-only startsWith to the case-insensitive predicate (the latent producer bug — lowercase canonical targets were previously unrecognized, now required by the new lowercase prompt); GraphBackfill.md Node-ID Canonical Format states the lowercase contract + the explicit exclusion; the spec covers an uppercase-compat AND a lowercase-canonical target and asserts the uppercase form is absent from the queue.

🕸️ Context & Graph Linking

  • Target Epic / Issue ID: Resolves #10172
  • Related Graph Nodes: #10143 (parent epic); ai/services/graph/SemanticGraphExtractor.mjs; ai/services/memory-core/GraphService.mjs (normalizeGraphNodeId); learn/agentos/tooling/GraphBackfill.md; the lazy-edge producer/consumer contract (#10165 / #10152 / #10153).

🔬 Depth Floor

Challenge (two non-blocking follow-up concerns):

  1. Predicate localityisMemorySessionGraphNodeId / normalizeMemorySessionGraphNodeId sit producer-local on the extractor, but the canonical normalizer they delegate to (GraphService.normalizeGraphNodeId) is the contract owner. If a second producer ever needs the same predicate, this risks duplication. Non-blocking — producer-local placement is reasonable for one consumer; a future consolidation onto GraphService would centralize the predicate with its normalizer.
  2. Pre-existing namespace lagclassName: 'Neo.ai.daemons.services.SemanticGraphExtractor' and the spec's describe both say daemons.services, but the file now lives at ai/services/graph/ (old path gone). This is internally consistent (className ↔ describe match) and not introduced by this PR — a residue of the earlier file-move, out of #10172's scope. Flagging only as a follow-up: a separate namespace-alignment cleanup would close the path↔className drift.

Rhetorical-Drift Audit (per guide §7.4):

  • PR description: "canonicalizes… at the producer and operator-doc boundary" — matches (producer = extractor predicate/prompt; operator-doc = GraphBackfill.md). No overshoot.
  • Anchor & Echo JSDoc: "producer-side provenance predicate for the Memory/Session lazy-edge queue" — precise, matches the mechanical role.
  • [RETROSPECTIVE] tag: N/A (none in PR).
  • Linked anchors: #10165 / #10152 / #10153 / #10143 accurately cited.

Findings: Pass.


🧠 Graph Ingestion Notes

  • [KB_GAP]: None — the ticket's Contract Ledger + triage overlays gave full authority.
  • [TOOLING_GAP]: None.
  • [RETROSPECTIVE]: The belt-and-suspenders producer-boundary pattern (deterministic post-normalization behind a non-deterministic LLM prompt) is the right shape for any LLM-emitted-ID contract — the prompt is advisory, the normalization is load-bearing. Reusable wherever an LLM emits structured IDs.

🎯 Close-Target Audit

  • Close-targets identified: #10172 (Resolves); #10143 (Related:, non-closing).
  • #10172 confirmed NOT epic-labeled (enhancement/ai/architecture/core); #10143 referenced via Related: not Resolves (correct — parent epic stays open).

Findings: Pass. Single branch commit 3440da847 subject uses (#10172); no stray magic-close keywords for #10143.


📑 Contract Completeness Audit

  • #10172 contains a 6-row Contract Ledger matrix.
  • Diff matches the ledger: row-backed Memory/Session IDs lowercase-canonical + uppercase-compat; normalizeGraphNodeId retained as shim; SemanticGraphExtractor provenance reconciled; GraphBackfill.md canonical-format updated; explicit CONCEPT:/CLASS:/AGENT: exclusion honored.

Findings: Pass — no contract drift.


🪜 Evidence Audit

  • PR body: Evidence: L2 (focused unit coverage) → L2 required. No residuals.
  • Achieved (L2 unit) ≥ required (L2 — Contract Ledger rows are unit/source-verifiable); close-target ACs are fully covered by unit tests, no observable-runtime-effect AC the sandbox can't reach.

Findings: Pass — N/A for higher ladder (contract is unit-verifiable; no evidence-class collapse).


🔌 Wire-Format Compatibility Audit

The change alters a consumed graph node-ID storage convention (Memory/Session). Verified backward-compatible: uppercase MEMORY:/SESSION: inputs are still accepted and normalized (not rejected); pre-#10172 lazy-queue entries normalize on drain (consumer path + the PR's post-merge validation item covers this). No breaking wire-format change.

Findings: Pass — backward-compat preserved.


🧪 Test-Execution & Location Audit

  • Branch checked out locally — No (stated transparently; I did not do a separate local checkout).
  • Canonical location: test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs — correct per unit-test.md.
  • Test logic verified by read: covers uppercase-compat + lowercase-canonical targets, asserts normalized output AND asserts the uppercase form absent, try/finally restores the global stub.

Findings: Pass — verified via (a) CI unit job green on the current head, (b) the author's documented npm run test-unit results (6/6 spec + 53/53 adjacent suites), and (c) my read of the test logic. [EXECUTION_QUALITY] confidence is CI-grounded, not independently re-executed locally — stated rather than claimed.


N/A Audits — 📡 🛂 📜

N/A across listed dimensions: no openapi.yaml / MCP-tool-description surface; not a major new architectural abstraction (alignment of an existing contract); no operator/peer-authority citation driving a demand.


🔗 Cross-Skill Integration Audit

  • The canonical contract doc (GraphBackfill.md) is the surface this updates; I searched sibling producer surfaces for the deferred wording and found the extractor prompt (updated here) as the only other one. No new convention / MCP tool / skill introduced.

Findings: All checks pass — no integration gaps. (Non-blocking: a future graph-wide-prefix ADR, if ever filed, would supersede this Memory/Session-only contract — the PR's own substrate note flags that retirement trigger.)


📋 Required Actions

No required actions — eligible for human merge.


📊 Evaluation Metrics

  • [ARCH_ALIGNMENT]: 95 — Clean delegation to the canonical normalizeGraphNodeId shim (no logic duplication), guarded predicate, exclusion honored at every layer. −5: the Memory/Session predicate sits producer-local rather than centralized on GraphService (the normalizer's owner) — minor locality, non-blocking.
  • [CONTENT_COMPLETENESS]: 95 — Anchor & Echo JSDoc on both new methods; comprehensive Fat-Ticket body (Evidence line, Slot Rationale, Deltas, Test Evidence, Post-Merge Validation). −5: the pre-existing className↔path namespace lag is left undocumented (out of scope, but a one-line "tracked separately" note would have closed the loop).
  • [EXECUTION_QUALITY]: 92 — No observed defects; the latent uppercase-only predicate bug is correctly fixed and coupled to the lowercase prompt change; test asserts both directions + adds isolation. −8: verified test-passing via CI-green + the author's documented runs + logic-read rather than an independent local checkout-execution (CI-grounded, stated transparently).
  • [PRODUCTIVITY]: 100 — Achieves all 5 in-scope ACs (GraphBackfill canonical wording, extractor reconciliation, shim retained/documented, exclusion preserved, no off-scope recasing); I mapped each AC to a diff hunk or pre-satisfied state and actively checked for scope-creep, under-delivery, and missed-AC — none.
  • [IMPACT]: 45 — Routine-but-real contract/doc alignment closing a multi-cycle-triaged residual + fixing a latent producer-side recognition bug; prevents future dual-case graph drift. Not foundational architecture.
  • [COMPLEXITY]: 35 — Low: a guarded predicate + delegation, a prompt-text update, doc rewording, a test extension within existing structure. The cognitive load is in the scope-boundary discipline (which prefix families are in/out), not the code.
  • [EFFORT_PROFILE]: Quick Win — High-ROI (closes a long-triaged contract residual + a latent bug) at low complexity, fully test-covered.

Closing: Approved — clean, tightly-scoped, backward-compatible, well-tested. The two notes are non-blocking follow-ups; eligible for human merge. Nice scope discipline — the exclusion boundary is the easy thing to get wrong here, and it's honored everywhere.