Frontmatter
| title | feat(ai): skip MC ChromaDB spawn in unified mode (#10007) |
| author | tobiu |
| state | Merged |
| createdAt | Apr 20, 2026, 3:06 PM |
| updatedAt | Apr 20, 2026, 3:40 PM |
| closedAt | Apr 20, 2026, 3:40 PM |
| mergedAt | Apr 20, 2026, 3:40 PM |
| branches | dev ← agent/10007-unified-db-bypass |
| url | https://github.com/neomjs/neo/pull/10123 |

Input from Claude Opus 4.7 (Claude Code):
❋ ## PR Review Summary
Status: Approved
Self-Review Opening: Self-review of #10007. This completes the unified-topology bypass end-to-end for Memory Core — the Architectural Pillar arc of sub-epic #10015 now closes with #10001 (routing) + #10007 (lifecycle). Scope was deliberately narrow: two guards in one file + a focused spec, matching the ticket's minimal task surface. Trade-offs and gaps below.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 90 — Guards placed at both the automatic entry point (initAsync) and the manual MCP-tool-exposed entry point (startDatabase, delegated viamanageDatabase). Class JSDoc Anchor echoes the #10001 vocabulary (chromaUnified, engines.kb.chroma, unified/federated) for graph-linking continuity. One minor inconsistency:stopDatabasehas no explicit guard; behavior is correct (chromaProcessstays null → existing!this.chromaProcesscheck returns{status: 'not_running'}), but a reader might question the contract. Optional polish.[CONTENT_COMPLETENESS]: 93 — Class Anchor +initAsync+startDatabaseall carry targeted JSDoc referencing Epic #9999, sub-epic #10015, ticket #10007. PR body covers Summary, Scope table, ticket-body drift disclosure, Test plan, What this unblocks, Follow-up.manageDatabaserouter docstring unchanged (it still works correctly via delegation, but a one-liner noting the unified-mode passthrough would improve reader ergonomics).learn/agentos/MemoryCore.mddocs update deferred — same rationale as #10001, wait until #10000 + #10010 complete the multi-tenant deployment story to avoid partial-feature doc drift.[EXECUTION_QUALITY]: 87 — Short-circuits are correct (check before federated-mode gate). Tests cover (1)startDatabasereturnsskipped_unified_mode+chromaProcessstays null, (2)manageDatabase({action:'start'})propagates the skip status, (3) default-posture guard. Tests use try/finally for config mutation per the symmetric-cleanup memory. Missing: explicit test forinitAsyncshort-circuit (currently implicit viastartDatabasecoverage + zero-side-effect assertion). Belt-and-suspenders improvement, not structural.[PRODUCTIVITY]: 95 — All 3 ticket tasks delivered with owner-drift correction (ticket namedDatabaseService.mjs; real owner isChromaLifecycleService.mjs). Task 3 (federated-mode fallback) is preserved by leaving the existingautoStartDatabase && architecturegate intact below the new short-circuit.[IMPACT]: 80 — This PR + #10001 together deliver the complete runtime unlock for single-container unified deployment. Higher than #10001's 75 because this PR is where operators actually see the end-to-end capability come alive (no more manualNEO_MEM_AUTO_START_DATABASE=falseworkaround). Sub-epic #10015's topology pillar closes with this merge.[COMPLEXITY]: 25 — 2 files, +89/-1, pure guard additions to existing methods, no cross-module refactor. Conceptually simple: check flag, early-return.[EFFORT_PROFILE]: Quick Win — Completes the Architectural Pillar arc of #10015; standalone-shippable + high leverage.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #10007
- Related Graph Nodes: Epic #9999 (Cloud-Native Knowledge & Multi-Tenant Memory Core); sub-epic #10015 (Dynamic Topology) — pillar now closed with #10001 (PR #10121) + #10007 (this PR); upcoming sub-epic #10016 for #10000 (identity injection) + #10010 (team-vs-private read flag)
- Branch state:
agent/10007-unified-db-bypassis 1 commit behindorigin/dev(missingb4c0565c7— the confidentiality-hygiene scrub from PR #10124). Merge is safe because this branch never touchedconfig.template.mjs; git 3-way merge will takedev's scrubbed version. Explicit confirmation rather than assumption.
🧠 Graph Ingestion Notes
[KB_GAP]: Ticket body drift repeats — #10007 namesai/mcp/server/memory-core/services/DatabaseService.mjsas the refactor target; actual lifecycle owner isChromaLifecycleService.mjs.DatabaseService.mjsis the unrelatedmanage_database_backupexport/import service. Same 2026-04-14 planning-session staleness that #10001's review flagged; theStorageRouter+CollectionProxyresidue from the abandonedvec0experiment compounds it. Future agents picking up any remaining #10015 or #10016 sub-tickets should treat named files as intent-signals and re-verify viagrep_searchbefore accepting the ticket's file-level prescription.
[RETROSPECTIVE]: Two architectural patterns worth capturing permanently:
Dual-point defense for lifecycle bypass. The
initAsyncshort-circuit prevents the common-path spawn; thestartDatabasetop-of-function guard catches the operator-manual path via themanage_databaseMCP tool. This pattern — defend at both the automatic entry point AND the manual tool-exposed entry point — is reusable for any lifecycle operation that can be triggered both by init flow and by MCP tool invocation. Will document inlearn/agentos/MemoryCore.mdwhen the deferred docs pass happens.Default-posture guard is now a 2-test pattern across the topology.
ChromaManager.spec.mjsassertsshipped config template defaults to federated mode — MC own instance on 8001; this PR addsshipped config template defaults chromaUnified=false — federated-mode startup path stays activefrom the lifecycle angle. The redundancy is intentional — routing and lifecycle are separate contracts; a template flip must fail CI in both. Pattern extends naturally to #10000 (default-deny userId filter posture) and #10010 (default-team-memory vs default-private-memory posture).
📋 Required Actions
No blocking changes identified. Three optional polish items — none rise to merge-breaking per the severity ladder.
- (Optional polish) Add a one-line JSDoc note on
manageDatabaserouter noting that it propagates theskipped_unified_modestatus whenchromaUnified=truevia delegation tostartDatabase. Reader ergonomics only; the code is correct.- (Optional polish) Add an explicit
initAsyncshort-circuit test — currently implicit viastartDatabasecoverage + zero-side-effect assertion. Belt-and-suspenders; happy to push a follow-up commit if you want it before merge.- (Follow-up ticket — carried forward from #10001) HealthService topology reporting — now that the full topology pillar closes with this merge,
/healthreturning{topology: 'unified' | 'federated', coordinates: {host, port}}becomes tangibly operator-valuable. Worth filing post-merge.Approved for merge. Next in sequence: #10000 (hardened identity ingestion from JWT claims — Pattern A per earlier alignment) → #10010 (team-vs-private read flag).

Input from Claude Opus 4.7 (Claude Code):
❋ Polish commit pushed addressing the two in-PR items from the self-review:
manageDatabaserouter JSDocAdded a paragraph explicitly describing the unified-mode passthrough behavior — both the
startbranch (propagatesstartDatabase'sskipped_unified_moderesponse) and thestopbranch (falls through tostopDatabase's existingnot_runningpath becausechromaProcesswas never populated). Reader lands on the method with full unified-mode context without having to cross-readstartDatabase.Explicit
initAsyncshort-circuit testNew test:
initAsync short-circuits in unified mode even when autoStartDatabase=true. SetschromaUnified=trueANDautoStartDatabase=true(which would normally trigger spawn in federated mode) and assertschromaProcessstays null — locks in that the short-circuit runs BEFORE theautoStartDatabase && architecturegate.Without this test, a future edit reordering the guard below the gate — or deleting it entirely — would pass the other two tests (they hit
startDatabase/manageDatabasedirectly and see the method-level guard). The explicit init-flow test forces the bypass to be observable at every layer it needs to hold.Uses the symmetric-cleanup pattern:
_initPromise+chromaUnified+autoStartDatabaseall reset infinallyto avoid leaking state into sibling specs (per thefeedback_symmetric_spec_cleanup.mdmemory surfaced byfullyParallelinterleaving flakes earlier this session).Results
ChromaLifecycleService.spec.mjs— 4/4 passing (1 new + 3 existing)- Net diff this commit: +40 / −5
Deferred item (not this PR)
HealthService topology reporting — follow-up ticket, not a polish commit. Best filed as a dedicated issue post-merge since it's behavior addition (new
/healthfield + associated spec) rather than hardening the existing #10007 surface. Carried over from #10001's self-review; becomes more operator-valuable now that the full topology pillar is complete. Happy to file it next if you want.
Summary
Completes the unified-topology bypass for Memory Core. When
aiConfig.chromaUnified === true,ChromaLifecycleService:initAsync()short-circuits before any daemon-spawn gate so MC does not race the Knowledge Base server for the shared ChromaDB portstartDatabase()returns{status: 'skipped_unified_mode', detail: '...'}without attempting a spawn — defensive guard so themanage_databaseMCP tool (which delegates here viamanageDatabase()) also returns a clear no-op if an operator invokes it manually in unified modePairs with #10001 (merged as PR #10121), which landed the client-side routing (
ChromaManager.resolveChromaCoordinatesselectingengines.kb.chromawhenchromaUnified=true). Together these two PRs let operators deploy a single-container KB + MC topology without the manualNEO_MEM_AUTO_START_DATABASE=falseworkaround that #10001's JSDoc documented.Scope
ai/mcp/server/memory-core/services/lifecycle/ChromaLifecycleService.mjsinitAsync()gainsaiConfig.chromaUnifiedshort-circuit.startDatabase()gains a top-of-function guard returningskipped_unified_mode.test/playwright/unit/ai/mcp/server/memory-core/services/lifecycle/ChromaLifecycleService.spec.mjsstartDatabasereturnsskipped_unified_modeand does not mutatechromaProcess;manageDatabase({action:'start'})propagates the skip status; default-posture guard asserts the shipped config shipschromaUnified=false(mirrors the equivalent guard inChromaManager.spec.mjsfrom #10001).No config template changes needed —
chromaUnifiedalready shipped via #10001.Ticket-body drift noted
#10007's body names
ai/mcp/server/memory-core/services/DatabaseService.mjsas the refactor target. The actual lifecycle owner isChromaLifecycleService.mjs.DatabaseService.mjsis the export/import/truncate service for themanage_database_backupMCP tool, which is unrelated to process spawn. Same ticket-body drift pattern I flagged during #10001 intake — planning-session files named before thevec0experiment rollback + the monolithic-service decomposition.Test plan
ChromaLifecycleService.spec.mjs— 3/3 passingChromaManager.spec.mjs+ both lifecycle specs together — 8/8 passing (no cross-spec regression)chromaUnified=false(default) preserves existinginitAsync→startDatabasepath. Default-posture guard asserts this invariant.What this unblocks
With #10001 + #10007 both merged, the single-container KB + MC unified deployment works without manual operator workarounds:
NEO_CHROMA_UNIFIED=trueon the cloud containerNEO_KB_CHROMA_HOST/NEO_KB_CHROMA_PORTif non-defaultChromaManagerclient to port 8000 (#10001)Next in the sub-epic sequence: #10000 (hardened identity ingestion from OAuth claims) + #10010 (team-vs-private read flag).
Follow-up worth filing post-merge
HealthService topology reporting — operators deploying with unified mode will want
/healthto surface{topology: 'unified' | 'federated'}+ the effective ChromaDB coordinates so they can verify the flag resolved as expected. Flagged as a follow-up during #10001 self-review; still open. Becomes more valuable once #10000 + #10010 land and the full multi-tenant deployment path is complete.Resolves #10007
Origin Session ID: 1c001810-be28-4554-bb56-c98f9b91bbfb