LearnNewsExamplesServices
Frontmatter
titlefeat(ai): introduce chromaUnified flag for shared-ChromaDB topology (#10001)
authortobiu
stateMerged
createdAtApr 20, 2026, 12:40 PM
updatedAtApr 20, 2026, 3:23 PM
closedAtApr 20, 2026, 1:05 PM
mergedAtApr 20, 2026, 1:05 PM
branchesdevagent/10001-chroma-unified-flag
urlhttps://github.com/neomjs/neo/pull/10121
Merged
tobiu
tobiu commented on Apr 20, 2026, 12:40 PM

Summary

Introduces the NEO_CHROMA_UNIFIED deployment-topology flag (Epic #9999, sub-epic #10015). When true, Memory Core's ChromaClient connects to the shared Knowledge Base ChromaDB instance at kbChroma.{host, port} (typically port 8000) instead of addressing its own instance at engines.chroma.{host, port} (port 8001). Default remains false — existing federated deploys see no behavioral change.

Motivation

Enables a single-cloud-container deployment shape where the Knowledge Base and Memory Core share one ChromaDB process — minimizing RAM overhead and operational surface for centrally hosted multi-tenant Memory Core setups, where a dev team runs agents against shared cross-session memory. This PR lands the client-side routing; sibling PRs in the sub-epic will cover lifecycle bypass (#10007), userId injection (#10000), and team-read flag (#10010) to complete the deployment target.

Scope

File Change
ai/mcp/server/memory-core/config.template.mjs Adds chromaUnified (env: NEO_CHROMA_UNIFIED) + kbChroma.{host, port} (envs: NEO_KB_CHROMA_HOST, NEO_KB_CHROMA_PORT)
ai/mcp/server/knowledge-base/config.template.mjs Adds chromaUnified for operator symmetry / documentation (no client-side branching — KB always owns its ChromaDB process)
ai/mcp/server/memory-core/managers/ChromaManager.mjs construct() resolves coordinates via a new resolveChromaCoordinates(cfg) instance method; updated class JSDoc Anchor to document the unified-vs-federated semantics
test/playwright/unit/ai/mcp/server/memory-core/managers/ChromaManager.spec.mjs 3 new tests: federated-mode resolution, unified-mode resolution, and a default-posture guard asserting the shipped template defaults to federated

Gitignored config.mjs files (MC + KB) were mirrored locally so tests pick up the new keys. Per the existing template/local parity convention, the template is the repo's source of truth; bootstrapWorktree.mjs copies main's config into fresh worktrees.

Out of scope / deferred

  • Ticket task 3 (federated OAuth headers for a secondary ChromaClient) — DreamService's original Macro-DB federation use case was superseded by Concept Ontology (#10030, which uses the SQLite graph). No cross-service ChromaDB queries are required for the single-container deployment shape. Can be added as a follow-up PR if a future cross-network federated scenario emerges; the chromaUnified flag and kbChroma config shape already leave room for it.
  • Lifecycle bypass in unified mode#10007, next PR in the sub-epic. Without it, operators must manually set NEO_MEM_AUTO_START_DATABASE=false in unified mode so MC doesn't spawn a duplicate ChromaDB process. JSDoc on chromaUnified documents this pairing explicitly.
  • Ticket body invariances caught during intake#10001's body references DatabaseService.mjs for lifecycle bypass (the actual owner is ChromaLifecycleService.mjs) and names StorageRouter as the ChromaClient owner (it's ChromaManager). Historical drift from the 2026-04-14 planning session before the better-sqlite3 + vec0 experiment was rolled back — the StorageRouter/CollectionProxy abstraction is a residual from that discarded alternative-vector-store exploration.

Test plan

  • ChromaManager.spec.mjs in isolation — 4/4 passing (3 new topology tests + 1 existing console.warn mutex regression test)
  • Full memory-core/ suite — baseline has 7 pre-existing failures (5 SessionSummarization specs failing on undefined aiConfig.engines.neo.*, McpServerToolLimits on a 1161-char tool description exceeding the 1024 limit, FileSystemIngestor path-pattern assertion). No new failures introduced by this PR; same 7 fail on git stash baseline.
  • Backward compatibility — chromaUnified=false (default) preserves the existing engines.chroma.{host, port} resolution. Default-posture guard in the new spec asserts this invariant so accidental flag flips in the shipped template fail CI.

Follow-ups identified during intake (filing separately)

  • engine vs architecture config-name drift in CollectionProxy.mjs:22 and ChromaLifecycleService.mjs:53 — code reads aiConfig.architecture; config exposes engine; || 'hybrid' fallback silently masks the mismatch.
  • aiConfig.engines.neo.* missing from both template and local config — causes the 5 pre-existing SessionSummarization failures.
  • HealthService topology reporting — surface effective unified / federated state in /health for operator diagnostics.

Resolves #10001

Origin Session ID: 1c001810-be28-4554-bb56-c98f9b91bbfb

PR Review Summary

Status: Approved

Self-Review Opening: Self-review of #10001. This implementation chose client-side routing as the narrow scope (tasks 1+2) over the ticket's original 3-task scope because the 2026-04-14 planning-session motivation for task 3 (DreamService federation for Macro-DB Gap Inference) was superseded by Concept Ontology (#10030)'s SQLite-graph approach. Key trade-offs and gaps noted below.


📊 Evaluation Metrics

  • [ARCH_ALIGNMENT]: 87 — Config-driven, singleton-preserving, backward-compatible. kbChroma sits top-level rather than under engines, which mildly fragments the config organization; the choice was semantic ("reference to another service's ChromaDB," not "one of MC's own engines") rather than structural, but the inconsistency cost is real.
  • [CONTENT_COMPLETENESS]: 92 — Full Anchor & Echo across MC/KB config JSDoc, ChromaManager class Anchor, and the extracted method. PR body covers motivation, scope, deferrals, follow-ups. Missed: no learn/agentos/MemoryCore.md update — deferred until #10007 completes the unified-mode picture to avoid partial-feature doc drift.
  • [EXECUTION_QUALITY]: 85 — Resolution is a pure function of config; no race conditions. Missing defensive handling for chromaUnified=true && !cfg.kbChroma — can't occur with defaultConfig present, but an explicit custom override file could strip the key silently. Tests cover both branches + default-posture guard; env-driven instantiation is E2E-level and correctly punted to #10008/#10009.
  • [PRODUCTIVITY]: 90 — 2/3 ticket tasks delivered; task 3 deferred with rationale. The deferral is architecturally correct (substrate moved under my feet when Macro DB died) rather than a scope cut.
  • [IMPACT]: 75 — Strategic impact is high (unlocks the Cloud-Native Memory Core deployment topology for team-shared MC deploys), but immediate runtime impact is partial until #10007 lands the lifecycle bypass; operators currently need manual NEO_MEM_AUTO_START_DATABASE=false in unified mode.
  • [COMPLEXITY]: 30 — 4 files, 93 insertions, no cross-module refactor; the topology concept itself is simple once stated.
  • [EFFORT_PROFILE]: Quick Win — High strategic ROI, low implementation cognitive load. Part of a larger Architectural Pillar arc (#10015 sub-epic) but standalone-shippable.

🕸️ Context & Graph Linking

  • Target Epic / Issue ID: Resolves #10001
  • Related Graph Nodes: Epic #9999 (Cloud-Native Knowledge & Multi-Tenant Memory Core); sub-epic #10015 (Dynamic Topology); sequential follow-on tickets #10007 (lifecycle bypass), #10000 (userId injection), #10010 (team/private read flag); active frontier node tracking the multi-tenant Memory Core deployment focus

🧠 Graph Ingestion Notes

  • [KB_GAP]: Ticket body drift from the 2026-04-14 planning session — #10001 references DatabaseService.mjs (actual lifecycle owner: ChromaLifecycleService.mjs) and names StorageRouter as the ChromaClient owner (actual owner: ChromaManager). The StorageRouter + CollectionProxy abstraction layer is residue from the abandoned better-sqlite3 + vec0 alternative-vector-store experiment that was rolled back after discovering the missing HNSW + skip-list support. Future agents processing stale tickets should treat named files as intent-signals and re-verify via grep rather than accepting the ticket's file-level prescription at face value.

  • [TOOLING_GAP]: The full test-unit memory-core/ run surfaces 10 failures — 7 pre-existing baseline (5 × SessionSummarization on undefined aiConfig.engines.neo.*, 1 × McpServerToolLimits on a 1161-char tool description exceeding the 1024 limit, 1 × FileSystemIngestor path-pattern assertion) and 3 × fullyParallel interleaving flake in GraphService/QueryReRanker specs. The 3 flakes pass cleanly in isolation (15/15) — symmetric beforeEach/afterEach cleanup for cross-singleton state mutations would stabilize them. Pre-existing issue, covered by the feedback_symmetric_spec_cleanup.md memory; documenting here that it surfaced again during this PR's verification so the Retrospective daemon re-weights it.

  • [RETROSPECTIVE]: The decision to extract resolveChromaCoordinates(cfg) as a testable instance method rather than inlining the ternary in construct() enabled a default-posture guard test (shipped config template defaults to federated mode) that prevents a silent-production-bug class: an accidental chromaUnified=true flip in the shipped template would redirect every fresh checkout to port 8000 without visible symptom until cross-tenant data pollution emerges. Pattern worth echoing: extract config-resolution into a named method when the resolution is operator-visible and has an invariant (here: default posture) worth guarding in CI. Same pattern applies to #10000 (userId extraction from JWT claims) and #10010 (team-mode read-flag resolution) — both will benefit from extracted-and-guarded resolution methods.


📋 Required Actions

No blocking changes identified. Three optional polish items below — none rise to merge-breaking per the severity ladder (Blocker reserved for correctness/test-breaking, not style nits).

  • (Optional polish) Consider relocating kbChroma under engines (e.g., engines.kb.chroma) for structural consistency with engines.chroma. Current top-level placement was a semantic choice ("reference to another service") but the inconsistency is real. Not a blocker — trivially refactorable in a follow-up if the asymmetry grates.
  • (Optional polish) Add a defensive check in resolveChromaCoordinates: if chromaUnified=true && !cfg.kbChroma, throw a clear error rather than destructuring undefined. Can't occur with defaultConfig present but a custom override file could strip the key silently and produce ChromaClient({host: undefined, port: undefined}) that would fail later at heartbeat with a less obvious error. Trivial addition; happy to push a follow-up commit if you want it before merge.
  • (Follow-up ticket, filing post-merge) HealthService topology reporting — surface {topology: 'unified' | 'federated'} in /health so operators deploying to cloud can verify the flag resolved as expected without inspecting logs. Already noted in the PR body's follow-ups section.

Approved for merge. Next in sequence: #10007 (lifecycle bypass in unified mode), then the ROADMAP.md update folding this work into the v12.2 path.


@tobiu commented on 2026-04-20T11:02:24Z

Input from Claude Opus 4.7 (Claude Code):

❋ Polish commit pushed (2e2bb47ff) addressing the two optional items from the self-review:

  1. kbChroma nested under engines.kb.chroma for structural consistency with engines.chroma. JSDoc on the engines block now documents the convention: flat engines.<engineType> = MC-owned; nested engines.<serviceName>.<engineType> = cross-service engine reference. Establishes the extension point for any future federated targets.

  2. Defensive throw in resolveChromaCoordinates when chromaUnified=true && !engines.kb.chroma. Surfaces the misconfiguration at construct-time with an actionable message pointing at the override and the env vars, rather than letting new ChromaClient({host: undefined, port: undefined}) fail later at heartbeat with a generic error.

New test coverage: unified mode throws a clear error when engines.kb.chroma is absent — 5/5 ChromaManager.spec.mjs pass. HealthService topology reporting remains as a post-merge follow-up ticket per the review.


tobiu
tobiu commented on Apr 20, 2026, 12:53 PM