---
id: 10172
title: 'Graph node-ID case canonicalization: settle memory:/MEMORY: and session:/SESSION: convention'
state: CLOSED
labels:
  - enhancement
  - ai
  - architecture
  - core
assignees:
  - neo-gpt
createdAt: '2026-04-21T23:19:23Z'
updatedAt: '2026-06-05T23:23:29Z'
githubUrl: 'https://github.com/neomjs/neo/issues/10172'
author: tobiu
commentsCount: 4
parentIssue: 10143
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-06-05T23:23:29Z'
---
# Graph node-ID case canonicalization: settle memory:/MEMORY: and session:/SESSION: convention

### Context

The Native Edge Graph previously carried two case conventions for row-backed Memory and Session node IDs:

- Ingestor/storage convention: lowercase `memory:<chromaId>` and `session:<sessionId>`.
- Extractor/provenance prompt convention: uppercase `MEMORY:<chromaId>` and `SESSION:<sessionId>`.

Current source and tests have already converged most runtime behavior toward lowercase storage. The remaining work is contract/documentation alignment, not a graph-wide prefix migration.

### Current State (2026-06-03 V-B-A)

- `MemorySessionIngestor` writes row-backed Memory/Session graph IDs as lowercase `memory:<id>` and `session:<id>`.
- `GraphService.normalizeGraphNodeId()` lowercases only Memory/Session prefixes and leaves other prefixes such as `CONCEPT:` / `CLASS:` / `AGENT:` outside this ticket.
- Current tests assert uppercase Memory/Session inputs normalize to lowercase storage/linking rather than persisting uppercase duplicates.
- `learn/agentos/tooling/GraphBackfill.md` still needs the canonical-format section updated from deferred/ambiguous wording.
- `SemanticGraphExtractor` provenance/lazy-queue examples and predicates still need reconciliation with the row-backed lowercase Memory/Session contract.

### Decision

Use Option A for this ticket: canonical row-backed Memory/Session storage IDs are lowercase `memory:<id>` and `session:<id>`. Uppercase `MEMORY:<id>` / `SESSION:<id>` remain compatibility input forms only.

This ticket explicitly does not migrate semantic or identity prefixes such as `CONCEPT:`, `CLASS:`, `AGENT:`, or other non-row-backed node families.

### Contract Ledger Matrix

| Target Surface | Source of Authority | Proposed Behavior | Fallback / Edge Case | Docs | Evidence |
|---|---|---|---|---|---|
| Row-backed Memory node IDs | Current `MemorySessionIngestor` + #10172 decision | Canonical storage ID is `memory:<chromaId>`. | Uppercase `MEMORY:<id>` accepted as compatibility input and normalized before storage/linking. | `GraphBackfill.md` canonical format section | Unit/source verification that storage remains lowercase |
| Row-backed Session node IDs | Current `MemorySessionIngestor` + #10172 decision | Canonical storage ID is `session:<sessionId>`. | Uppercase `SESSION:<id>` accepted as compatibility input and normalized before storage/linking. | `GraphBackfill.md` canonical format section | Unit/source verification that storage remains lowercase |
| `GraphService.normalizeGraphNodeId()` | Existing compatibility shim | Retain as backward-compatible input normalization for Memory/Session only. | Unknown or non-row-backed prefixes pass through unchanged unless a separate graph-wide prefix policy ticket exists. | JSDoc if wording is stale | Focused tests for Memory/Session normalization and non-Memory/Session passthrough |
| `SemanticGraphExtractor` provenance / lazy-queue producer | Current extractor prompt/predicate + #10172 decision | Reconcile Memory/Session examples and provenance target detection with lowercase canonical row-backed IDs while preserving compatibility with uppercase queued inputs. | Existing uppercase queued entries still drain through normalization. | JSDoc/prompt text if changed | Fixture/unit coverage for queued provenance targets |
| `GraphBackfill.md` operator guidance | Operator/manual-query surface | Document lowercase Memory/Session canonical IDs and uppercase compatibility inputs so manual SQLite/graph queries do not miss rows. | Mention semantic uppercase prefixes are separate and out of scope. | `learn/agentos/tooling/GraphBackfill.md` | Docs grep + review evidence |
| Explicit exclusions | #10172 scope boundary | Do not migrate `CONCEPT:`, `CLASS:`, `AGENT:`, or other non-row-backed prefixes. | If future evidence needs graph-wide prefix settlement, file a separate ticket. | Ticket body / docs note | No source changes touching those prefixes under this ticket |

### Acceptance Criteria

- [x] Canonical decision recorded: lowercase `memory:` / `session:` for row-backed Memory/Session storage.
- [ ] `GraphBackfill.md` documents canonical lowercase Memory/Session IDs and uppercase compatibility input forms.
- [ ] `SemanticGraphExtractor` provenance/lazy-queue Memory/Session examples or predicates are reconciled with the canonical contract, if current source still emits misleading uppercase row-backed targets.
- [ ] `GraphService.normalizeGraphNodeId()` remains as a backward-compatible shim and is documented/tested as Memory/Session-only.
- [ ] No migration or recasing is applied to `CONCEPT:`, `CLASS:`, `AGENT:`, or other non-row-backed prefixes.

### Out of Scope

- Graph-wide prefix policy.
- Agent identity prefix convention.
- DB migration from lowercase to uppercase.
- Recasting semantic node IDs such as `CONCEPT:` / `CLASS:`.

### Related

- Predecessor shipped work: #10151, #10152, #10153, PR #10171.
- Parent: #10143.
- Grand-parent: #9999.
- Review origin: Gemini 3.1 Pro PR review on #10171.

Origin Session ID: `d69ac7a0-9fe8-4416-b766-cd9edb8bee71`.

## Timeline

- 2026-04-21T23:19:25Z @tobiu added the `enhancement` label
- 2026-04-21T23:19:25Z @tobiu added the `ai` label
- 2026-04-21T23:19:25Z @tobiu added the `architecture` label
- 2026-04-21T23:19:25Z @tobiu added the `core` label
- 2026-04-21T23:19:33Z @tobiu added parent issue #10143
- 2026-04-21T23:20:19Z @tobiu cross-referenced by PR #10171
- 2026-04-22T09:09:44Z @tobiu cross-referenced by #9999
- 2026-04-22T14:33:46Z @neo-opus-ada cross-referenced by PR #10175
### @neo-opus-ada - 2026-04-22T15:40:05Z

**Input from Claude Opus 4.7 (Claude Code):**

> ❋ ## Cross-reference: `#10175` auto-extraction writes uppercase `CONCEPT:` / `CLASS:` into the write path
> 
> Flagging for the case-canonicalization scope settlement here, not a scope addition.
> 
> `#10175` (merged 2026-04-22) wired `SemanticGraphExtractor.extractMessageConcepts` into `MailboxService.addMessage`'s fire-and-forget `.then(...)` block. The extractor prompts the LLM to return concepts in the `CONCEPT:name` / `CLASS:Name` uppercase-prefix form; each concept is auto-seeded via `GraphService.upsertNode` if absent, then linked via `TAGGED_CONCEPT` edge (weight 0.8). See `ai/daemons/services/SemanticGraphExtractor.mjs` lines 264+ and `ai/mcp/server/memory-core/services/MailboxService.mjs` auto-emit block in `addMessage`.
> 
> **The write-path implication for this ticket:** whichever case settlement this ticket lands on (option A lowercase canonical vs option B uppercase canonical + migration), the auto-extraction prompt + post-processing must be updated in lockstep. If A (lowercase) wins, the extractor's system-instruction JSON schema example and the `c.includes(':')` validator both need adjustment, and any previously-written `CONCEPT:`/`CLASS:` nodes need migration to the canonical case at the same checkpoint as the `memory:` / `session:` lowercase migration (or face dual-case drift).
> 
> Opus-lean at review time was option A, but this is deliberately left for the `#10172` owner to settle — flagging so the write-path auto-emitter doesn't get missed when the ticket reshapes.
> 
> No action requested here; just keeping the cross-substrate coordination visible for whoever picks up `#10172`.
> 

- 2026-05-17T08:08:24Z @neo-opus-ada cross-referenced by PR #11528
### @neo-gpt - 2026-05-20T03:50:12Z

**Input from GPT-5 (Codex Desktop):**

> ● ## Read-only triage overlay — canonical scope is narrower than the ticket currently implies
> 
> V-B-A pass from Project 12 nightshift triage: #10172 is still a valid open Todo item, but the live source reality has already partially settled the decision.
> 
> Evidence checked:
> - `MemorySessionIngestor.syncSessionToGraph` writes row-backed graph IDs as lowercase `session:<sessionId>` and `memory:<chromaId>` (`ai/daemons/services/MemorySessionIngestor.mjs` lines 160-203).
> - `GraphService.linkNodesAsync` / `normalizeGraphNodeId` now describe lowercase `memory:` / `session:` as the canonical destination for uppercase `MEMORY:` / `SESSION:` inputs (`ai/services/memory-core/GraphService.mjs` lines 365-368 and 440-464).
> - `learn/agentos/tooling/GraphBackfill.md` still says the canonical decision is deferred (lines 96-100), which now lags behind the service contract.
> - `SemanticGraphExtractor` still uses the generic `Type:Name` / uppercase semantic-node convention and its provenance lazy-queue predicate only recognizes uppercase `SESSION:` / `MEMORY:` (`ai/daemons/services/SemanticGraphExtractor.mjs` lines 62, 207-210, 250-252).
> 
> Conclusion: this should not become a broad "all graph prefixes lowercase" migration. The safer narrowed interpretation is:
> 
> 1. `memory:` / `session:` are canonical for row-backed Memory/Session storage IDs.
> 2. `MEMORY:` / `SESSION:` remain compatibility input forms and lazy-queue producer forms until the extractor path is updated.
> 3. Uppercase semantic node-type IDs such as `CONCEPT:` / `CLASS:` remain a separate convention and should not be swept into this ticket unless a follow-up explicitly chooses a graph-wide prefix policy.
> 
> Recommended future implementation shape after the lane-claim gate clears: choose Option A for Memory/Session only, update the extractor provenance prompt/predicate and `GraphBackfill.md` canonical-format section, keep `GraphService.normalizeGraphNodeId` as backward-compatible input normalization, and avoid DB migration unless live SQLite evidence later shows uppercase Memory/Session rows persisted in storage.
> 
> No tracked files changed in this pass; this is a read-only narrowing overlay.

### @neo-gpt - 2026-05-25T05:14:20Z

## Ticket Intake Refresh - needs Contract Ledger before branch work

Live V-B-A on 2026-05-25:
- #10172 remains open and unassigned in Project 12 Todo.
- KB plus current source/tests already settle row-backed Memory/Session storage toward lowercase canonical `memory:` / `session:`.
- `GraphService.normalizeGraphNodeId()` and `linkNodesAsync()` normalize uppercase inputs before lazy back-fill/linking; current unit coverage asserts uppercase `MEMORY:` stores as lowercase.
- `MemorySessionIngestor` unit coverage also asserts uppercase `MEMORY:` normalizes to lowercase storage.
- `learn/agentos/tooling/GraphBackfill.md` still says the canonical decision is deferred.
- `SemanticGraphExtractor` still only treats uppercase `MEMORY:` / `SESSION:` as provenance lazy-queue targets.
- Scope should stay memory/session only. `CONCEPT:`, `CLASS:`, `AGENT:`, and other semantic/identity prefixes remain out of scope for this ticket.

### Disposition
Do not branch yet. This ticket changes a consumed Agent OS graph-ID contract, so ticket-intake requires a Contract Ledger before implementation. I am applying `agent-task:blocked` until the issue body carries the contract matrix.

### Required Contract Ledger rows
- Row-backed Memory node IDs: canonical lowercase `memory:<id>` storage; uppercase `MEMORY:<id>` accepted only as compatibility input.
- Row-backed Session node IDs: canonical lowercase `session:<id>` storage; uppercase `SESSION:<id>` accepted only as compatibility input.
- `GraphService.normalizeGraphNodeId()`: backward-compatible shim retained for input normalization and lazy back-fill/link paths.
- `SemanticGraphExtractor`: provenance predicate/producer behavior reconciled with the canonical memory/session contract.
- `learn/agentos/tooling/GraphBackfill.md`: deferred wording replaced with the chosen canonical format and compatibility note.
- Explicit exclusion: `CONCEPT:`, `CLASS:`, `AGENT:`, and other non-row-backed prefixes are not migrated or recased by this ticket.

lane-state: next-lane (routing #10172 as blocked; continuing Project 12/v13 survey on subsequent watchdog cycles).

- 2026-05-25T05:14:26Z @neo-gpt added the `agent-task:blocked` label
- 2026-05-26T07:16:40Z @neo-gpt removed the `agent-task:blocked` label
- 2026-05-26T07:16:45Z @neo-gpt added the `needs-re-triage` label
### @neo-gpt - 2026-05-26T07:16:58Z

## Board routing update - canonical form settled, implementation still contract-blocked

Live watchdog intake rechecked #10172 because it is the current top unassigned Project 12 candidate.

Verdict: keep open as a valid Memory/Session graph-ID contract residual, but remove it from current Project 12 Todo. The canonical direction is already mostly settled; the remaining implementation is contract/documentation alignment, not deployment-window release work.

Evidence:
- Current source and tests settle row-backed Memory/Session graph IDs to lowercase `memory:<id>` / `session:<id>`.
- `MemorySessionIngestor` writes lowercase IDs and accepts uppercase `MEMORY:` / `SESSION:` as compatibility input.
- `GraphService.normalizeGraphNodeId()` lowercases only Memory/Session prefixes and leaves other prefixes such as `CONCEPT:` unchanged.
- Unit tests assert uppercase Memory/Session inputs store/link to lowercase nodes and do not persist uppercase duplicates.
- KB agrees that lowercase Memory/Session is the canonical convention and uppercase is compatibility input.
- The remaining ticket gap is the previously named Contract Ledger: reconcile `SemanticGraphExtractor` provenance producer/predicate, update `learn/agentos/tooling/GraphBackfill.md`, retain the normalization shim as compatibility, and explicitly exclude `CONCEPT:`, `CLASS:`, `AGENT:`, and other non-row-backed prefixes.

Action taken:
- Removed the legacy `agent-task:blocked` label.
- Added `needs-re-triage`.
- Removed #10172 from Project 12 Todo so the current-release board does not present contract-blocked graph-ID cleanup as claimable deployment work.

Re-entry condition: add the Contract Ledger rows named in the 2026-05-25 intake comment, then decide whether this is a small post-launch contract-alignment PR or should be folded into a broader GraphBackfill/docs cleanup lane.

- 2026-06-03T07:52:17Z @neo-gpt removed the `needs-re-triage` label
- 2026-06-05T21:52:51Z @neo-gpt cross-referenced by #10143
- 2026-06-05T21:53:41Z @neo-gpt assigned to @neo-gpt
- 2026-06-05T22:00:21Z @neo-gpt cross-referenced by PR #12601
- 2026-06-05T23:23:30Z @tobiu referenced in commit `07655f1` - "fix(memory): canonicalize memory session graph ids (#10172) (#12601)"
- 2026-06-05T23:23:30Z @tobiu closed this issue

