Context
Filed 2026-05-09 after @tobiu caught a substrate-discipline failure on PR #11001 Cycle review (MC migration sub-4 of M6 epic #10986): all 4 M6 sub-issues (#10991 KB, #10993 GH-WF, #10994 NL, #10996 MC) physically moved service classes from ai/mcp/server/<server>/services/ to the flat ai/services/<server>/ SDK boundary BUT did not update the corresponding Neo.setupClass() className strings.
Result: the four M6-migrated server families now have systemic drift between file location and Neo class registry namespace:
- File at
ai/services/memory-core/GraphService.mjs
- className still
Neo.ai.mcp.server.memory-core.services.GraphService
Operator's framing: "if you did not update neo classNames, inside the migration, this is an utter failure and requires new tickets."
The cited rationale on M6 sub-issues — "Renaming MC services or class names — physical move only" — was misread. "Physical move only" was meant to constrain the migration to physical relocation including namespace alignment, not to forbid namespace updates. Per Neo conventions (@class Neo.<path> mirrors file path; KB indexing, Native Edge Graph hierarchy traversal, and class-introspection tooling all rely on this mirror), a physical move without namespace update violates the convention.
The Problem
54-ish className strings across 4 server families now lie about their location. Concrete fallout:
- Knowledge Base indexing —
mcp__neo-mjs-knowledge-base__get_class_hierarchy and query_documents rely on className-to-file-path resolution. Stale className causes wrong-file lookups or empty results.
- Native Edge Graph hierarchy — class-tree traversal in NEG uses className as the canonical identifier; child-class-of-X queries on the old namespace will return migrated classes that no longer live there.
- Agent introspection tooling —
inspect_class and get_method_source derive expected file paths from className; agents will fail to resolve OR resolve to the wrong path expectation.
- Documentation drift — every JSDoc
@class Neo.ai.mcp.server.<server>.services.X and @member className=... block in the 54 affected classes is now structurally wrong. KB doc pipeline ingests these as authoritative.
- Future-reader confusion — humans + agents navigating from
ai/services/memory-core/GraphService.mjs see className Neo.ai.mcp.server.memory-core.services.GraphService, then can't reverse-derive the new location.
The runtime DOES NOT crash because Neo's class registry uses className as a registry key (string-equality lookup), not as a path-derivation rule. So CI passed for KB + GH-WF + NL merges and currently passes for MC PR #11001. The drift is silent at runtime, loud in tooling and documentation surfaces.
The Architectural Reality
Neo's Neo.setupClass(Class) uses Class.config.className as the registry key under globalThis.Neo. Two consumers depend on className-mirrors-path:
Neo.create('Neo.ai.X.Y.Z') resolves the class via the registry; works regardless of file location IF the registry was populated correctly. Migration-time module loading still triggers Neo.setupClass, so the class IS in the registry. But the registry key now disagrees with the file system.
learn/agentos/CodebaseOverview.md + ChromaDB-indexed source content map className → file path for documentation/AI tooling. KB embeddings include @class JSDoc tags as semantic anchors. Stale anchors mis-direct semantic search.
Neo conventions per learn/guides/fundamentals/CodebaseOverview.md and core/Base.mjs JSDoc precedent: className mirrors file path. The physical migration should preserve that mirror. Sub-issues #10991/#10993/#10994/#10996 all violated it.
Affected files (per grep -h "className.*Neo\.ai" ai/services/<server>/*.mjs):
| Server |
className count |
OLD prefix |
NEW prefix (target) |
| Knowledge Base |
~10 |
Neo.ai.mcp.server.knowledge-base.services.X |
Neo.ai.services.knowledge-base.X |
| GitHub Workflow |
~13 |
Neo.ai.mcp.server.github-workflow.services.X (+ .sync.X, .queries.X) |
Neo.ai.services.github-workflow.X (+ .sync.X, .queries.X) |
| Neural Link |
~9 |
Neo.ai.mcp.server.neural-link.services.X |
Neo.ai.services.neural-link.X |
| Memory Core |
~22 |
Neo.ai.mcp.server.memory-core.services.X (+ .lifecycle.X, .managers.X) |
Neo.ai.services.memory-core.X (+ .lifecycle.X, .managers.X) |
Total: ~54 className strings + corresponding @member className=... JSDoc tags.
The Fix
Mechanical regex sweep across the 4 affected ai/services/<server>/ directories:
For each migrated service:
- Update
className: 'Neo.ai.mcp.server.<server>.services.X' → className: 'Neo.ai.services.<server>.X'
- Update
@member {String} className='Neo.ai.mcp.server.<server>.services.X' JSDoc tag (paired)
- Update
@class Neo.ai.mcp.server.<server>.services.X JSDoc tag (where present)
- Preserve sub-namespaces:
- MC
.services.lifecycle.X → .lifecycle.X
- MC
.managers.X → .managers.X
- GH-WF
.services.sync.X → .sync.X
- GH-WF
.services.queries.X → .queries.X
Per-server regex examples:
<h1 class="neo-h1" data-record-id="6">Memory Core flat services (top-level)</h1>
sed -i '' "s|Neo\.ai\.mcp\.server\.memory-core\.services\.|Neo.ai.services.memory-core.|g" ai/services/memory-core/*.mjs
<h1 class="neo-h1" data-record-id="7">(then specific sub-namespace passes for lifecycle/managers, etc.)</h1>
Recommended approach: write a one-shot buildScripts/ai/_align_classnames.mjs migration helper (mirroring the M6 migration script pattern), run it, delete in same PR.
No runtime-behavior change. The Neo class registry repopulates under the new keys at Neo.setupClass(); consumers using Neo.create('Neo.ai.X.Y.Z') need their references updated only if they explicitly hardcoded the old fully-qualified namespace (rare; the SDK-aliasing layer in ai/services.mjs is the proper consumer interface).
Acceptance Criteria
Out of Scope
- Renaming the SHORT class name (e.g.,
GraphService → SomethingElse). Convention preserved.
- Reshaping the SDK aliasing layer (
Memory_*, KB_*, GH_*, NeuralLink_* aliases in ai/services.mjs). Aliases are stable consumer API.
- Changing
Neo.setupClass() semantics or class-registry behavior. Pure data update.
- Consolidating the per-server namespaces (e.g., flattening
memory-core and knowledge-base into a unified services namespace). Server-family identity preserved.
- Updating the OLD
Neo.ai.mcp.server.<server> namespace in non-migrated code (config.mjs, logger.mjs, openapi.yaml, Server.mjs, shared/ helpers — these stay at server level and keep the original namespace).
Avoided Traps / Gold Standards Rejected
- Rejected: hold M6 epic closeout until className sweep lands. The four physical migrations are independently shippable; merging them without className alignment is a known-narrow gap that this ticket explicitly captures. M6 closes on physical-move-complete.
- Rejected: retroactively patch className updates into PR #11001 (open at filing time). Scope-creep during cross-family review; would force re-review on a now-clean PR. Better to merge #11001 as-is (consistent with KB+GH-WF+NL precedent) and close the systemic gap in this dedicated follow-up.
- Rejected: per-server follow-up tickets (4 separate sub-issues). Single-ticket scope is mechanical regex sweep across 4 dirs; splitting creates artificial coordination overhead without granularity benefit.
- Rejected: fold this into the v13 release-gate ticket #11003. That's a Dockerized remote MCP transport proof; mixing a cosmetic-namespace sweep with a release-gate test bloats both.
Related
- Parent epic: #10986 — Migrate Tier-1 MCP services to flat SDK boundary (M6). Drift originates here. Linked as Related rather than sub-issue, since M6 closes on the physical migrations regardless.
- Migrated PRs that introduced the drift:
- Strategic anchor:
learn/agentos/v13-path.md §3 D4 + §4 M6
- Convention reference:
learn/guides/fundamentals/CodebaseOverview.md (className-mirrors-file-path); src/core/Base.mjs setupClass registry-key behavior
Origin Session ID: c2912891-b459-4a03-b2af-154d5e264df1
Retrieval Hint: query_raw_memories(query="M6 SDK migration className drift Neo.ai.mcp.server.services flat namespace alignment KB GH-WF NL MC follow-up sweep")
Context
Filed 2026-05-09 after @tobiu caught a substrate-discipline failure on PR #11001 Cycle review (MC migration sub-4 of M6 epic #10986): all 4 M6 sub-issues (#10991 KB, #10993 GH-WF, #10994 NL, #10996 MC) physically moved service classes from
ai/mcp/server/<server>/services/to the flatai/services/<server>/SDK boundary BUT did not update the correspondingNeo.setupClass()className strings.Result: the four M6-migrated server families now have systemic drift between file location and Neo class registry namespace:
ai/services/memory-core/GraphService.mjsNeo.ai.mcp.server.memory-core.services.GraphServiceOperator's framing: "if you did not update neo classNames, inside the migration, this is an utter failure and requires new tickets."
The cited rationale on M6 sub-issues — "Renaming MC services or class names — physical move only" — was misread. "Physical move only" was meant to constrain the migration to physical relocation including namespace alignment, not to forbid namespace updates. Per Neo conventions (
@class Neo.<path>mirrors file path; KB indexing, Native Edge Graph hierarchy traversal, and class-introspection tooling all rely on this mirror), a physical move without namespace update violates the convention.The Problem
54-ish className strings across 4 server families now lie about their location. Concrete fallout:
mcp__neo-mjs-knowledge-base__get_class_hierarchyandquery_documentsrely on className-to-file-path resolution. Stale className causes wrong-file lookups or empty results.inspect_classandget_method_sourcederive expected file paths from className; agents will fail to resolve OR resolve to the wrong path expectation.@class Neo.ai.mcp.server.<server>.services.Xand@member className=...block in the 54 affected classes is now structurally wrong. KB doc pipeline ingests these as authoritative.ai/services/memory-core/GraphService.mjssee classNameNeo.ai.mcp.server.memory-core.services.GraphService, then can't reverse-derive the new location.The runtime DOES NOT crash because Neo's class registry uses className as a registry key (string-equality lookup), not as a path-derivation rule. So CI passed for KB + GH-WF + NL merges and currently passes for MC PR #11001. The drift is silent at runtime, loud in tooling and documentation surfaces.
The Architectural Reality
Neo's
Neo.setupClass(Class)usesClass.config.classNameas the registry key underglobalThis.Neo. Two consumers depend on className-mirrors-path:Neo.create('Neo.ai.X.Y.Z')resolves the class via the registry; works regardless of file location IF the registry was populated correctly. Migration-time module loading still triggersNeo.setupClass, so the class IS in the registry. But the registry key now disagrees with the file system.learn/agentos/CodebaseOverview.md+ ChromaDB-indexed source content map className → file path for documentation/AI tooling. KB embeddings include@classJSDoc tags as semantic anchors. Stale anchors mis-direct semantic search.Neo conventions per
learn/guides/fundamentals/CodebaseOverview.mdandcore/Base.mjsJSDoc precedent: className mirrors file path. The physical migration should preserve that mirror. Sub-issues #10991/#10993/#10994/#10996 all violated it.Affected files (per
grep -h "className.*Neo\.ai" ai/services/<server>/*.mjs):Neo.ai.mcp.server.knowledge-base.services.XNeo.ai.services.knowledge-base.XNeo.ai.mcp.server.github-workflow.services.X(+.sync.X,.queries.X)Neo.ai.services.github-workflow.X(+.sync.X,.queries.X)Neo.ai.mcp.server.neural-link.services.XNeo.ai.services.neural-link.XNeo.ai.mcp.server.memory-core.services.X(+.lifecycle.X,.managers.X)Neo.ai.services.memory-core.X(+.lifecycle.X,.managers.X)Total: ~54 className strings + corresponding
@member className=...JSDoc tags.The Fix
Mechanical regex sweep across the 4 affected
ai/services/<server>/directories:For each migrated service:
className: 'Neo.ai.mcp.server.<server>.services.X'→className: 'Neo.ai.services.<server>.X'@member {String} className='Neo.ai.mcp.server.<server>.services.X'JSDoc tag (paired)@class Neo.ai.mcp.server.<server>.services.XJSDoc tag (where present).services.lifecycle.X→.lifecycle.X.managers.X→.managers.X.services.sync.X→.sync.X.services.queries.X→.queries.XPer-server regex examples:
<h1 class="neo-h1" data-record-id="6">Memory Core flat services (top-level)</h1> sed -i '' "s|Neo\.ai\.mcp\.server\.memory-core\.services\.|Neo.ai.services.memory-core.|g" ai/services/memory-core/*.mjs <h1 class="neo-h1" data-record-id="7">(then specific sub-namespace passes for lifecycle/managers, etc.)</h1>Recommended approach: write a one-shot
buildScripts/ai/_align_classnames.mjsmigration helper (mirroring the M6 migration script pattern), run it, delete in same PR.No runtime-behavior change. The Neo class registry repopulates under the new keys at
Neo.setupClass(); consumers usingNeo.create('Neo.ai.X.Y.Z')need their references updated only if they explicitly hardcoded the old fully-qualified namespace (rare; the SDK-aliasing layer inai/services.mjsis the proper consumer interface).Acceptance Criteria
className: '...'config strings updated across the 4 M6-migrated server directories (ai/services/{knowledge-base,github-workflow,neural-link,memory-core}/).@member {String} className='...'JSDoc tags updated to match (1:1 with AC1).@class Neo.ai.mcp.server.<server>.services.XJSDoc tags updated to@class Neo.ai.services.<server>.Xform..lifecycle.X+.managers.X; GH-WF.sync.X+.queries.X.npm run test-unitbaseline pre/post comparison; fullnpm run test-integrationfor affected scopes; expected delta = 0 new failures.ai/,buildScripts/ai/,test/playwright/directories) audited viagrep -rn "Neo\.ai\.mcp\.server\.<server>\.services"— every match either updated or explicitly justified as a string-literal that survives className renames (e.g., wire-format strings, error-message templates).npm run ai:build-kbor equivalent) so semantic search picks up the new className anchors. Verify viaask_knowledge_base(query='GraphService class')— top result should referenceNeo.ai.services.memory-core.GraphService, not the old namespace.expect(X.className).toBe('Neo.ai.mcp.server...')or similar regex-literal assertion updated (mirrors the bulk-migration regex-edge-case from PR #11001 fix0be1f622d).mcp__neo-mjs-neural-link__get_class_hierarchy) return the migrated classes under the new namespace prefix.Out of Scope
GraphService → SomethingElse). Convention preserved.Memory_*,KB_*,GH_*,NeuralLink_*aliases inai/services.mjs). Aliases are stable consumer API.Neo.setupClass()semantics or class-registry behavior. Pure data update.memory-coreandknowledge-baseinto a unifiedservicesnamespace). Server-family identity preserved.Neo.ai.mcp.server.<server>namespace in non-migrated code (config.mjs, logger.mjs, openapi.yaml, Server.mjs, shared/ helpers — these stay at server level and keep the original namespace).Avoided Traps / Gold Standards Rejected
Related
learn/agentos/v13-path.md§3 D4 + §4 M6learn/guides/fundamentals/CodebaseOverview.md(className-mirrors-file-path);src/core/Base.mjssetupClassregistry-key behaviorOrigin Session ID:
c2912891-b459-4a03-b2af-154d5e264df1Retrieval Hint:
query_raw_memories(query="M6 SDK migration className drift Neo.ai.mcp.server.services flat namespace alignment KB GH-WF NL MC follow-up sweep")