Skip to content

feat: support scoped model config overlays - #355

Open
mikemikimike wants to merge 3 commits into
cortexkit:masterfrom
mikemikimike:feat/config-env-override-354
Open

feat: support scoped model config overlays#355
mikemikimike wants to merge 3 commits into
cortexkit:masterfrom
mikemikimike:feat/config-env-override-354

Conversation

@mikemikimike

@mikemikimike mikemikimike commented Aug 22, 2026

Copy link
Copy Markdown

Summary

Closes #354.

This PR adds an optional MAGIC_CONTEXT_CONFIG JSONC overlay for per-run model selection across historian, dreamer, and sidekick.

  • Supports model, fallback_models, and variant overrides only.
  • Uses the existing nested opencode shape for historian and dreamer, and the flat model shape for sidekick.
  • Reports unsupported top-level, harness, and field-level keys instead of silently ignoring them.
  • Preserves shared embedding, storage, memory, compaction, prompt-surface, and threshold settings.
  • Adds regression coverage for model overrides, ignored keys, flat sidekick configuration, and preservation of durable settings.

Existing behavior is unchanged when MAGIC_CONTEXT_CONFIG is unset.

Verification

  • bun test packages/plugin/src/config/index.test.ts — passed (53 tests, 0 failures).
  • git diff --check — passed.
  • Typecheck and lint were not completed because dependency installation did not finish and the required tools were unavailable.

Status / Design Note

The implementation follows the scoped two-profile model-selection workflow discussed for #354. The PR is currently on hold pending the maintainer's design discussion about the configuration mechanism and profile/process behavior.

The latest automated review also identified follow-up coverage/robustness questions around invalid supported overlay values replacing valid base settings, and duplicate/mislabelled warnings for unsupported flat sidekick fields. These should be resolved against the final direction agreed in #354 before merging.

Greptile Summary

This PR adds a MAGIC_CONTEXT_CONFIG JSONC overlay that limits per-run overrides to historian, dreamer, and sidekick model-selection fields while preserving durable configuration.

  • Loads and filters the optional overlay before final schema parsing.
  • Supports nested OpenCode settings for historian and dreamer and flat settings for sidekick.
  • Adds regression coverage for overlay precedence, unsupported keys, and preservation of shared settings.

Confidence Score: 4/5

The PR is not yet safe to merge because invalid supported overlay values can erase valid base model selections.

The overlay is merged over the valid base configuration before validation, and nested recovery removes invalid replacement fields from that merged object rather than restoring the values they displaced.

Files Needing Attention: packages/plugin/src/config/index.ts

Important Files Changed

Filename Overview
packages/plugin/src/config/index.ts Adds overlay loading, model-field filtering, warning collection, and final precedence merging; invalid supported values can still displace valid base settings.
packages/plugin/src/config/index.test.ts Adds focused coverage for valid model overlays, ignored keys, flat sidekick settings, and preservation of durable configuration.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  U[User configuration] --> M[Merge user and project configuration]
  P[Project configuration] --> M
  E[MAGIC_CONTEXT_CONFIG] --> F[Filter to supported model fields]
  F --> M
  M --> V[Schema validation and recovery]
  V --> R[Runtime configuration]
Loading

Reviews (3): Last reviewed commit: "fix: support flat sidekick model overlay..." | Re-trigger Greptile

Context used:

@mikemikimike

Copy link
Copy Markdown
Author

Review: this reproduces the scoped two-profile model-selection workflow accepted by the maintainer, not arbitrary full-config replacement. Full replacement would conflict with shared embedding/storage/compaction/memory state. I found one issue: unsupported top-level or non-OpenCode overlay keys were silently ignored. I am tightening warnings and adding regression coverage.

@mikemikimike

Copy link
Copy Markdown
Author

Follow-up review and fix: unsupported top-level fields and non-OpenCode harness fields in MAGIC_CONTEXT_CONFIG are now reported explicitly instead of being silently ignored. Regression coverage verifies model fields apply while embedding and memory settings remain unchanged. Focused suite: 53 passed, 0 failed; git diff --check passed. Commit: 24f3dcf.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/plugin/src/config/index.ts">

<violation number="1" location="packages/plugin/src/config/index.ts:74">
P2: Keys in `MAGIC_CONTEXT_CONFIG` that are not inside one of the three agents' `opencode` blocks are silently dropped with no warning, which contradicts the PR's stated behavior of emitting a config warning for any unsupported key. The loop only iterates `MODEL_OVERLAY_AGENTS`, so a top-level key such as `embedding`, `memory`, or `storage`, or a `pi`-harness block (`historian.pi.model`), or any key directly under an agent's `opencode` that isn't one of the three model fields never reaches `ignored`. The regression test itself includes `embedding: { provider: off }` in the overlay and only asserts it isn't applied — it never asserts a warning, so the silent-drop path is untested. A user who puts durable settings in the overlay (or a Pi-only override) gets no feedback that they were ignored, which is exactly the "ignored key" risk the warning was meant to surface. Consider iterating all remaining overlay keys (outside the supported agent/field set) and adding them to the `ignored` list.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/plugin/src/config/index.ts Outdated
ignored.push(key);
}
}
for (const agent of MODEL_OVERLAY_AGENTS) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Keys in MAGIC_CONTEXT_CONFIG that are not inside one of the three agents' opencode blocks are silently dropped with no warning, which contradicts the PR's stated behavior of emitting a config warning for any unsupported key. The loop only iterates MODEL_OVERLAY_AGENTS, so a top-level key such as embedding, memory, or storage, or a pi-harness block (historian.pi.model), or any key directly under an agent's opencode that isn't one of the three model fields never reaches ignored. The regression test itself includes embedding: { provider: off } in the overlay and only asserts it isn't applied — it never asserts a warning, so the silent-drop path is untested. A user who puts durable settings in the overlay (or a Pi-only override) gets no feedback that they were ignored, which is exactly the "ignored key" risk the warning was meant to surface. Consider iterating all remaining overlay keys (outside the supported agent/field set) and adding them to the ignored list.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/config/index.ts, line 74:

<comment>Keys in `MAGIC_CONTEXT_CONFIG` that are not inside one of the three agents' `opencode` blocks are silently dropped with no warning, which contradicts the PR's stated behavior of emitting a config warning for any unsupported key. The loop only iterates `MODEL_OVERLAY_AGENTS`, so a top-level key such as `embedding`, `memory`, or `storage`, or a `pi`-harness block (`historian.pi.model`), or any key directly under an agent's `opencode` that isn't one of the three model fields never reaches `ignored`. The regression test itself includes `embedding: { provider: off }` in the overlay and only asserts it isn't applied — it never asserts a warning, so the silent-drop path is untested. A user who puts durable settings in the overlay (or a Pi-only override) gets no feedback that they were ignored, which is exactly the "ignored key" risk the warning was meant to surface. Consider iterating all remaining overlay keys (outside the supported agent/field set) and adding them to the `ignored` list.</comment>

<file context>
@@ -55,6 +56,58 @@ function getProjectConfigBasePath(directory: string): string {
+
+    const overlay: Record<string, unknown> = {};
+    const ignored: string[] = [];
+    for (const agent of MODEL_OVERLAY_AGENTS) {
+        const source = loaded.config[agent];
+        if (!source || typeof source !== "object" || Array.isArray(source)) {
</file context>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The top-level/non-OpenCode warning path was added in commit 24f3dcf and is covered by the existing regression assertions for embedding and memory. The current branch now also adds flat sidekick coverage.

Comment thread packages/plugin/src/config/index.ts
Comment thread packages/plugin/src/config/index.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/plugin/src/config/index.ts">

<violation number="1" location="packages/plugin/src/config/index.ts:87">
P3: For a flat sidekick overlay, `harness` is `sourceRecord`, so the existing second loop (`for (const [key, value] of Object.entries(harness))`) iterates the exact same keys the new first loop just scanned. Any unsupported sidekick key (e.g. `system_prompt`) is pushed to `ignored` twice — once as `sidekick.system_prompt` (first loop) and again as `sidekick.opencode.system_prompt` (second loop, which still uses the historian/dreamer `.opencode.` label). Users see a duplicate, wrongly-labeled entry in the `[model overlay] Ignoring unsupported keys` warning. Skip the first-loop push for flat sidekick and label the second-loop push correctly using `isFlatSidekick`.</violation>

<violation number="2" location="packages/plugin/src/config/index.ts:106">
P2: When a flat sidekick overlay contains an invalid model field, it erases the corresponding valid base setting before recovery runs. Validate overlay fields before merging, or retain the base value when an overlay field fails validation.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

}
}
if (Object.keys(selected).length > 0) {
overlay[agent] = isFlatSidekick ? selected : { opencode: selected };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a flat sidekick overlay contains an invalid model field, it erases the corresponding valid base setting before recovery runs. Validate overlay fields before merging, or retain the base value when an overlay field fails validation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/config/index.ts, line 106:

<comment>When a flat sidekick overlay contains an invalid model field, it erases the corresponding valid base setting before recovery runs. Validate overlay fields before merging, or retain the base value when an overlay field fails validation.</comment>

<file context>
@@ -99,7 +103,7 @@ function loadModelOverlay(): Record<string, unknown> | null {
         }
         if (Object.keys(selected).length > 0) {
-            overlay[agent] = { opencode: selected };
+            overlay[agent] = isFlatSidekick ? selected : { opencode: selected };
         }
     }
</file context>

}
const sourceRecord = source as Record<string, unknown>;
const isFlatSidekick = agent === "sidekick";
const harness = isFlatSidekick ? sourceRecord : sourceRecord.opencode;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: For a flat sidekick overlay, harness is sourceRecord, so the existing second loop (for (const [key, value] of Object.entries(harness))) iterates the exact same keys the new first loop just scanned. Any unsupported sidekick key (e.g. system_prompt) is pushed to ignored twice — once as sidekick.system_prompt (first loop) and again as sidekick.opencode.system_prompt (second loop, which still uses the historian/dreamer .opencode. label). Users see a duplicate, wrongly-labeled entry in the [model overlay] Ignoring unsupported keys warning. Skip the first-loop push for flat sidekick and label the second-loop push correctly using isFlatSidekick.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/config/index.ts, line 87:

<comment>For a flat sidekick overlay, `harness` is `sourceRecord`, so the existing second loop (`for (const [key, value] of Object.entries(harness))`) iterates the exact same keys the new first loop just scanned. Any unsupported sidekick key (e.g. `system_prompt`) is pushed to `ignored` twice — once as `sidekick.system_prompt` (first loop) and again as `sidekick.opencode.system_prompt` (second loop, which still uses the historian/dreamer `.opencode.` label). Users see a duplicate, wrongly-labeled entry in the `[model overlay] Ignoring unsupported keys` warning. Skip the first-loop push for flat sidekick and label the second-loop push correctly using `isFlatSidekick`.</comment>

<file context>
@@ -82,12 +82,16 @@ function loadModelOverlay(): Record<string, unknown> | null {
-            if (key !== "opencode") ignored.push(`${agent}.${key}`);
+        const sourceRecord = source as Record<string, unknown>;
+        const isFlatSidekick = agent === "sidekick";
+        const harness = isFlatSidekick ? sourceRecord : sourceRecord.opencode;
+        for (const key of Object.keys(sourceRecord)) {
+            if (isFlatSidekick ? !(MODEL_OVERLAY_FIELDS as readonly string[]).includes(key) : key !== "opencode") {
</file context>

@magic-alfonso

magic-alfonso Bot commented Aug 22, 2026

Copy link
Copy Markdown

Status update, honestly framed: your implementation announcement on #354 was legitimate — you followed the words of our comment more carefully than we followed the thread, and the hours of silence before your PR were our miss. Since then we've done the deeper surface walk that should have preceded that comment, and posted the resulting concerns on #354 (process-vs-profile scoping, doctor/dashboard visibility, a direnv-shaped trust edge, loader proliferation, cross-process config sync). The env-var mechanism itself is now an open design question there, so this PR is on hold pending that conversation rather than in review.

Two things so your work isn't wasted: your historian/dreamer reading is already per-harness-aware, which is exactly right against the shape landing in the next release; and if the design conversation lands on proceeding (in env-var form or the profile-block alternative sketched on #354), we have concrete review feedback ready — the known items are the sidekick block (sidekick is flat in the current schema, so those overlay entries are silently discarded), unvalidated overlay values overwriting valid config, and Pi-side coverage. Thanks for the speed and the engagement either way.

@mikemikimike

Copy link
Copy Markdown
Author

Thanks for the detailed status update and for clarifying the current direction. I agree that the PR should remain on hold while #354 settles the configuration mechanism and the process/profile semantics.

I have updated the PR description to reflect that status and to record the remaining review questions: invalid overlay values must not erase valid base settings, and flat sidekick warnings need correct de-duplication/labels. I will align any follow-up implementation with the final design from #354 rather than making those changes against a potentially obsolete env-var contract.

@mikemikimike
mikemikimike force-pushed the feat/config-env-override-354 branch from f457364 to e985c28 Compare August 23, 2026 15:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

support loading config from env var

1 participant