LearnNewsExamplesServices
Frontmatter
titlefeat(ai): skip MC ChromaDB spawn in unified mode (#10007)
authortobiu
stateMerged
createdAtApr 20, 2026, 3:06 PM
updatedAtApr 20, 2026, 3:40 PM
closedAtApr 20, 2026, 3:40 PM
mergedAtApr 20, 2026, 3:40 PM
branchesdevagent/10007-unified-db-bypass
urlhttps://github.com/neomjs/neo/pull/10123
Merged
tobiu
tobiu commented on Apr 20, 2026, 3:06 PM

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 port
  • startDatabase() returns {status: 'skipped_unified_mode', detail: '...'} without attempting a spawn — defensive guard so the manage_database MCP tool (which delegates here via manageDatabase()) also returns a clear no-op if an operator invokes it manually in unified mode

Pairs with #10001 (merged as PR #10121), which landed the client-side routing (ChromaManager.resolveChromaCoordinates selecting engines.kb.chroma when chromaUnified=true). Together these two PRs let operators deploy a single-container KB + MC topology without the manual NEO_MEM_AUTO_START_DATABASE=false workaround that #10001's JSDoc documented.

Scope

File Change
ai/mcp/server/memory-core/services/lifecycle/ChromaLifecycleService.mjs Class JSDoc Anchor expanded with unified-mode contract. initAsync() gains aiConfig.chromaUnified short-circuit. startDatabase() gains a top-of-function guard returning skipped_unified_mode.
test/playwright/unit/ai/mcp/server/memory-core/services/lifecycle/ChromaLifecycleService.spec.mjs New spec. 3 tests: startDatabase returns skipped_unified_mode and does not mutate chromaProcess; manageDatabase({action:'start'}) propagates the skip status; default-posture guard asserts the shipped config ships chromaUnified=false (mirrors the equivalent guard in ChromaManager.spec.mjs from #10001).

No config template changes needed — chromaUnified already shipped via #10001.

Ticket-body drift noted

#10007's body names ai/mcp/server/memory-core/services/DatabaseService.mjs as the refactor target. The actual lifecycle owner is ChromaLifecycleService.mjs. DatabaseService.mjs is the export/import/truncate service for the manage_database_backup MCP tool, which is unrelated to process spawn. Same ticket-body drift pattern I flagged during #10001 intake — planning-session files named before the vec0 experiment rollback + the monolithic-service decomposition.

Test plan

  • New ChromaLifecycleService.spec.mjs — 3/3 passing
  • ChromaManager.spec.mjs + both lifecycle specs together — 8/8 passing (no cross-spec regression)
  • Backward compatibility: chromaUnified=false (default) preserves existing initAsyncstartDatabase path. 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:

  1. Set NEO_CHROMA_UNIFIED=true on the cloud container
  2. Set NEO_KB_CHROMA_HOST / NEO_KB_CHROMA_PORT if non-default
  3. KB server starts, owns the ChromaDB daemon on port 8000
  4. MC server starts, skips its own daemon spawn (this PR), routes ChromaManager client 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 /health to 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

tobiu
tobiu commented on Apr 20, 2026, 3:32 PM

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 via manageDatabase). Class JSDoc Anchor echoes the #10001 vocabulary (chromaUnified, engines.kb.chroma, unified/federated) for graph-linking continuity. One minor inconsistency: stopDatabase has no explicit guard; behavior is correct (chromaProcess stays null → existing !this.chromaProcess check returns {status: 'not_running'}), but a reader might question the contract. Optional polish.
  • [CONTENT_COMPLETENESS]: 93 — Class Anchor + initAsync + startDatabase all 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. manageDatabase router docstring unchanged (it still works correctly via delegation, but a one-liner noting the unified-mode passthrough would improve reader ergonomics). learn/agentos/MemoryCore.md docs 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) startDatabase returns skipped_unified_mode + chromaProcess stays 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 for initAsync short-circuit (currently implicit via startDatabase coverage + zero-side-effect assertion). Belt-and-suspenders improvement, not structural.
  • [PRODUCTIVITY]: 95 — All 3 ticket tasks delivered with owner-drift correction (ticket named DatabaseService.mjs; real owner is ChromaLifecycleService.mjs). Task 3 (federated-mode fallback) is preserved by leaving the existing autoStartDatabase && architecture gate 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 manual NEO_MEM_AUTO_START_DATABASE=false workaround). 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-bypass is 1 commit behind origin/dev (missing b4c0565c7 — the confidentiality-hygiene scrub from PR #10124). Merge is safe because this branch never touched config.template.mjs; git 3-way merge will take dev's scrubbed version. Explicit confirmation rather than assumption.

🧠 Graph Ingestion Notes

  • [KB_GAP]: Ticket body drift repeats — #10007 names ai/mcp/server/memory-core/services/DatabaseService.mjs as the refactor target; actual lifecycle owner is ChromaLifecycleService.mjs. DatabaseService.mjs is the unrelated manage_database_backup export/import service. Same 2026-04-14 planning-session staleness that #10001's review flagged; the StorageRouter + CollectionProxy residue from the abandoned vec0 experiment compounds it. Future agents picking up any remaining #10015 or #10016 sub-tickets should treat named files as intent-signals and re-verify via grep_search before accepting the ticket's file-level prescription.

  • [RETROSPECTIVE]: Two architectural patterns worth capturing permanently:

    1. Dual-point defense for lifecycle bypass. The initAsync short-circuit prevents the common-path spawn; the startDatabase top-of-function guard catches the operator-manual path via the manage_database MCP 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 in learn/agentos/MemoryCore.md when the deferred docs pass happens.

    2. Default-posture guard is now a 2-test pattern across the topology. ChromaManager.spec.mjs asserts shipped config template defaults to federated mode — MC own instance on 8001; this PR adds shipped config template defaults chromaUnified=false — federated-mode startup path stays active from 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 manageDatabase router noting that it propagates the skipped_unified_mode status when chromaUnified=true via delegation to startDatabase. Reader ergonomics only; the code is correct.
  • (Optional polish) Add an explicit initAsync short-circuit test — currently implicit via startDatabase coverage + 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, /health returning {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).


tobiu
tobiu commented on Apr 20, 2026, 3:36 PM

Input from Claude Opus 4.7 (Claude Code):

❋ Polish commit pushed addressing the two in-PR items from the self-review:

manageDatabase router JSDoc

Added a paragraph explicitly describing the unified-mode passthrough behavior — both the start branch (propagates startDatabase's skipped_unified_mode response) and the stop branch (falls through to stopDatabase's existing not_running path because chromaProcess was never populated). Reader lands on the method with full unified-mode context without having to cross-read startDatabase.

Explicit initAsync short-circuit test

New test: initAsync short-circuits in unified mode even when autoStartDatabase=true. Sets chromaUnified=true AND autoStartDatabase=true (which would normally trigger spawn in federated mode) and asserts chromaProcess stays null — locks in that the short-circuit runs BEFORE the autoStartDatabase && architecture gate.

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 / manageDatabase directly 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 + autoStartDatabase all reset in finally to avoid leaking state into sibling specs (per the feedback_symmetric_spec_cleanup.md memory surfaced by fullyParallel interleaving 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 /health field + 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.