LearnNewsExamplesServices
Frontmatter
title>-
authorneo-opus-ada
stateMerged
createdAtMay 29, 2026, 8:57 PM
updatedAtMay 29, 2026, 10:26 PM
closedAtMay 29, 2026, 10:26 PM
mergedAtMay 29, 2026, 10:26 PM
branchesdevagent/12166-parent-chain
urlhttps://github.com/neomjs/neo/pull/12179
Merged
neo-opus-ada
neo-opus-ada commented on May 29, 2026, 8:57 PM

Resolves #12166 Related: #12101

Stage 2 of the aiConfig parent-chain epic: the per-server config snapshots of deployment-wide realm leaves are removed so Knowledge Base + Memory Core inherit them from the Tier-1 root (Neo.ai.Config) through the BaseConfig.getParent() chain (added in Stage 1, this branch). This resolves the dummyEmbeddingFunction child-snapshot anti-pattern and eliminates ~40 duplicated leaf declarations across the two servers, with no change to how any consumer reads its config (ServerConfig.x.y resolves up the chain, keyed/dotted).

Authored by Claude Opus 4.8 (Claude Code). Session 653970c8-8466-4609-8a15-28350234af10.

FAIR-band: over-target — operator-directed, in-session continuation of the #12101 epic ("let us continue!"); this is the in-flight lane I authored end-to-end across Stage 1 + Stage 2, not a new-lane pickup, so over-target-with-rationale applies (not a yield candidate).

Evidence: L2 (node smoke — KB + MC inherit the realm via createConfigProxygetParent chain, env-cleared so defaults prove inheritance not passthrough; + targeted unit suites green, see Test Evidence) → L2 required (close-target ACs are config-shape + chain-resolution, fully unit+smoke coverable; the operator separately runtime-confirmed the live orchestrator boots on this realm at qwen3-8b/4096). No residuals.

Changed config keys (per mcp-config-template-change-guide)

  • knowledge-base/config.template.mjs — removed (now inherited from Tier-1): backupPath, auth.{host,port,realm,issuerUrl,clientId,clientSecret,trustProxyIdentity}, dummyEmbeddingFunction. Kept local (path-mismatched top-level chroma aliases, pending the S3/S4 consumer-read codemod): host, port, path (+ retains import AiConfig).
  • memory-core/config.template.mjs — removed (now inherited): auth.*, modelProvider, graphProvider, embeddingProvider, ollama.*, openAiCompatible.*, localModels, vectorDimension, modelName, embeddingModel, engines.chroma.*, backupPath. import AiConfig → bare side-effect import (loads the Tier-1 realm root for the chain; no value snapshot).
  • scripts/setup/initServerConfigs.mjsmaterializeServerConfigTemplate extended to rewrite side-effect Tier-1 imports (template → overlay) so the generated MC runtime config.mjs points at the operator overlay.

Local config.mjs follow-up: YES — see Post-Merge Validation. Restart: recommended after a config.mjs regen. Gitignored config.mjs: not committed (verified).

Deltas from ticket (if any)

  • Ticket Fix §1 / avoided-traps amended (load-order): the ticket assumed "runtime boot imports ai/config.mjs / no import edge." V-B-A of the KB + MC MCP-server import graphs falsified this — each server process's only Tier-1 load path was its config's import AiConfig. So MC keeps a bare side-effect import (no value snapshot ⇒ anti-pattern still resolved), and KB keeps its value import (still needed by the retained chroma aliases). This mooted the originally-floated "side-effect-import everywhere" option.
  • Chroma deferred to S3/S4: KB exposes chroma at top-level host/port/path while Tier-1 owns engines.chroma.*; the chain resolves by exact path, so collapsing them is a consumer-read change (S3/S4 codemod), out of #12166 scope. KB therefore retains those 3 snapshots + import AiConfig. MC keeps engines.chroma.* nested (same path) → fully inherited.
  • Enumeration-asymmetry is real for tests: facet-9 ("no runtime consumer enumerates a shared namespace") held, but the #11963 / #10010 config specs DID enumerate (toEqual(config.auth)) — rewritten to keyed assertions. The deferred getTopLevelDataKeys merge remains a documented latent hazard.
  • getParent blast-radius: the Stage-1 override makes every fresh BaseConfig inherit the registered realm root; Orchestrator.invariants mlx/lms undefined-safe tests now detach the root to simulate a genuinely-missing namespace. Config specs install a deterministic Tier-1 root in beforeAll (the template side-effect import only registers Neo.ai.Config on first module-eval → reused Playwright workers would otherwise see it unset).

Test Evidence

npm run test-unit (targeted), all green:

  • BaseConfig.spec 17/17; config.template.spec (Tier-1) 5/5; KB config.template.spec 2/2; MC config.template.spec 6/6.
  • Consumers 24/24: SemanticGraphExtractor, SessionService, ChromaManager, KB + MC DatabaseService.
  • daemons/orchestrator (incl. Orchestrator.invariants) + initServerConfigs.spec — 406 passed; ConfigCompleteness 5/5.
  • node smoke (env-cleared): KB inherits auth.realm=master / dummyEmbeddingFunction.name / backupPath; MC inherits the full realm incl. localModels.chat.contextLimitTokens=262144 + engines.chroma.host=localhost; both via createConfigProxygetParent. Materializer rewrites MC's side-effect import → overlay.

Post-Merge Validation

  • Operators regenerate local config.mjs to ADOPT the dedup (config.mjs then inherits from Tier-1). NOTE: --migrate-config will NOT auto-trigger — this is a leaf-body change (KB) / side-effect-import change (MC), neither captured by the import-shape detectDrift (the S0 #12102 fragility). The stale config.mjs stays FUNCTIONAL (retains local snapshots + still loads Tier-1) ⇒ no boot-break. To adopt, force-regenerate the gitignored config.mjs from templates, then restart.
  • All three core clones (Claude / GPT / Gemini) force-regenerate + restart to adopt the inheriting shape.

Commits

  • 9256e9c0c — Stage 1: getParent() override + cross-tier write validation + BaseConfig.spec tests.
  • 18feed683 — Stage 2: realm split (KB + MC inherit) + materializer + config-spec + Orchestrator.invariants updates.

Decision Record impact: none (extends Epic #12101's "extend Provider, don't reinvent" mandate; the ticket's load-order assumption is corrected in Deltas).