Authored by: [Claude Opus 4.7] (Claude Code)
Graduated from: Discussion #11869 ([GRADUATION_APPROVED] by @neo-gpt @ DC_kwDODSospM4BA_ND)
Decision Record impact: none (no existing ADR addresses unified config/env substrate; this Epic establishes the foundational shape that a future ADR may codify).
Context
V-B-A failure cascade earlier today:
- Task #22 lifted
ai/mcp/server/shared/helpers/EnvConfig.mjs into Neo.util.Env (src/util/Env.mjs, created TODAY) — but the "3 partial extractions" note was the V-B-A tell that the lift was incomplete: EnvConfig.mjs was NOT deleted, 5 MCP server config.templates still import from it.
- Sub 14 (#11855, PR #11868 merged) added
ai/config/env.mjs as a 3rd env-binding mechanism — designed without surveying the 2 pre-existing ones.
Operator surfaced the cascade + supplied historical context (Discussion #11869 body). Cross-family Ideation Sandbox cycle followed; @neo-gpt's STEP_BACK + signal pivot to APPROVED on operator-confirmed direct-cutover scope.
The Problem
3 coexisting env-binding mechanisms with semantic divergence + no top-level/server config merge contract + no two-file pattern at ai/ root + no npx-workspace path + no dotenv integration in MCP servers + 5 per-server logger reimplementations.
| Mechanism |
Where |
Used by |
Issue |
Neo.util.Env (Tier-1) |
src/util/Env.mjs (created TODAY) |
Only ai/config/env.mjs directly |
Permissive parseBool (true/yes/on/1) |
EnvConfig.mjs (Tier-2, 83 LOC) |
ai/mcp/server/shared/helpers/EnvConfig.mjs |
5 MCP server config.templates + BaseConfig.mjs |
Strict parseBool ('true'/'false' only); semantic-diverges from Tier-1 |
ai/config/env.mjs (flat registry) |
ai/config/env.mjs (NEW, Sub 14) |
Orchestrator only |
3rd mechanism; the proximate trigger |
Bare process.env.X || default |
Inline in ai/config.template.mjs, ai/daemons/bridge/daemon.mjs, ai/daemons/kb-alerting/KbAlertingService.mjs, ai/daemons/orchestrator/TaskDefinitions.mjs |
Various |
4th de-facto pattern |
The Architectural Reality
Per @neo-gpt's STEP_BACK 8-point sweep:
Consumer ledger (full surface — graduation AC requires explicit list):
- 5 MCP config.templates (memory-core, knowledge-base, neural-link, github-workflow, gitlab-workflow)
ai/mcp/server/shared/BaseConfig.mjs (natural merge hook)
- Top-level
AiConfig (ai/config.template.mjs, 347 LOC, ~30 importers)
- Orchestrator (
ai/daemons/orchestrator/Orchestrator.mjs)
- Backup / defrag scripts
- KB alerting / reconciliation / GC fallback readers
ai/scripts/setup/initServerConfigs.mjs (config bootstrap)
ai/scripts/migrations/bootstrapWorktree.mjs (BOOTSTRAP_CONFIGS array)
- npm postinstall hook (mirrors bootstrapWorktree scope)
- npx neo-app workspaces (JSON shape override)
- Tests importing gitignored
config.mjs
- KB tenant config (graph/yaml/default resolver; reserve extension slot)
Current-state corrections (per @neo-gpt's authority sweep):
ai/config.mjs already exists as a gitignored top-level companion in parts of the substrate — but not uniformly bootstrapped / universally consumed. The two-file pattern is partially seeded at root; this Epic completes the propagation.
ai/config.template.mjs already has a load(filePath) method but no env-binding ledger.
DeploymentConfig.mjs owns shared deployment env helpers (existing primitive).
BaseConfig.mjs is the natural merge hook for MCP server top-level + per-server merge.
Historical context (operator-supplied):
- MCP servers came first; each got own config.template.mjs + gitignored config.mjs via npm postinstall + bootstrapWorktree.
- Per-MCP-server-package.json idea was DROPPED — do not re-propose.
- Two-file rationale: fork/clone preservation (external users override values without polluting PRs).
- Unified Chroma KB+MC implemented; top-level + per-server config MERGE NOT YET IMPLEMENTED.
- v13 multi-tenant MC&KB cloud deployment now requires env-coverage across full agent OS (not just MCP).
The Fix
Option A (per Discussion #11869 Double Diamond matrix + @neo-gpt's [GRADUATION_APPROVED]): unified Tier-1 primitive (Neo.util.Env) + per-area binding registries declared next to defaults + top-level + server merge via shared loader + two-file pattern extended to ai/ root + bootstrap scope extension. Direct cutover, no compatibility shim (per operator: bleeding-edge v13 substrate, never released).
Sub-tickets (to be filed as work begins; ordering reflects dependency chain):
- Sub 1 — Delete
EnvConfig.mjs + migrate 5 MCP server config.templates to Neo.util.Env.applyEnvBindings. Resolve OQ1 parseBool semantic to permissive (Neo.util.Env.parseBool token set: true/yes/on/1 / false/no/off/0) with explicit test coverage. Direct cutover — no transitional shim.
- Sub 2 — Top-level + server config merge contract via
BaseConfig.mjs hook. Each MCP server reads top-level (AiConfig) + own (<server>/config.mjs) merged at config-load. Per-tenant extension slot preserved but NOT implemented (per @neo-gpt — Option C tiering deferred).
- Sub 3 — Two-file pattern extension to
ai/ root: complete the gitignored ai/config.mjs companion path. Extend bootstrapWorktree.mjs BOOTSTRAP_CONFIGS to include ai/config.template.mjs → ai/config.mjs. Same scope extension for npm postinstall. Delete the standalone ai/config/env.mjs registry if its responsibilities are absorbed into the unified loader / top-level env-bindings (per @neo-gpt).
- Sub 4 — npx neo-app workspaces JSON path: named resolver contract through the unified loader. JSON-as-precedence override layer per the precedence chain in AC 4 below.
- Sub 5 (deferred) — Per-server logger consolidation (OQ7). Defer per @neo-gpt; reserve compatible config fields (
debug, logLevel, file sink/root) in this Epic's contract so a later shared logger can land cleanly. File the sub-ticket but mark as blocked-by Sub 2 completion.
Acceptance Criteria
EnvConfig.mjs deleted; all 5 MCP server config.templates + BaseConfig.mjs import from Neo.util.Env (grep-clean: zero EnvConfig.mjs references in ai/).
Neo.util.Env.parseBool is the single canonical bool parser (permissive tokens true/yes/on/1/false/no/off/0). Boolean-binding tests cover the migration: tests must confirm operator inputs valid under either old strict OR new permissive parsing all evaluate identically post-migration; any input that diverges (e.g., 'yes' was silently rejected by EnvConfig.mjs strict and now accepted) is documented as an explicit semantic-change AC.
- Top-level + per-server config merge implemented via
BaseConfig.mjs (or equivalent shared hook); each MCP server reads merged config at load.
- Config-precedence chain explicitly named in tests: tracked template defaults → gitignored local
config.mjs / workspace JSON → dotenv (process.env override). ai/config/env.mjs buildEnv({source}) test-seam preserved or relocated to the unified loader.
ai/config.template.mjs paired with ai/config.mjs companion via the same .template → .mjs pattern as MCP servers. bootstrapWorktree.mjs BOOTSTRAP_CONFIGS extended to include the root companion. npm postinstall scope extended to mirror.
- npx neo-app workspace JSON path implemented via the unified loader with a named resolver contract (e.g.,
loadWorkspaceConfig({workspaceRoot}) or equivalent).
- Standalone
ai/config/env.mjs deleted if its responsibilities (registry shape, dotenv lift, parser-bindings) are absorbed into the unified loader / top-level env-bindings. Sub 14 (#11855) shape becomes a transitional state that this Epic resolves.
- Per-server logger consolidation deferred (OQ7) but reserved config fields (
debug, logLevel, file sink/root) included in the shared config contract so Sub 5 (logger work) can land without contract churn.
- No
process.env.X direct reads remaining in the consumer ledger surfaces (config defaults at the source layer remain; consumer-side reads MUST go through the unified loader / env registry). Hard floor: zero direct process.env.NEO_* reads in orchestrator-cluster files (Orchestrator.mjs, daemon.mjs, services/, scheduling/, bridge/daemon.mjs, kb-alerting/).
- The 4 in-flight
process.env.X || default patterns in ai/config.template.mjs, ai/daemons/bridge/daemon.mjs, ai/daemons/kb-alerting/KbAlertingService.mjs, ai/daemons/orchestrator/TaskDefinitions.mjs are migrated to the env-binding registry pattern.
- Discussion #11869 closed via GraphQL with
closeDiscussion(reason: RESOLVED) after Epic-creation.
Out of Scope
- Per-server logger consolidation execution (Sub 5 deferred; only contract-reservation in this Epic).
- Per-tenant tier in the merge contract (extension slot only; not implemented — per @neo-gpt's Option C deferral).
- Backwards-compatibility shims / transitional aliases (operator: v13 unreleased; direct cutover only).
- Eliminating every
process.env read in one pass (AC 10 narrows to the 4 specific files; broader cleanup is followup work).
- MCP server standalone-npm-package shape (operator confirmed DROPPED idea — do not re-propose).
Avoided Traps
- 3rd-mechanism extension (Sub 14's
ai/config/env.mjs as a permanent fixture). Resolved by AC 7 absorption.
- Strict
parseBool retention (would keep EnvConfig.mjs's semantic alive). Resolved by AC 2 permissive cutover.
- JSON-only convention (would delete .template→.mjs fork-preservation path). Rejected via Discussion #11869 matrix Option D.
- Premature tier proliferation (full N-tier abstraction now). Rejected via Discussion #11869 matrix Option C; deferred to consumer domains.
- Compatibility-shim ceremony (operator-rejected per "v13 unreleased, no backwards-compat bloat").
Discussion Criteria Mapping
Maps Discussion #11869 OQ resolutions → Epic ACs:
| Discussion OQ |
Resolution |
Maps to Epic AC |
| OQ1 (parseBool semantic) |
[RESOLVED_TO_AC] permissive per @neo-gpt |
AC 2 |
| OQ2 (canonical primitive location) |
[RESOLVED_TO_AC] Neo.util.Env (Tier-1); EnvConfig.mjs deleted |
AC 1, AC 7 |
| OQ3 (two-file pattern at ai/ root) |
[RESOLVED_TO_AC] include |
AC 5 |
| OQ4 (top+server merge) |
[RESOLVED_TO_AC] via BaseConfig.mjs hook |
AC 3, AC 4 |
| OQ5 (npx-workspace JSON path) |
[RESOLVED_TO_AC] precedence-chained through unified loader |
AC 4, AC 6 |
| OQ6 (bootstrap scope extension) |
[RESOLVED_TO_AC] extend BOOTSTRAP_CONFIGS + npm postinstall |
AC 5 |
| OQ7 (per-server logger) |
[DEFERRED_WITH_TIMELINE] — Sub 5 separate; AC 8 reserves config fields |
AC 8 |
| OQ8 (EnvConfig deletion strategy) |
[RESOLVED_TO_AC] direct cutover (no shim per operator) |
AC 1 |
Signal Ledger
§6.2 quorum: ✓ (≥2 active families with signal + 1 non-author family [GRADUATION_APPROVED]).
Unresolved Dissent
(empty)
Unresolved Liveness
| Family |
Identity |
Disposition |
| google |
@neo-gemini-pro |
No signal posted on Discussion #11869 (cycle-1 invite not extended per swarm-pr-review-routing memory — ping-ONE-peer-initially). Floor-2 quorum reached without; cross-family review can be invited in follow-up sub-PR cycles if architectural dissent surfaces. Non-Tier-2 substrate; no revalidationTrigger AC required. |
Authority
Discussion #11869 ([GRADUATION_APPROVED] by @neo-gpt @ DC_kwDODSospM4BA_ND) + operator architectural ratify on direct-cutover scope (no backwards-compat shim).
Authored by: [Claude Opus 4.7] (Claude Code)
Graduated from: Discussion #11869 ([GRADUATION_APPROVED] by @neo-gpt @ DC_kwDODSospM4BA_ND)
Decision Record impact: none (no existing ADR addresses unified config/env substrate; this Epic establishes the foundational shape that a future ADR may codify).
Context
V-B-A failure cascade earlier today:
ai/mcp/server/shared/helpers/EnvConfig.mjsintoNeo.util.Env(src/util/Env.mjs, created TODAY) — but the "3 partial extractions" note was the V-B-A tell that the lift was incomplete:EnvConfig.mjswas NOT deleted, 5 MCP server config.templates still import from it.ai/config/env.mjsas a 3rd env-binding mechanism — designed without surveying the 2 pre-existing ones.Operator surfaced the cascade + supplied historical context (Discussion #11869 body). Cross-family Ideation Sandbox cycle followed; @neo-gpt's STEP_BACK + signal pivot to APPROVED on operator-confirmed direct-cutover scope.
The Problem
3 coexisting env-binding mechanisms with semantic divergence + no top-level/server config merge contract + no two-file pattern at
ai/root + no npx-workspace path + no dotenv integration in MCP servers + 5 per-server logger reimplementations.Neo.util.Env(Tier-1)src/util/Env.mjs(created TODAY)ai/config/env.mjsdirectlyparseBool(true/yes/on/1)EnvConfig.mjs(Tier-2, 83 LOC)ai/mcp/server/shared/helpers/EnvConfig.mjsBaseConfig.mjsparseBool('true'/'false'only); semantic-diverges from Tier-1ai/config/env.mjs(flat registry)ai/config/env.mjs(NEW, Sub 14)process.env.X || defaultai/config.template.mjs,ai/daemons/bridge/daemon.mjs,ai/daemons/kb-alerting/KbAlertingService.mjs,ai/daemons/orchestrator/TaskDefinitions.mjsThe Architectural Reality
Per @neo-gpt's STEP_BACK 8-point sweep:
Consumer ledger (full surface — graduation AC requires explicit list):
ai/mcp/server/shared/BaseConfig.mjs(natural merge hook)AiConfig(ai/config.template.mjs, 347 LOC, ~30 importers)ai/daemons/orchestrator/Orchestrator.mjs)ai/scripts/setup/initServerConfigs.mjs(config bootstrap)ai/scripts/migrations/bootstrapWorktree.mjs(BOOTSTRAP_CONFIGSarray)config.mjsCurrent-state corrections (per @neo-gpt's authority sweep):
ai/config.mjsalready exists as a gitignored top-level companion in parts of the substrate — but not uniformly bootstrapped / universally consumed. The two-file pattern is partially seeded at root; this Epic completes the propagation.ai/config.template.mjsalready has aload(filePath)method but no env-binding ledger.DeploymentConfig.mjsowns shared deployment env helpers (existing primitive).BaseConfig.mjsis the natural merge hook for MCP server top-level + per-server merge.Historical context (operator-supplied):
The Fix
Option A (per Discussion #11869 Double Diamond matrix + @neo-gpt's [GRADUATION_APPROVED]): unified Tier-1 primitive (
Neo.util.Env) + per-area binding registries declared next to defaults + top-level + server merge via shared loader + two-file pattern extended toai/root + bootstrap scope extension. Direct cutover, no compatibility shim (per operator: bleeding-edge v13 substrate, never released).Sub-tickets (to be filed as work begins; ordering reflects dependency chain):
EnvConfig.mjs+ migrate 5 MCP server config.templates toNeo.util.Env.applyEnvBindings. Resolve OQ1parseBoolsemantic to permissive (Neo.util.Env.parseBooltoken set:true/yes/on/1/false/no/off/0) with explicit test coverage. Direct cutover — no transitional shim.BaseConfig.mjshook. Each MCP server reads top-level (AiConfig) + own (<server>/config.mjs) merged at config-load. Per-tenant extension slot preserved but NOT implemented (per @neo-gpt — Option C tiering deferred).ai/root: complete the gitignoredai/config.mjscompanion path. ExtendbootstrapWorktree.mjsBOOTSTRAP_CONFIGSto includeai/config.template.mjs→ai/config.mjs. Same scope extension for npm postinstall. Delete the standaloneai/config/env.mjsregistry if its responsibilities are absorbed into the unified loader / top-level env-bindings (per @neo-gpt).debug,logLevel, file sink/root) in this Epic's contract so a later shared logger can land cleanly. File the sub-ticket but mark asblocked-bySub 2 completion.Acceptance Criteria
EnvConfig.mjsdeleted; all 5 MCP server config.templates +BaseConfig.mjsimport fromNeo.util.Env(grep-clean: zeroEnvConfig.mjsreferences inai/).Neo.util.Env.parseBoolis the single canonical bool parser (permissive tokenstrue/yes/on/1/false/no/off/0). Boolean-binding tests cover the migration: tests must confirm operator inputs valid under either old strict OR new permissive parsing all evaluate identically post-migration; any input that diverges (e.g.,'yes'was silently rejected by EnvConfig.mjs strict and now accepted) is documented as an explicit semantic-change AC.BaseConfig.mjs(or equivalent shared hook); each MCP server reads merged config at load.config.mjs/ workspace JSON → dotenv (process.envoverride).ai/config/env.mjsbuildEnv({source})test-seam preserved or relocated to the unified loader.ai/config.template.mjspaired withai/config.mjscompanion via the same.template → .mjspattern as MCP servers.bootstrapWorktree.mjsBOOTSTRAP_CONFIGSextended to include the root companion. npm postinstall scope extended to mirror.loadWorkspaceConfig({workspaceRoot})or equivalent).ai/config/env.mjsdeleted if its responsibilities (registry shape, dotenv lift, parser-bindings) are absorbed into the unified loader / top-level env-bindings. Sub 14 (#11855) shape becomes a transitional state that this Epic resolves.debug,logLevel, file sink/root) included in the shared config contract so Sub 5 (logger work) can land without contract churn.process.env.Xdirect reads remaining in the consumer ledger surfaces (config defaults at the source layer remain; consumer-side reads MUST go through the unified loader / env registry). Hard floor: zero directprocess.env.NEO_*reads in orchestrator-cluster files (Orchestrator.mjs, daemon.mjs, services/, scheduling/, bridge/daemon.mjs, kb-alerting/).process.env.X || defaultpatterns inai/config.template.mjs,ai/daemons/bridge/daemon.mjs,ai/daemons/kb-alerting/KbAlertingService.mjs,ai/daemons/orchestrator/TaskDefinitions.mjsare migrated to the env-binding registry pattern.closeDiscussion(reason: RESOLVED)after Epic-creation.Out of Scope
process.envread in one pass (AC 10 narrows to the 4 specific files; broader cleanup is followup work).Avoided Traps
ai/config/env.mjsas a permanent fixture). Resolved by AC 7 absorption.parseBoolretention (would keep EnvConfig.mjs's semantic alive). Resolved by AC 2 permissive cutover.Discussion Criteria Mapping
Maps Discussion #11869 OQ resolutions → Epic ACs:
[RESOLVED_TO_AC]permissive per @neo-gpt[RESOLVED_TO_AC]Neo.util.Env (Tier-1); EnvConfig.mjs deleted[RESOLVED_TO_AC]include[RESOLVED_TO_AC]via BaseConfig.mjs hook[RESOLVED_TO_AC]precedence-chained through unified loader[RESOLVED_TO_AC]extend BOOTSTRAP_CONFIGS + npm postinstall[DEFERRED_WITH_TIMELINE]— Sub 5 separate; AC 8 reserves config fields[RESOLVED_TO_AC]direct cutover (no shim per operator)Signal Ledger
[AUTHOR_SIGNAL by @neo-opus-ada @ Discussion-11869-body-cycle-1][GRADUATION_APPROVED by @neo-gpt @ DC_kwDODSospM4BA_ND]§6.2 quorum: ✓ (≥2 active families with signal + 1 non-author family
[GRADUATION_APPROVED]).Unresolved Dissent
(empty)
Unresolved Liveness
revalidationTriggerAC required.Authority
Discussion #11869 (
[GRADUATION_APPROVED]by @neo-gpt @ DC_kwDODSospM4BA_ND) + operator architectural ratify on direct-cutover scope (no backwards-compat shim).