feat(config): llm.runMode block — local | cloud | fusion with a fusion cloud-share dial - #161
Open
plombeer31 wants to merge 2 commits into
Open
feat(config): llm.runMode block — local | cloud | fusion with a fusion cloud-share dial#161plombeer31 wants to merge 2 commits into
plombeer31 wants to merge 2 commits into
Conversation
…n cloud-share dial Adds the config foundation for an operator run mode without changing any behaviour: nothing calls `resolveRunMode` outside its tests yet. `llm.runMode` sits beside `llm.fallback` rather than under `agent` (loop budgets) or at the top level: `llm` has a single writer, and the mode and `activeTextProvider` must move in one write + cache-reset cycle or they can disagree. `activeTextProvider` stays authoritative — `runMode.mode` is additive. The effective mode is derived from which provider is active, and a stored `fusion` is honoured only when the cloud leg is the active one. So an operator who switches provider by hand in Manage → LLM simply drops out of fusion on the next read: no reconciliation step, and no state that lies about what is running. It also means fusion pins the cloud provider as the fallback chain's primary, so `resolveFallbackChain` hoists it to the head and appends local at the tail with no changes of its own. `fusion.cloudShare` is documented as a dial, not a quota: it moves the cutoff on a bounded per-step complexity score rather than promising that N% of steps reach the cloud. Degradation is explicit and reported rather than silent — cloud/fusion without a cloud provider stays local, fusion without a local provider runs cloud-only, and a pinned `toolTransport` warns without downgrading. USER_CONFIG_VERSION 37 → 38. No migration code: `runMode` is an optional sub-key of an already-optional block, so absence is exactly the v37 behaviour; the bump only records the schema change. Verified: npm run lint and npm run build clean; npm test 4094 passed / 8 failed, the same 6 files / 8 tests that already fail on main @ 667dae1 (stale banner + tui-app fixtures, the `localModels.embeddings.url` fixture, a dev-machine-specific fs-glob path, and send-message-concurrency).
This was referenced Aug 19, 2026
…mmand This branch takes USER_CONFIG_VERSION to 38. Hand testing showed what that does to a machine that also has the installed v0.2.2 release: they share one ~/.atomic-agent/config.json, so the moment the newer build wrote v38, every single v0.2.2 command died with ConfigValidationError: invalid config: version: unsupported config version 38; expected one of 5, 6, ... 37 — models status, config get, the TUI, all of it, with no way out short of hand-editing the file. Running a release and a build under test side by side is normal, and the version bump made them mutually exclusive. Every bump this schema has taken is additive: the parser reads field by field and writeUserConfigFileSync preserves unknown top-level keys. So a newer file already parses correctly — the allow-list only ever needed a floor, not a ceiling. Older-than-supported is still refused, and a non-integer version is now refused explicitly rather than falling through the membership test. ensureUserConfigFileSync gets the other half: it must not rewrite a newer file back down to this build's shape. Reading it is safe; overwriting would delete the newer build's keys, and with a shared file the two builds would take turns destroying each other's config on every launch. Knowingly replaces the "rejects unsupported version" test, which pinned the broken behaviour.
This was referenced Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack: 1 of 3. Foundation only — nothing calls
resolveRunModeoutside its tests, so this changes no behaviour.llm.runModeconfig + resolver ← you are herefeat/fusion-routing— the routing engine (based on this branch)feat/run-mode-tui— the Run submenu + docsWhy
Switching between the local llama-server model and a cloud provider means walking into Manage → LLM, picking a pane and selecting a model row — several keystrokes deep in a panel you have to leave the chat to reach. The goal of the stack is a submenu on Run with three entries (Local · Cloud · Fusion) that flips models in one gesture, plus a Fusion mode that orchestrates on the cloud model and executes on the local one.
Where the block lives, and why
llm.runMode, a sibling ofllm.fallback— not underagent(which is loop budgets) and not top-level (whenllmis absent the runtime synthesizes a singlelocal-llamaprovider, so onlylocalis reachable anyway).The decisive reason is atomicity:
llmhas a single writer, and the mode andactiveTextProviderhave to move in onewriteUserConfigFileSync+resetConfigCache()cycle or they can disagree.The non-contradiction rule
llm.activeTextProviderstays authoritative;runMode.modeis additive:Consequences, all deliberate:
resolveFallbackChainhoists it to the chain head and appends local at the tail — sofallback-config.tsneeds no changes and its locked invariants stay green.cloudShareis a dial, not a quotaIt moves a cutoff on a bounded per-step complexity score (PR 2); it does not promise that N% of steps reach the cloud.
0behaves exactly likelocal,100exactly likecloud. That is stated in the JSDoc and (in PR 3) in AGENTS.md, so nobody later "fixes" it into a running-counter scheduler — a quota necessarily sends some trivial steps to the cloud and keeps some hard ones local, the opposite of the intent.Degradation is reported, never silent
localcloudtoolTransportpinnedfusionConfig version
USER_CONFIG_VERSION37 → 38, with no migration code:runModeis an optional sub-key of an already-optional block, so absence is exactly the v37 behaviour. The bump only records the schema change, per the AGENTS.md rule. Pinned by a test that a v37 file upgrades untouched.Verification
npm run lintandnpm run buildclean.npm test: 4094 passed, 8 failed — the same 6 files / 8 tests that already fail onmain @ 667dae1(verified by re-running them on a clean checkout of main): stale splash-banner and tui-app fixtures, thelocalModels.embeddings.urlfixture, a dev-machine-specificfs-globpath, andsend-message-concurrency. No new failures.29 new tests across
llm-run-mode-config.test.ts,resolve-run-mode.test.tsandrun-mode-degradation.test.ts, covering the full degradation matrix and the non-contradiction rule in both directions.