---
id: 10103
title: 'Agent OS config ownership: Tier-1 deployment defaults'
state: CLOSED
labels:
  - epic
  - ai
  - architecture
assignees:
  - neo-gpt
createdAt: '2026-04-19T15:31:55Z'
updatedAt: '2026-06-07T17:19:47Z'
githubUrl: 'https://github.com/neomjs/neo/issues/10103'
author: tobiu
commentsCount: 7
parentIssue: null
subIssues:
  - '[x] 11963 Map Agent OS config defaults into Tier-1 AiConfig'
  - '[x] 11965 Wire provider routing and native Ollama runtime paths'
  - '[x] 11964 Prove cloud provider readiness for embeddings, chat, and dream'
subIssuesCompleted: 3
subIssuesTotal: 3
blockedBy: []
blocking: []
closedAt: '2026-05-25T22:22:44Z'
---
# Agent OS config ownership: Tier-1 deployment defaults

## Current Context

#10103 is reopened as the active epic for Agent OS config ownership. The original broad intent remains valid, but several earlier ticket statements are now stale after the 2026-05-25 merge train.

This issue is intentionally kept as the existing epic instead of creating another config-substrate ticket. The current goal is to finish the remaining Tier-1 configuration substrate cleanup needed for multi-user cloud deployment, while preserving the selective boundary: deployment-wide defaults live at Tier-1; server-local knobs stay local.

## Current Source Truth

Verified against `dev` after these merged PRs:

- #11967 / #11969 / #11988 moved the Tier-1/provider/auth/storage config substrate forward.
- #11966 / #11970 proved provider routing and cloud readiness paths.
- #11989 removed the stale `aiConfig.engines.neo` consumers from maintenance/diagnostics scripts.

Delivered and no longer open in this epic:

- `ai/BaseConfig.mjs` is now top-level and exported through `Neo.setupClass(BaseConfig)`.
- `ai/config.template.mjs` extends `BaseConfig` instead of carrying a separate clone/load/proxy implementation.
- `applyLegacyEnv()` is gone from the shared config base and active config templates.
- Env binding ledgers now use nested object shape, and `Neo.util.Env.applyEnvBindings()` rejects dotted binding keys.
- Active ignored config overlays have been refreshed from their templates after #11988/#11989.
- Stale graph-path consumers now use `aiConfig.storagePaths.graph`.

Still open after the same verification:

- Config templates still keep `defaultConfig` and/or `envBindings` as module-scope constants, then reference them from `static config`. The remaining shape should move config ownership into the Neo class substrate instead of using module-level ledgers copied into the singleton.
- `backupPath` is still Memory-Core-local while both `Memory_DatabaseService` and `KB_DatabaseService` default export paths to `aiConfig.backupPath`. That asymmetry was called out in the original #10103 comment and remains relevant.
- The final shape must preserve the current nested-env-binding contract and the Tier-1/server-local boundary.

## Contract Ledger Matrix

| Target Surface | Source of Authority | Current State | Required Behavior | Evidence |
|---|---|---|---|---|
| Shared config base | #11988 + current `ai/BaseConfig.mjs` | Top-level `Neo.ai.BaseConfig`, setupClass-backed, no legacy env hook | Keep as the single shared behavior base for Tier-1 and server configs | Import smoke + focused config unit tests |
| Tier-1 config class substrate | #10103 reopened current-state check | Extends `BaseConfig`, but `defaultConfig` / `envBindings` are module-scope constants | Move ownership into `Neo.ai.Config` class substrate while preserving defaults and env behavior | Tier-1 config unit tests + template import smoke |
| Server config class substrate | #10103 reopened current-state check | Server templates extend `BaseConfig`, but ledgers remain module-scope constants | Move server `defaultConfig` / `envBindings` ownership into each config class substrate | KB/MC/GitHub/GitLab/Neural config unit tests |
| Env binding topology | Operator clarification + #11988 | Nested env-binding objects; dotted keys rejected | Preserve nested binding topology; do not reintroduce dotted strings | `Env.spec.mjs` and representative config tests |
| `backupPath` shared default | #10103 comment 4283913925 + current services | MC config defines `backupPath`; KB service defaults to `aiConfig.backupPath` without owning the key | Promote shared backup default to Tier-1 or otherwise make MC/KB backup defaults symmetric without server-local duplication | MC + KB backup/default tests or focused smoke |
| Tier-1 / server-local boundary | Discussion #11961 + #10103 | Mostly delivered by prior PRs | Keep provider/auth/vector/unified-Chroma coordinates deployment-wide; keep collection names, listener ports, parser/scoring, batch knobs, and tenant-source config server-local | No moved-key regression in config tests |

## Acceptance Criteria

- [ ] Tier-1 `ai/config.template.mjs` owns its config ledgers inside the `Neo.ai.Config` class substrate, not as module-scope `defaultConfig` / `envBindings` constants.
- [ ] Server config templates own their config ledgers inside their `Config` class substrate, not as module-scope `defaultConfig` / `envBindings` constants.
- [ ] The nested env-binding contract remains intact and dotted env-binding keys remain rejected.
- [ ] The `backupPath` default is made symmetric for Memory Core and Knowledge Base without duplicating a cross-cutting deployment default in server-local config.
- [ ] Existing global/server-local boundaries are preserved.
- [ ] Focused config tests and syntax/import smoke pass.

## Out of Scope

- Per-tenant provider/auth variation.
- A monolithic all-server config catalog.
- Reintroducing backwards-compatibility hooks for v13-unreleased config shapes.
- Moving server-local collection names, MCP listener ports, parser/scoring knobs, batch limits, or tenant-source config to Tier-1.
- Reworking unrelated concept ontology behavior.

## Avoided Traps

- Do not treat the previous closeout comment as authoritative after later operator V-B-A. The current source still has residual substrate work.
- Do not file another duplicate config epic for this closure shape.
- Do not preserve module-scope ledgers for convenience if the point of the lane is native Neo class ownership.
- Do not regress the already-delivered nested env-binding contract.

## Related

- Discussion #11961
- #11988
- #11989
- #11967
- #11969
- #11966
- #11970

Handoff Retrieval Hint: "reopened #10103 BaseConfig setupClass module-scope defaultConfig envBindings backupPath Tier-1 Agent OS config ownership"

## Timeline

- 2026-04-19T15:31:56Z @tobiu added the `enhancement` label
- 2026-04-19T15:31:57Z @tobiu added the `ai` label
- 2026-04-19T15:31:57Z @tobiu added the `architecture` label
- 2026-04-19T15:32:11Z @tobiu cross-referenced by PR #10102
- 2026-04-19T15:39:36Z @tobiu cross-referenced by #10104
- 2026-04-19T16:26:28Z @tobiu referenced in commit `4df4b09` - "feat(ai): config-lift ConceptDiscoveryService tuning knobs (#10036)

Addresses tobi's polish catch on PR #10105: `DEFAULT_PR_SCAN_LIMIT = 20` and
`MIN_SOURCE_LENGTH = 200` were file-local magic numbers. Operator-tuning knobs
belong in config, not as consts buried in the service. Same discipline as
#10086 (`guideGapWeightThreshold` config-lift).

## Scope

- `config.template.mjs` + local `config.mjs`: new `conceptDiscovery` nested
  object grouping `prScanLimit` (default 20) + `minSourceLength` (default 200).
  Env overrides via `NEO_CONCEPT_DISCOVERY_PR_SCAN_LIMIT` +
  `NEO_CONCEPT_DISCOVERY_MIN_SOURCE_LENGTH`. Nested grouping matches existing
  `openAiCompatible` / `ollama` convention for service-scoped configs.
- `ConceptDiscoveryService.mjs`: dropped the two constants. Reads from
  `aiConfig.data.conceptDiscovery?.prScanLimit ?? 20` +
  `aiConfig.data.conceptDiscovery?.minSourceLength ?? 200` at call time (live
  reads, not module-load capture — enables runtime tuning + test overrides
  without reimport). Instance-level `prScanLimit` field retained for test
  overrides (falls through to aiConfig when null).
- Parity: local `config.mjs` mirrors the template addition (gitignored per
  `.gitignore`; not committed — only template goes into git).

## Out of scope (tracked for #10103)

Per `feedback_config_service_boundary.md` + #10103: these keys belong on the
SDK-layer config (ConceptDiscoveryService is a daemon service, not a memory-
core concern). Placed in memory-core config as the pragmatic destination today
— same debt as `guideGapWeightThreshold`. Migration is #10103's scope.

## Tests (49/49 stable × 3 runs)

New test: `aiConfig.data.conceptDiscovery.minSourceLength` override changes
extraction behavior. Proves the config is read at call time (raise threshold
above body length → LLM skipped; restore default → LLM invoked).

Origin Session ID: 1db25bbe-f39d-4dcd-bb0e-bc125ce91326"
- 2026-04-19T16:26:32Z @tobiu cross-referenced by PR #10105
- 2026-04-19T16:30:19Z @tobiu referenced in commit `a5802d7` - "feat(ai): ConceptDiscoveryService + validated flag for mined candidates (#10036) (#10105)

* feat(ai): ConceptDiscoveryService (LLM-based) + validated flag (#10036)

Lands deliverables 1, 2, and 4 of #10036 in one cohesive slice per the intake
discussion.

## Approach

LLM-driven concept extraction via `OpenAiCompatible.generate` (gemma4 or
configured provider — same substrate used by `SemanticGraphExtractor` and
`GoldenPathSynthesizer`). Two high-signal corpora:

- **Epics** — issue markdown files labeled `epic` in `resources/content/issues/`.
  Epic bodies are curated distilled architecture.
- **Pull requests** — `resources/content/pulls/*.md`, including their
  `## Comments` section. PR review comments contain `[ARCH_ALIGNMENT]` /
  `[RETROSPECTIVE]` / `[KB_GAP]` / "Gold Standards" / "Traps Avoided" prose —
  the exact architectural discourse where new vocabulary emerges. Sorted
  descending by PR number (freshest discourse first), capped at
  `DEFAULT_PR_SCAN_LIMIT = 20` per cycle to bound LLM cost.

The system prompt encodes the Teaching Test + explicit anti-patterns (class
names, file paths, framework names without architectural weight, routine
ticket vocabulary). Output is strict JSON parsed via `Json.extract` for
fence-tolerance. Per-source malformed output is logged and skipped — never
fatal to the cycle.

## Schema (Deliverable 4)

- `validated: boolean` field on concept nodes. Default `undefined` (legacy)
  treated as validated (no data migration). Mined candidates carry
  `validated: false`, tier 3, default weight → silenced in sandman_handoff.md
  via two independent mechanisms (weight gate + validated gate).
- `ConceptIngestor.computePayloadHash` includes validated, so flipping
  curator-review state triggers re-upsert.
- `ConceptIngestor` upsert-properties include validated, propagating to graph.
- `GapInferenceEngine.inferConceptGraphGaps` skips `validated === false`
  concepts regardless of weight — belt-and-suspenders alongside the weight
  gate. All three signals (GUIDE_GAP / EXAMPLE_GAP / ORPHAN_CONCEPT) respect
  the gate.
- Curator promotion = edit nodes.jsonl: flip `validated: true` + bump tier
  and/or weight. Git diff is the review surface.

## Tests (48/48 stable × 5 runs)

- `ConceptDiscoveryService.spec.mjs` (6 tests): epic-label filtering,
  PR cap + recency ordering, ConceptService dedupe (by id + alias),
  malformed-LLM tolerance, MIN_SOURCE_LENGTH short-circuit, end-to-end
  runDiscoveryCycle + JSONL append. Mocks `OpenAiCompatible.prototype.generate`
  with queued responses per call. Symmetric afterEach per
  `feedback_symmetric_spec_cleanup.md`.
- `DreamService.spec.mjs` new test: `validated: false` silences all three
  concept-graph signals even with weight 1.3 > 0.8 threshold.

## Deferred (out of this PR)

- **Deliverable 3** (KB-assisted classification) — blocked on KB MCP timeout
  per ticket body.
- **Deliverable 5** (glossary gap report) — pure reporting tool; file when
  ontology matures.
- **REM-cycle integration** — manual invocation via
  `DreamService.runConceptDiscovery()` for now. Per-cycle LLM cost would
  flood nodes.jsonl; deliberate to let curator/operator set cadence.
- **Session-summary mining** — epics + PRs are higher signal; add
  session-based mining as a follow-up if epic/PR coverage proves insufficient.

Origin Session ID: 1db25bbe-f39d-4dcd-bb0e-bc125ce91326

* feat(ai): config-lift ConceptDiscoveryService tuning knobs (#10036)

Addresses tobi's polish catch on PR #10105: `DEFAULT_PR_SCAN_LIMIT = 20` and
`MIN_SOURCE_LENGTH = 200` were file-local magic numbers. Operator-tuning knobs
belong in config, not as consts buried in the service. Same discipline as
#10086 (`guideGapWeightThreshold` config-lift).

## Scope

- `config.template.mjs` + local `config.mjs`: new `conceptDiscovery` nested
  object grouping `prScanLimit` (default 20) + `minSourceLength` (default 200).
  Env overrides via `NEO_CONCEPT_DISCOVERY_PR_SCAN_LIMIT` +
  `NEO_CONCEPT_DISCOVERY_MIN_SOURCE_LENGTH`. Nested grouping matches existing
  `openAiCompatible` / `ollama` convention for service-scoped configs.
- `ConceptDiscoveryService.mjs`: dropped the two constants. Reads from
  `aiConfig.data.conceptDiscovery?.prScanLimit ?? 20` +
  `aiConfig.data.conceptDiscovery?.minSourceLength ?? 200` at call time (live
  reads, not module-load capture — enables runtime tuning + test overrides
  without reimport). Instance-level `prScanLimit` field retained for test
  overrides (falls through to aiConfig when null).
- Parity: local `config.mjs` mirrors the template addition (gitignored per
  `.gitignore`; not committed — only template goes into git).

## Out of scope (tracked for #10103)

Per `feedback_config_service_boundary.md` + #10103: these keys belong on the
SDK-layer config (ConceptDiscoveryService is a daemon service, not a memory-
core concern). Placed in memory-core config as the pragmatic destination today
— same debt as `guideGapWeightThreshold`. Migration is #10103's scope.

## Tests (49/49 stable × 3 runs)

New test: `aiConfig.data.conceptDiscovery.minSourceLength` override changes
extraction behavior. Proves the config is read at call time (raise threshold
above body length → LLM skipped; restore default → LLM invoked).

Origin Session ID: 1db25bbe-f39d-4dcd-bb0e-bc125ce91326"
- 2026-04-19T17:53:00Z @tobiu cross-referenced by #10030
- 2026-04-19T23:40:14Z @tobiu cross-referenced by PR #10116
### @tobiu - 2026-04-20T20:04:04Z

**Input from Claude Opus 4.7 (Claude Code):**

> ❋ ## Additional migration candidate: `backupPath`
> 
> Surfaced during #10132 / #10133 cleanup review on 2026-04-20.
> 
> **Current state:**
> - `ai/mcp/server/memory-core/config.mjs:205` + `.../config.template.mjs:272` define `backupPath: path.resolve(cwd, '.neo-ai-data/backups')`
> - `ai/mcp/server/knowledge-base/config.mjs` + `...template.mjs` — **no equivalent entry**
> 
> **Consumers:**
> - `Memory_DatabaseService#exportDatabase` defaults `backupPath` to `aiConfig.backupPath` (MC config — works today)
> - `KB_DatabaseService#exportDatabase` defaults `backupPath` to `aiConfig.backupPath` (KB config — **undefined**; would throw at `fs.ensureDir(undefined)` if called without an explicit arg)
> - `buildScripts/ai/backup.mjs` orchestrator — always passes `backupPath` explicitly (one bundle subfolder per subsystem), so the KB default path is not exercised in practice today
> 
> **Why this belongs in #10103 rather than a new ticket:**
> - `backupPath` is a canonical cross-cutting filesystem root consumed by an operator script + two peer services — fits the "cross-cutting daemon/service config" profile in this ticket's Problem section
> - Fixing it in isolation (e.g. by adding `backupPath` to KB config too) doubles the debt rather than resolving it — the asymmetry just moves from "MC has it, KB doesn't" to "MC has one copy, KB has an identical copy"
> - When the SDK-layer config file lands per this ticket's Fix section, `backupPath` should migrate alongside the other cross-cutting keys (`guideGapWeightThreshold`, `decayFactor`, `autoDream`, etc.) — both MC and KB services then pull from the same SDK-level default
> 
> **Latent fragility note:** the KB default is currently `undefined` — safe only because `backup.mjs` always passes the arg. If a future caller invokes `KB_DatabaseService.manageDatabaseBackup({action:'export'})` without `backupPath`, it breaks at `fs.ensureDir(undefined)`. Migration to SDK-layer config resolves this without needing a per-server patch.
> 
> **Suggested addition to this ticket's Acceptance Criteria list:**
> - [ ] `backupPath` migrated to SDK-layer config; both `Memory_DatabaseService` and `KB_DatabaseService` dispatchers read from the shared SDK-level default; MC + KB server configs drop their local `backupPath` entries
> 
> Origin Session ID: 5a521819-dc75-4549-888e-fcea818d0401
> 

- 2026-05-01T10:37:58Z @neo-opus-ada cross-referenced by #10569
- 2026-05-01T10:42:28Z @neo-opus-ada cross-referenced by PR #10570
- 2026-05-01T10:46:50Z @neo-opus-ada cross-referenced by #10469
- 2026-05-01T16:01:42Z @neo-opus-ada cross-referenced by #10586
- 2026-05-01T16:03:15Z @neo-gpt cross-referenced by #10587
- 2026-05-01T16:09:24Z @neo-opus-ada cross-referenced by PR #10588
- 2026-05-01T16:20:53Z @neo-gpt cross-referenced by PR #10590
- 2026-05-01T18:02:21Z @neo-opus-ada cross-referenced by #10591
- 2026-05-01T21:35:35Z @neo-opus-ada cross-referenced by PR #10603
- 2026-05-03T21:47:54Z @neo-opus-ada cross-referenced by #10650
- 2026-05-06T11:40:41Z @neo-opus-ada cross-referenced by #10815
- 2026-05-08T19:38:19Z @neo-opus-ada cross-referenced by #10986
- 2026-05-08T20:48:45Z @neo-opus-ada cross-referenced by #10991
- 2026-05-08T21:02:09Z @neo-opus-ada cross-referenced by #10993
- 2026-05-08T21:06:43Z @neo-opus-ada cross-referenced by #10994
- 2026-05-08T21:17:50Z @neo-opus-ada cross-referenced by #10996
- 2026-05-09T10:20:24Z @neo-gpt cross-referenced by PR #11001
### @neo-gpt - 2026-05-11T18:39:02Z

**Input from GPT-5.5 (Codex):**

> ● ## Ticket Intake Gate — needs-contract-alignment
> 
> I started intake for #10103 after live lane coordination showed no collision. I am **not assigning, branching, or coding** yet because the Contract Completeness Gate blocks implementation.
> 
> ### Intake Checks Completed
> 
> - **Live issue state:** open, unassigned; labels include `enhancement`, `ai`, `architecture`.
> - **Parent/epic gate:** #10030 is the stated indirect parent and is `epic`-labeled. Prior `epic-review` by @neo-gpt exists: https://github.com/neomjs/neo/issues/10030#issuecomment-4361619448
> - **Duplicate sweep:** exact searches for `"SDK-layer config"`, `"daemon/service-layer config"`, and `"backupPath" "SDK-layer config"` found #10103 as the only direct open target. Related M6 service-boundary tickets (#10991/#10993/#10994/#10996/#10986) are closed and orthogonal.
> - **Premise spot-check:** direct grep confirms the ticket's concern is still live: daemon/service-facing keys such as `autoDream`, `autoGoldenPath`, `handoffFilePath`, `decayFactor`, `guideGapWeightThreshold`, `modelProvider`, and `backupPath` still sit in `ai/mcp/server/memory-core/config.template.mjs` / `config.mjs`, while consumers span `ai/daemons/**`, `ai/services/memory-core/**`, `ai/services/knowledge-base/DatabaseService.mjs`, `buildScripts/ai/**`, and tests.
> - **Memory/Core context:** Memory summaries surface the originating #10102/#10086 service-boundary lesson (`feedback_config_service_boundary.md`) and the later backupPath comment. Raw-memory search did not surface a better active implementation trail. KB health is green, but the specific KB query failed with an embedding model load cancellation, so I did not rely on KB synthesis for this gate.
> 
> ### Blocking Gate
> 
> This ticket introduces/modifies consumed configuration surfaces:
> 
> - a new SDK-level config module (`ai/config.mjs` or `ai/services.config.mjs` — still undecided),
> - env-var compatibility for existing operator contracts,
> - MCP server config/template boundaries,
> - daemon/service imports,
> - `bootstrapWorktree.mjs` config-copy behavior,
> - backup/default-path behavior shared by Memory Core and Knowledge Base.
> 
> Per `learn/agentos/contract-ledger.md`, that requires a **Contract Ledger** before implementation. The current ticket body has good prose and ACs, but no ledger matrix defining the exact contract rows.
> 
> ### Required Body Update Before Pickup
> 
> Please add a Contract Ledger matrix with at least these rows:
> 
> | Target Surface | Source of Authority | Proposed Behavior | Fallback / Edge Case | Docs | Evidence |
> |---|---|---|---|---|---|
> | SDK-level config module path/name | #10103 + M6 SDK-boundary direction | Chosen module exports default config/proxy shape for cross-cutting Agent OS config | If custom config file missing/stale, define fallback and compatibility behavior | JSDoc + `learn/agentos/` boundary doc | focused config smoke test |
> | Env-var compatibility | Existing operator env contracts | Existing env var names continue to work during migration | Deprecated legacy names warn or remain readable for one window where applicable | config JSDoc / docs | tests for representative migrated key |
> | Memory Core MCP config boundary | #10103 problem statement | MC config retains MC-owned transport/storage/auth/summarization fields only | Backward-compatible reads for migrated keys if needed, or explicit breaking-change note | config template comments | targeted unit / import tests |
> | Cross-cutting daemon/service keys | current daemon/service consumers | keys like `guideGapWeightThreshold`, `decayFactor`, `autoDream`, `autoGoldenPath`, `handoffFilePath`, `modelProvider` move to SDK config | define precedence if both old and new locations contain a value | service JSDoc + boundary doc | consumer smoke test |
> | `backupPath` shared default | #10103 comment 4283913925 | Memory Core and Knowledge Base backup defaults read the shared SDK config | caller-supplied `backupPath` still overrides default | DatabaseService JSDoc | MC + KB backup default tests or explicit scoped smoke |
> | `bootstrapWorktree.mjs` handling | worktree bootstrap contract | copies/syncs the new local config file if it is gitignored/local | no-op if tracked config path chosen; explain explicitly | script JSDoc | focused bootstrap test/update |
> 
> ### Structural Pre-Flight Note
> 
> Because #10103 prescribes a new `.mjs` config surface, `structural-pre-flight` will also need to run before implementation. `ArchitectureOverview.md` currently lists `ai/services.mjs`, `ai/services/<server>/`, and `ai/mcp/server/<server>/config`, but not a root-level `ai/config.mjs` role. The ticket body should either pick the destination and justify it, or explicitly leave the destination open for structural pre-flight to resolve.
> 
> **Intake verdict:** `needs-contract-alignment`. The goal appears valid and high-ROI, but implementation is blocked until the contract matrix is present.

- 2026-05-14T23:52:40Z @neo-opus-ada cross-referenced by #11383
### @neo-gpt - 2026-05-25T08:16:19Z

## Current-state V-B-A update — ticket shape stale, premise still live

Authored by @neo-gpt during the 2026-05-25 cloud-deployment config sweep.

I re-checked current `origin/dev` source and related tickets/discussions before treating this as an implementation lane. Verdict: the architectural concern is still live, but this ticket cannot be implemented as written without a current-state rewrite / Contract Ledger update.

### Evidence

- `ai/config.template.mjs` now exists as Tier-1 config substrate, so the original prescription to create `ai/config.mjs` / `ai/services.config.mjs` is stale.
- `ai/mcp/server/memory-core/config.template.mjs` still owns cross-Agent-OS provider and storage values: `modelProvider`, `embeddingProvider`, `openAiCompatible`, `ollama`, `vectorDimension`, `engines.chroma`, backup paths, and daemon/service-facing keys.
- `ai/mcp/server/knowledge-base/config.template.mjs` still owns its own Chroma host/port surface while Memory Core uses `engines.chroma`; unified Chroma is runtime reality but not fully represented as Tier-1 config reality.
- `ai/daemons/orchestrator/TaskDefinitions.mjs` / orchestrator config only model orchestrator scheduling + MLX process supervision. Provider/storage config reaches the cloud stack through per-service env forwarding, not through a first-class top-level Agent OS config contract.
- Cloud deployment docs and compose route the optional Ollama container via OpenAI-compatible env (`NEO_MODEL_PROVIDER=openAiCompatible`, `NEO_OPENAI_COMPATIBLE_HOST=http://local-model:11434`). Native `ollama` is advertised in config/health surfaces, but runtime generation/embedding/graph paths are not consistently provider-routed.
- Current integration tests prove container health + OpenAI-compatible embedding metadata. They do not prove `/v1/chat/completions` or graph-processing readiness for the cloud provider path.

### Related-ticket reconciliation

- #10822 is the original broad config-substrate epic and is closed. Its body expected Tier-1 provider/storage globals, but current source still shows residual ownership split.
- #11871 is intentionally narrow: env primitive dedup + shared MCP logger. It explicitly does not revive the broad cycle-1 “single config source” framing.
- #10270 is narrow Chroma misconfig observability, not ownership migration.
- #11734 delivered the optional local-model profile, but it does not solve provider config ownership or native `ollama` routing.

### Required rewrite before pickup

This issue should be refreshed from “create SDK-layer config file” to “consolidate Agent OS provider + storage config ownership around existing Tier-1 `ai/config.template.mjs` while preserving per-server deployment composition.” Suggested Contract Ledger rows:

1. Tier-1 provider config: `modelProvider`, `embeddingProvider`, `openAiCompatible`, `ollama`, `vectorDimension`.
2. Unified Chroma config: shared host/port/topology plus clear boundary for per-server collection names.
3. Runtime provider routing: decide whether native `ollama` is supported or explicitly retired from advertised selectors; graph services must not hardwire OpenAI-compatible if config says otherwise.
4. Cloud deployment env parity: compose/docs should reflect the selected provider contract and avoid implying unsupported native routes.
5. Integration evidence: mock both `/v1/embeddings` and `/v1/chat/completions`, then prove summary/dream/golden-path can use the cloud provider path.

Until that rewrite lands, implementation should stay blocked as `needs-contract-alignment`, not assigned straight to code.

- 2026-05-25T09:44:49Z @neo-gpt removed the `enhancement` label
- 2026-05-25T09:44:49Z @neo-gpt changed title from **[enhancement] SDK-layer config file — migrate daemon/service-layer keys out of memory-core/config.template.mjs** to **Agent OS config ownership: Tier-1 deployment defaults**
- 2026-05-25T09:44:49Z @neo-gpt added the `epic` label
- 2026-05-25T09:45:42Z @neo-gpt cross-referenced by #11963
- 2026-05-25T09:45:44Z @neo-gpt cross-referenced by #11964
- 2026-05-25T09:45:44Z @neo-gpt cross-referenced by #11965
- 2026-05-25T09:46:10Z @neo-gpt added sub-issue #11965
- 2026-05-25T09:46:10Z @neo-gpt added sub-issue #11963
- 2026-05-25T09:46:11Z @neo-gpt added sub-issue #11964
### @neo-gpt - 2026-05-25T09:50:29Z

## Epic Review by @neo-gpt

### Stage 1 — Roadmap Fit: Pass

This epic fits the current v13 cloud-deployment direction. The live Discussion #11961, Memory Core mining, and duplicate sweep all point to the same shape: #10103 is the right existing target, and the cloud trial makes provider/auth/storage config ownership high-ROI now. Related older epics (#10822, #10804) are important context, but they do not make this rewrite redundant because current source still has residual provider/storage ownership split.

### Stage 2 — Approach Elegance: Pass

The approach is selective Tier-1 ownership, not a central all-server config catalog. It reuses existing `ai/config.template.mjs`, Env parser patterns, KB/MC server config overlays, native provider classes, and cloud compose profiles. The Discussion-origin Double Diamond is present in #11961 and rejected the two main traps: per-server-only documentation and monolithic config.

### Stage 2.5 — Source Discussion Mapping: Pass

#10103 now carries `## Signal Ledger`, `## Unresolved Dissent`, `## Unresolved Liveness`, and `## Discussion Criteria Mapping`. The mapping covers OQ1-OQ8 from #11961 and preserves the Google-family liveness gap rather than converting no-signal into consent.

### Stage 3 — Sub-Structure Coherence: Pass

The three subissues collectively close the parent:

| Parent AC | Required evidence | Owning sub(s) | Delivered PR(s) | Achieved evidence | Residual state |
|---|---|---|---|---|---|
| Tier-1 config ownership ledger + KB/MC overlays | L2 | #11963 | pending | pending | pending |
| Runtime provider routing + native Ollama chat/embedding path | L2 | #11965 | pending | pending | pending |
| Cloud docs + embeddings/chat/dream readiness | L2/L3 where CI can only mock provider endpoints | #11964 | pending | pending | pending |
| Preserve server-local knobs and avoid monolithic catalog | L1/L2 | #11963, #11964 | pending | pending | pending |

No missing phase surfaced. The split is file-group/subsystem based rather than per-file: config mapping, runtime provider routing, and cloud docs/readiness.

### Stage 4 — Prescription Layer: Pass

- #11963 is correctly scoped to Tier-1 config plus KB/MC overlay mapping.
- #11965 is correctly scoped to runtime provider dispatch and native Ollama capability.
- #11964 is correctly scoped to docs and readiness evidence, and it depends on not over-claiming runtime support before #11965 proves it.

The service boundaries are explicit: collection names/listener ports/parser knobs stay server-local; provider/auth/storage deployment defaults become Tier-1.

### Stage 5 — Avoided Traps Completeness: Pass

The epic names the important rejected paths: monolithic config, per-server-only docs, retiring native Ollama, and treating session summarization as sufficient graph readiness. That is enough to start sub work; additional traps can be documented in the sub PR bodies if implementation exposes more.

### Verdict

Greenlight. Proceed with subissue pickup. My first lane is #11963.

Session ID: current request-bound Memory Core session (Codex Desktop; session id not exposed in this harness response).


- 2026-05-25T09:59:13Z @neo-opus-ada cross-referenced by PR #11966
- 2026-05-25T10:00:19Z @neo-gpt cross-referenced by PR #11967
- 2026-05-25T10:45:51Z @neo-opus-ada cross-referenced by PR #11962
- 2026-05-25T11:21:17Z @tobiu referenced in commit `58d352b` - "feat(memory-core): native Ollama runtime dispatch + provider.embed() method (#11965 Sub-2) (#11966)

* feat(ai/provider): add native Ollama embed() method (#11965 Sub-2 stage 1)

Sub-2 of #10103 (graduated from #11961). Stage 1: add embed() method to
Neo.ai.provider.Ollama using Ollama's native /api/embed endpoint.

V-B-A confirmed (#11961 OQ2 disposition): native Ollama is ACTIVE wire-up
target. Chat (generate/stream) was already implemented; embed was missing.

Method shape:
- Accepts single string OR array-of-strings for input (Ollama native API)
- Returns {embeddings: Number[][], raw: Object} — always 2D array for
  caller uniformity even with single-string input
- Uses configured embeddingModel; falls back to modelName chat model if
  embeddingModel unset (Ollama supports embeddings from many chat models
  but dedicated embedding models like nomic-embed-text are typical)
- Same native http(s).request shape as generate() (1h timeout, no fetch)

Config slot added:
- embeddingModel: null (dedicated embedding model, falls back to modelName)

Next stages:
- Stage 2: wire case 'ollama' in TextEmbeddingService runtime dispatch
- Stage 3: wire case 'ollama' in SessionService runtime dispatch
- Stage 4: observational branch cleanup (HealthService + GoldenPathSynthesizer)

Refs #11965 (Sub-2 native Ollama wire-up)
Refs #11961 (graduated Discussion)

Co-Authored-By: Claude Opus 4.7 <neo-opus-4-7@neomjs.com>

* feat(memory-core): wire native Ollama dispatch in TextEmbeddingService (#11965 Sub-2 stage 2)

Stage 2 of #11965 (Sub-2 of #10103). Wires `explicitProvider === 'ollama'`
branches in TextEmbeddingService.embedText + embedTexts to dispatch to the
native Neo.ai.provider.Ollama instance (Stage 1 added its embed() method).

Implementation:
- New private `#getOllamaProvider()` lazy-init + cache on the singleton's
  reactive `ollamaProvider_` config slot. Reads host/model/embeddingModel
  from `aiConfig.ollama` block. Tests inject a fake via direct singleton
  property assignment to bypass lazy-init.
- embedText('text', 'ollama') → provider.embed(text); returns embeddings[0]
  (single-text variant projects from Ollama's always-2D response).
- embedTexts(['t1', 't2'], 'ollama') → provider.embed([...]); returns
  embeddings array directly (batch variant matches Ollama's array-of-arrays
  shape).

5 new tests:
1. embedText dispatches to Ollama when explicitProvider='ollama'
2. embedTexts dispatches batch to Ollama when explicitProvider='ollama'
3. embedText returns undefined when Ollama returns empty embeddings array
4. embedText with explicitProvider='openAiCompatible' does NOT dispatch to Ollama
5. embedText with explicitProvider='gemini' does NOT dispatch to Ollama

7/7 PASS (2 existing + 5 new).

Next stages:
- Stage 3: SessionService chat dispatch (case 'ollama')
- Stage 4: observational branch cleanup (HealthService + GoldenPathSynthesizer)

Refs #11965 (Sub-2 native Ollama wire-up)

Co-Authored-By: Claude Opus 4.7 <neo-opus-4-7@neomjs.com>

* feat(memory-core): wire native Ollama chat dispatch in SessionService (#11965 Sub-2 stage 3)

Stage 3 of #11965. Wires modelProvider=ollama branch in SessionService.construct()
to instantiate Neo.ai.provider.Ollama and expose the Gemini-shaped
`this.model.generateContent` shim for downstream consumers
(summarizeSession, invokeWithGuardrail).

Implementation:
- New `else if (aiConfig.modelProvider === 'ollama')` branch in construct()
  between openAiCompatible and gemini paths
- Creates Neo.ai.provider.Ollama instance from aiConfig.ollama.{host, model,
  embeddingModel} with reasonable defaults (host: 127.0.0.1:11434, model: gemma4)
- Per-request config refresh matches openAiCompatible-path semantic: runtime
  config mutation by tests / embedded operators still applies to subsequent
  invocations
- Wraps provider.generate() in Gemini-compatible response envelope:
  `{response: {text: () => content}}`

Additional update:
- consumerModel naming logic at line 472 extended to include ollama branch
  (returns aiConfig.ollama.model for guardrail/log surfaces); pure ternary
  expansion, no semantic shift

Testing note:
- TextEmbeddingService dispatch (Stage 2) is unit-tested with injected
  provider seam (7/7 PASS)
- SessionService construct() dispatch is structurally symmetric to Stage 2
  but uses singleton-construct lifecycle that resists clean unit test injection
  in the existing spec env-setup. Full integration coverage of this path
  belongs to Sub-3 (#11964 cloud readiness tests for embeddings/chat/dream)
  per the ticket scope — Sub-2 owns the wiring; Sub-3 owns the end-to-end
  readiness proof.

Next stage:
- Stage 4: observational branch acknowledgment (HealthService +
  GoldenPathSynthesizer case 'ollama' branches transition from
  "vestigial half-support" to "active observational" — no code change
  required; existing branches were always correct, what was missing was
  the runtime dispatch this PR adds)

Refs #11965 (Sub-2 native Ollama wire-up)

Co-Authored-By: Claude Opus 4.7 <neo-opus-4-7@neomjs.com>

* fix(memory-core): explicit unsupported-provider rejection + testable buildChatModel (#11965 cycle-2)

* feat(memory-core): graph-provider dispatch + #11965 AC5 close (#11965 cycle-3)

Resolves @neo-gpt cycle-2 follow-up review CHANGES_REQUESTED on PR #11966 —
closes the graph-mutator provider-reachability blocker (#11965 AC5).

Per the #11965 Contract Ledger row "Dream/graph-mutator readiness gate" and
the operator client-need signal that flipped OQ2 disposition during
Discussion #11961 graduation (cloud-deployment clients prefer Ollama; no Mac
OS in cloud rules out MLX), graph-generation services must honor the active
`modelProvider` instead of hardwiring `OpenAiCompatible`.

Before: `SemanticGraphExtractor.mjs` (2 callsites), `GoldenPathSynthesizer.mjs`
(1 callsite), and `TopologyInferenceEngine.mjs` (1 callsite) all directly
instantiated `Neo.create(OpenAiCompatible, {...})`. Cloud deployments
configured for native Ollama would still see graph generation hit
OpenAI-compatible endpoints — speculative-support pattern.

After: shared `buildGraphProvider({modelProvider, ollamaConfig,
openAiCompatibleConfig, ...})` helper in `ai/services/graph/providerDispatch.mjs`
dispatches based on `aiConfig.modelProvider`. Pure function with injectable
provider factories. Throws explicitly for unsupported `modelProvider` (no
silent fallback). All 4 callsites migrated.

`SemanticGraphExtractor.consumerModel` (used for `invokeWithGuardrail`
telemetry) now reflects the active modelProvider so friction-aggregation logs
attribute work to the right provider family.

Gemini graph dispatch is intentionally out of Sub-2 scope (different API
shape; deferred to Sub-3 / follow-up). The default
`modelProvider: 'gemini'` in Memory Core's config is fine for chat
(`SessionService.buildChatModel` returns a Gemini-shaped envelope), but
operators wanting graph generation must currently configure
`modelProvider: 'ollama'` or `'openAiCompatible'`. Tests opt the legacy
fixture into the openAiCompatible path explicitly.

Test evidence: 25/25 PASS across the affected memory-core + graph specs.
7 new tests in `providerDispatch.spec.mjs` cover selector boundaries +
factory pass-through + envelope shape per provider.

---------

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-25T11:43:52Z @neo-gpt cross-referenced by PR #11970
- 2026-05-25T11:55:37Z @neo-gpt cross-referenced by PR #11969
### @neo-gpt - 2026-05-25T12:41:40Z

## Epic Resolution Review

**Reviewer:** @neo-gpt
**Started:** 2026-05-25T12:41:40Z
**Completed:** 2026-05-25T12:42:48Z
**Verdict:** RECOMMEND_CLOSE_COMPLETED

### Matrix

| Parent AC | Required evidence | Owning sub(s) | Delivered PR(s) | Achieved evidence | Residual state |
|---|---|---|---|---|---|
| Tier-1 config ownership ledger plus KB/MC overlays | L1/L2 | #11963 | #11967, corrective #11969 | #11967 maps Tier-1 provider/auth/storage defaults and KB/MC overlays; #11969 fixes runtime imports to use operator overlay `ai/config.mjs`. Evidence: #11967 targeted config/import suites passed, #11969 76/76 passed plus zero `config.template.mjs` runtime imports. | none — closed |
| Runtime provider routing plus native Ollama chat/embedding/graph path | L1/L2 | #11965 | #11966, corrective #11969 | #11966 wires native Ollama chat, embeddings, and graph-provider dispatch with explicit unsupported-provider rejection. Evidence: 38/38 provider/unit tests. #11969 removes overlay-bypass regression for runtime config consumption. | none — closed |
| Cloud docs plus provider readiness for embeddings, chat, and Dream/REM graph mutation | L2/L3 | #11964 | #11970 | #11970 adds Day-0 readiness docs and Dockerized provider readiness tests. Evidence verified from Actions run 26398752426: `CloudProviderReadiness.integration.spec.mjs` tests #13 and #14 both passed in `integration-unified` on the Docker-capable runner. | none — closed |
| Preserve env compatibility and server-local knobs | L1/L2 | #11963, #11964, #11965 | #11967, #11966, #11969, #11970 | Env override surface remains final; server-local collection names, listener ports, parser/scoring/batch knobs, and tenant-source config stay local. #11969 documents and validates local overlay bootstrap behavior. | none — closed |
| Discussion #11961 graduation mapping and liveness disposition | L1 | Parent #10103 plus #11963/#11965/#11964 | #11967, #11966, #11969, #11970 | #10103 body carries Signal Ledger, Unresolved Liveness, and Discussion Criteria Mapping. OpenAI authored, Anthropic approved, Google-family liveness remains explicitly archived as non-blocking under quorum. | none — closed |

### Source Discussion Closeout Gate

| Source Discussion criterion | Epic AC(s) | Owning sub(s) | Delivered PR(s) | Achieved evidence | Residual / deferral |
|---|---|---|---|---|---|
| OQ1 auth defaults become Tier-1 deployment defaults with KB/MC overlays | Tier-1 config ownership | #11963 | #11967, #11969 | Tier-1 defaults mapped; overlay import corrected. | none — delivered |
| OQ2 native Ollama active target plus OpenAI-compatible fallback | Runtime provider routing; cloud docs | #11965, #11964 | #11966, #11970 | Native Ollama chat/embedding/graph dispatch wired; Day-0 docs distinguish native vs OpenAI-compatible fallback. | none — delivered |
| OQ3 vectorDimension plus unified Chroma storage invariant | Tier-1 storage config; cloud readiness | #11963, #11964 | #11967, #11970 | Tier-1 storage defaults mapped; readiness spec runs against deployed Chroma/MC fixture. | none — delivered |
| OQ4 precedence Tier-1 template to gitignored top-level overlay to per-server mapped defaults to env | Config ownership | #11963 plus corrective #11968 | #11967, #11969 | `ai/config.mjs` overlay bootstrap and runtime imports are now the active path; env remains final. | none — delivered |
| OQ5 avoid monolithic config catalog | Config ownership boundary | #11963 | #11967 | Server-local knobs kept local by design. | none — delivered |
| OQ6 embeddings plus chat plus Dream/REM readiness | Cloud readiness | #11964 | #11970 | CI log verifies both cloud-readiness tests executed and passed under integration-unified. | none — delivered |
| OQ7 rewrite #10103 rather than duplicate | Parent epic shape | #10103 | issue body + epic review comment | #10103 body was rewritten and reviewed before subs. | none — delivered |
| OQ8 tenant-tier config remains data-source-only | Boundary guard | #11963 | #11967 | Tenant source config is explicitly out of the Tier-1 migration. | none — delivered |

### Rationale

All required subs are closed and close-linked to merged PRs: #11963 -> #11967, #11965 -> #11966 and #11969, #11964 -> #11970. The operator-flagged overlay regression discovered after Sub-1/Sub-2 is also closed by #11969, so the closeout should not pretend the first-pass sub PRs alone were sufficient.

The one evidence point that needed extra verification was #11970. Its PR body honestly declared local Docker as unavailable, so I checked the GitHub Actions integration log before marking the row complete. The Docker-capable `integration-unified` run executed and passed both new CloudProviderReadiness tests, including the Dream/REM graph-mutator path.

No source Discussion criteria are LOST. The Google-family liveness gap is explicitly recorded in #10103 and was non-blocking under the accepted quorum; it is not a missing sub.

### Required operator action

None for this epic. I am closing #10103 as completed after posting this verdict.

### A2A coordination

A2A notification is currently blocked by Memory Core/Chroma degradation in this Codex session: `list_messages` and healthcheck report ChromaDB not accessible on localhost:8000. The heartbeat thread is the current operator-visible handoff; I will still attempt the required `add_message` and record the failure if it remains unavailable.

Origin Session ID: 3c3566e9-59e1-45c2-a6ae-983897a856a3

- 2026-05-25T12:44:12Z @neo-gpt closed this issue
### @neo-gpt - 2026-05-25T18:33:26Z

Reopening based on 2026-05-25 operator V-B-A: the original intent is still valid, but the implementation is not actually complete for the config substrate shape. This is NOT a new ticket; it is the existing Tier-1 Agent OS config ownership epic being brought back in line with current code reality.

Verified current code shape:

- `ai/mcp/server/shared/BaseConfig.mjs` extends `Neo.core.Base`, but exports the raw class without registering the base class through `Neo.setupClass()`; it also still calls `applyLegacyEnv()` from `applyEnv()` and defines an override hook (`BaseConfig.mjs` lines 15-64).
- `ai/config.template.mjs` is still a separate implementation of clone/load/proxy behavior instead of extending/reusing the shared config base (`ai/config.template.mjs` lines 432-499).
- KB and MC server templates extend `BaseConfig`, but keep `defaultConfig` and `envBindings` in module scope and then assign `defaultConfig = defaultConfig; envBindings = envBindings;` inside the singleton (`knowledge-base/config.template.mjs` lines 18-44 and 378-398; `memory-core/config.template.mjs` lines 413-500).
- Env binding paths are currently dotted strings by design in `Neo.util.Env.applyEnvBindings()` (`src/util/Env.mjs` lines 44-83). That avoids env-value-derived path injection, but it is still brittle developer-authored string topology and does not mirror the nested config object shape.
- Generated/operator overlay files still contain legacy hooks in some server configs (`rg applyLegacyEnv ai/mcp/server`).

Duplicate/adjacency sweep before reopening:

- #11871 is adjacent env/logger primitive work, but its body explicitly keeps top-level + per-server config merge contract out of scope. Folding this into #11871 would violate its negative boundary.
- #11976 / #11981 cover test overlay determinism, not runtime config class ownership.
- #11965 covers provider runtime dispatch, not config substrate shape.
- #10126 covers stale `aiConfig.engines.neo` consumers, not BaseConfig/Tier-1 inheritance.

Required closure shape for this epic before it can be considered done:

1. Remove `applyLegacyEnv()` from shared BaseConfig and remaining server template/overlay surfaces. v13 is unreleased; no compatibility hook should survive unless a ticket proves a concrete deployed consumer.
2. Decide and implement the native Neo class shape for shared config: either `BaseConfig` itself is registered with `Neo.setupClass()` or a named, setupClass-backed shared config base replaces it. The final shape must be consistent with the ArchitectureOverview rule that Agent OS classes use the Neo class system natively.
3. Stop `ai/config.template.mjs` from going rogue. Tier-1 config should share the same base config behavior as server configs, while preserving its Tier-1 ownership role.
4. Move server `defaultConfig` / `envBindings` ownership into the class substrate instead of module-scope constants copied back onto singleton instances.
5. Replace or strictly contain dotted-string envBinding paths. Preferred direction: nested env-binding objects or explicit path arrays that mirror the config shape; if dotted strings remain, the PR must justify why the brittle string topology is still the right canonical contract.
6. Preserve the important boundary from #10103: global deployment defaults (auth, providers, vector dimension, unified Chroma coordinates) live at Tier-1; server-local ports, collections, parsers, scoring, batch knobs, and tenant-source config stay local.

This is cloud-deployment readiness work. The current shape still lets global config exist while orchestrator/server layers ignore or duplicate parts of it, which is exactly the invisibility failure mode this epic was supposed to close.

Retrieval Hint: "reopened #10103 BaseConfig setupClass applyLegacyEnv module-scope envBindings dotted paths Tier-1 Agent OS config ownership"

- 2026-05-25T18:33:27Z @neo-gpt reopened this issue
- 2026-05-25T18:36:29Z @neo-gpt assigned to @neo-gpt
- 2026-05-25T18:48:33Z @neo-opus-ada cross-referenced by PR #11985
- 2026-05-25T18:57:18Z @neo-gpt cross-referenced by PR #11988
- 2026-05-25T19:03:33Z @neo-gpt referenced in commit `5dd0740` - "feat(ai): hard-cut config env substrate (#10103)"
- 2026-05-25T19:26:26Z @tobiu referenced in commit `a838ff8` - "feat(ai): hard-cut config env substrate (#10103) (#11988)

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-25T20:08:39Z @neo-gpt cross-referenced by PR #11991
- 2026-05-25T21:22:22Z @tobiu referenced in commit `592c0a6` - "feat(ai): move config ledgers into class substrate (#10103) (#11991)

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
### @neo-gpt - 2026-05-25T22:22:32Z

## Epic Resolution Review

**Reviewer:** @neo-gpt
**Started:** 2026-05-25T22:22:00Z
**Completed:** 2026-05-25T22:22:00Z
**Verdict:** RECOMMEND_CLOSE_COMPLETED

### Matrix

| Parent AC | Required evidence | Owning sub(s) | Delivered PR(s) | Achieved evidence | Residual state |
|---|---|---|---|---|---|
| Tier-1 `ai/config.template.mjs` owns config ledgers inside `Neo.ai.Config` class substrate | L2 | #10103 reopened residual scope | #11988, #11991 | `origin/dev` shows `class Config extends BaseConfig`; no module-scope `const defaultConfig` / `const envBindings`; #11991 evidence reports template import smoke + 46/46 focused config tests | none — closed |
| Server config templates own `defaultConfig` / `envBindings` inside each `Config` class substrate | L2 | #10103 reopened residual scope | #11988, #11991 | `origin/dev` shows KB/MC/GitHub/GitLab/Neural templates no longer define module-scope ledgers; #11991 moved active template ledgers into Neo classes | none — closed |
| Nested env-binding contract remains intact and dotted env-binding keys remain rejected | L2 | #10103 reopened residual scope | #11988, #11991 | `origin/dev` has no `applyLegacyEnv` and no module-scope ledger remnants across active config templates; #11988/#11991 focused `Env.spec.mjs` + config-template tests cover the hard-cut env substrate | none — closed |
| `backupPath` default is symmetric for Memory Core and Knowledge Base without server-local duplication | L2 | #10103 comment 4283913925 + reopened residual scope | #11991 | `origin/dev` Tier-1 owns `backupPath`; KB and MC templates map `backupPath: AiConfig.backupPath` and `NEO_BACKUP_PATH` final override | none — closed |
| Preserve global/server-local boundaries | L2 | #10103 + Discussion #11961 | #11967, #11988, #11991 | Tier-1 owns provider/auth/storage/backup deployment defaults; server-local collection names/listener ports/parser/scoring/batch/tenant-source knobs remain in server templates | none — closed |
| Focused config tests and syntax/import smoke pass | L2 | #10103 reopened residual scope | #11988, #11991 | #11991 body records `initServerConfigs --migrate-config`, template import smoke, 46/46 focused config tests, and diff checks passing | none — closed |

### Source Discussion Closeout Gate

| Source Discussion criterion | Epic AC(s) | Owning sub(s) | Delivered PR(s) | Achieved evidence | Residual / deferral |
|---|---|---|---|---|---|
| Discussion #11961 Tier-1 provider/auth/storage ownership without monolithic all-server catalog | Tier-1/server-local boundary ACs | #11963 plus reopened residual scope | #11967, #11988, #11991 | Tier-1 ownership is implemented through `ai/config.template.mjs` + `ai/BaseConfig.mjs`; server-local knobs remain local | none — delivered |
| Native provider/cloud readiness remains compatible with config ownership | Provider/runtime/doc ACs from prior closeout | #11965, #11964 | #11966, #11970 | Prior closeout already verified runtime provider routing and Docker-capable cloud readiness; reopened residual did not add new runtime-provider criteria | none — delivered |
| Reopened operator V-B-A on BaseConfig/setupClass/legacy env/module-scope ledgers/backupPath | Reopened residual ACs | #10103 residual scope | #11988, #11991 | `origin/dev` verification confirms the reopened residual list is complete | none — delivered |

### Rationale

This epic was correctly reopened after the earlier closeout because the residual config-substrate shape was still live. The later merge train changed that: #11988 hard-cut the legacy env substrate and moved the shared base to top-level `ai/BaseConfig.mjs`; #11991 moved the remaining active template ledgers into the Neo config classes and promoted the shared backup default.

I verified the current `origin/dev` source before closing: `applyLegacyEnv` is absent from `ai/BaseConfig.mjs`, `ai/config.template.mjs`, and active MCP config templates; module-scope `const defaultConfig` / `const envBindings` are absent from the active templates; `ai/BaseConfig.mjs` exports `Neo.setupClass(BaseConfig)`; Tier-1, KB, and MC templates extend `BaseConfig`; KB/MC backup defaults now point at `AiConfig.backupPath` with `NEO_BACKUP_PATH` as final override.

No source Discussion criterion is lost. The reopened residual was a concrete implementation-shape gap, not a new unresolved architectural phase. The issue can close as completed; active clones still need the already-documented post-merge overlay refresh/restart step from #11991, but that is operational rollout work, not an open substrate AC.

### Required operator action

None. Closing #10103 as completed.

### A2A coordination

Broadcasting closeout to `AGENT:*` after posting this verdict.

Origin Session ID: 3b454ac4-f2c6-4bf0-9c18-c0af6f432ffa


- 2026-05-25T22:22:44Z @neo-gpt closed this issue
- 2026-05-26T02:04:05Z @neo-opus-ada cross-referenced by PR #12010
- 2026-05-26T23:47:32Z @neo-gpt cross-referenced by #12059
- 2026-06-03T13:40:33Z @neo-gpt cross-referenced by #12438

