LearnNewsExamplesServices
Frontmatter
id10859
titlePer-domain env-file split: replace monolithic .env with .env.<domain> substrate
stateClosed
labels
enhancementaiarchitecturemodel-experience
assigneesneo-opus-4-7
createdAtMay 7, 2026, 1:08 AM
updatedAtMay 9, 2026, 11:30 PM
githubUrlhttps://github.com/neomjs/neo/issues/10859
authorneo-opus-4-7
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 7, 2026, 1:22 AM

Per-domain env-file split: replace monolithic .env with .env.<domain> substrate

Closedenhancementaiarchitecturemodel-experience
neo-opus-4-7
neo-opus-4-7 commented on May 7, 2026, 1:08 AM

Context

The repo's monolithic .env aggregates credentials + configuration spanning multiple domains: Anthropic API key, OpenAI compatible endpoint, GitHub PAT, Chroma host/port, Memory Core flags, etc. Operators editing one domain's variable risk inadvertently overwriting adjacent variables (paste-into-wrong-line, partial-replace-not-isolated-replace, etc.).

This ticket is preventive operational substrate / MX hardening — it splits the monolithic .env into domain-scoped files so cross-domain edits become structurally isolated.

The Problem

Empirically, edits to one domain's variable can accidentally clobber another domain's variable in a monolithic .env. The blast radius scales with the number of variables in the file. With ~30+ env vars now in scope (GH_TOKEN, ANTHROPIC_API_KEY, NEO_OPENAI_COMPATIBLE_*, NEO_CHROMA_*, NEO_AUTH_*, ONEO_AUTH_*, GEMINI_API_KEY, etc.), any operator edit carries a real cross-contamination risk.

The Architectural Reality

  • dotenv package supports loading from explicit file paths.
  • Many config consumers currently expect process.env to be pre-populated (no per-call .env load).
  • A loader that reads multiple .env.<domain> files at process start can populate process.env identically to a single .env, transparent to consumers.
  • Domain-scoped files would be:
    • .env.github (GH_TOKEN, GH_TOKEN_NEO_*)
    • .env.anthropic (ANTHROPIC_API_KEY)
    • .env.openai (NEO_OPENAI_COMPATIBLE_*)
    • .env.gemini (GEMINI_API_KEY)
    • .env.chroma (NEO_CHROMA_HOST, NEO_CHROMA_PORT, NEO_CHROMA_UNIFIED)
    • .env.auth (NEO_AUTH_*, ONEO_AUTH_*)
    • .env.memory-core (NEO_MC_*, AUTO_*, REAL_TIME_MEMORY_PARSING, etc.)

The Fix

  1. Loader helper at process start: enumerate .env.<domain> files in repo root + load each into process.env (later files don't override earlier — first-wins, intentional).
  2. Migration: split current .env into the domain-scoped files. Existing .env retained as legacy fallback for one deprecation window, then removed.
  3. .gitignore update: ensure all .env.* files are gitignored (verify before commit).
  4. Operator runbook: documents which domain file to edit for which variable. Lives in learn/agentos/env-file-conventions.md.
  5. Bootstrap helper (buildScripts/bootstrapEnv.mjs): generates the domain files from a template (.env.example.<domain>) on first checkout, mirroring the existing bootstrapWorktree.mjs pattern.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
.env.<domain> files New env-loader helper Each domain file loaded into process.env at process start Missing domain file → that domain's vars absent (consumers handle defaults) learn/agentos/env-file-conventions.md (new) Unit + integration test: env-loader populates process.env from multiple files
Legacy .env Backward-compat shim Loaded if present, with deprecation WARN Removed after deprecation window (separate hard-cut ticket) Same Unit test: legacy-file-warns-but-loads
.gitignore Repo-level All .env.* files gitignored Templates .env.example.<domain> checked-in Same Verify in CI

Acceptance Criteria

  • Env-loader helper at process start reads .env.<domain> files
  • .gitignore excludes all .env.* (with !.env.example.* exception for templates)
  • Domain-scoped templates .env.example.<domain> committed for: github, anthropic, openai, gemini, chroma, auth, memory-core
  • Bootstrap helper generates domain files from templates on first checkout
  • Legacy .env continues to load with deprecation WARN (one-window backward-compat)
  • learn/agentos/env-file-conventions.md documenting which variable belongs to which domain
  • Unit + integration tests
  • Operator-side migration: split current monolithic .env content into domain files (operator's local action; doc only)

Out of Scope

  • Per-process domain-file selection (e.g., MC daemon loads only .env.memory-core + .env.chroma) — orthogonal optimization; v1 loads all
  • Encrypted env files — out of scope
  • Cross-machine env synchronization — out of scope; per-machine local files

Avoided Traps / Gold Standards Rejected

  • Single .env with section headers — comments don't prevent paste-into-wrong-line; need physical file boundaries
  • Symlinks from .env to .env.<domain> for backward-compat — fragile; explicit loader is cleaner
  • JSON config file replacing .env — different format paradigm; forks ecosystem tooling expectations; .env-shape preserved is the KISS choice

Related

  • #10858 (per-agent credential namespacing) — GH_TOKEN_NEO_OPUS_4_7 etc. would live in .env.github cleanly
  • Pairs with the broader operator-credentials hygiene story; per-agent + per-domain together close the cross-contamination surface

Origin Session ID: 8b31fd62-6a53-40b5-aae2-c5288f8ced09 Retrieval Hint: "per-domain env file split monolithic .env cross-contamination credential isolation"