feat(desktop): global agent config defaults with readiness/spawn parity#1448
Open
wpfleger96 wants to merge 4 commits into
Open
feat(desktop): global agent config defaults with readiness/spawn parity#1448wpfleger96 wants to merge 4 commits into
wpfleger96 wants to merge 4 commits into
Conversation
34f9571 to
3903f9c
Compare
ef15981 to
828f73d
Compare
Introduce a global agent config record (global-agent-config.json) that applies to ALL managed agents as the lowest user-settable precedence layer. Per-agent and persona configs always win on collision. Precedence: baked build floor < global < persona < per-agent. ## Backend (Rust) - New global_config module: GlobalAgentConfig struct (env_vars, provider, model), load/save with atomic_write_json_restricted (0600), validate_global_config (strips empty values, rejects reserved and derived-provider-model keys), strip_empty_env_vars helper. - Tauri commands get_global_agent_config / set_global_agent_config with save-time validation; registered in invoke_handler. - ConfigOrigin::GlobalDefault added; resolve_config_surface re-tags injected global fields via retag_global_default (mirrors PersonaDefault pattern), so AgentConfigPanel shows 'Inherited from global defaults'. - Merge seams: resolve_effective_agent_env (readiness.rs — fixes readiness gate + nudge in one place), spawn_agent_child (runtime.rs — replaces empty lower-map with global env), build_deploy_payload (agents.rs — global < persona < agent for env and model/provider fallback chain). Global config is live-resolved at spawn/readiness/deploy — no delete+respawn required when the global record changes. ## Frontend (TypeScript) - tauriGlobalAgentConfig.ts: getGlobalAgentConfig / setGlobalAgentConfig invokeTauri wrappers. - useGlobalAgentConfig hook: loads once on mount, fails safe (no global config is not an error state for callers). - GlobalAgentConfigSettingsCard: Settings screen card with provider picker (reuses getPersonaProviderOptions), model input, EnvVarsEditor, and save-with-feedback button. Added to Settings → Agents section. - computeLocalModeGate: accepts optional globalEnvVars param; keys present in global count as satisfied so Create dialog does not flag a key missing that global already provides. - CreateAgentDialog: passes globalConfig.env_vars as inheritedFrom to EnvVarsEditor (shows 'global defaults' hint row). - EditAgentDialog: merges global + persona into inheritedWithGlobal for the EnvVarsEditor inherited hint so both sources are visible. - AgentConfigPanel: provenanceSentence handles new GlobalDefault case. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…obal defaults Three bugs found by Thufir in pass-1 review; all verified red-before/green-after. Fix 1 (CRITICAL — readiness/spawn divergence): extract `resolve_effective_model_provider(record, personas, global)` in `global_config/mod.rs` encoding the agent→persona→global→None precedence chain, and use it in both `resolve_effective_agent_env` (readiness) and `spawn_agent_child` (runtime). Before the fix, readiness used the full fallback chain but the spawn path read only `record.model`/`record.provider`, so a global-default-only agent reported Ready but spawned without provider/model env. Also fixes the same drift in `build_deploy_payload`. Hoists the global config load out of the readiness scoped block in runtime.rs to reuse it for the env-var merge, eliminating the duplicate load. Fix 2 (IMPORTANT — override baseline for global-default agents): in `resolve_config_surface`, when `!had_model && persona_model.is_none() && model_overridden`, use `(global.model, ConfigOrigin::GlobalDefault)` as the baseline. Before the fix, baseline was None in this case, so a live model switch on a global-default-only agent had no secondary row to show. Fix 3 (IMPORTANT — required rows): extract `isRequiredKeyMissing` from `EnvVarsEditor.tsx` and teach it to check `inheritedFrom?.[key]` in addition to the agent-local value. Before the fix, a globally-satisfied required key still rendered the amber "Required" badge because `isMissing` only checked the local map. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…ed copy
build_deploy_payload now uses a dedicated deploy-specific resolver
(live-persona → record → global) instead of the shared readiness/spawn
resolver (record → persona → global). Provider start does not
re-snapshot the linked persona onto the record before deploy, so the
record may hold a stale snapshot while the live persona has moved on.
Deploy needs live-persona-first to ensure remote agents receive the
current config after a persona update, without requiring delete+recreate.
Also fix EnvVarsEditor hint copy: rows where the local value is empty
but an inherited (global/persona) value satisfies the key now read
'Inherited from {label} value …' instead of 'Overrides {label} value …'.
Regression tests added:
- deploy_resolver_uses_live_persona_over_stale_record_snapshot
- deploy_resolver_falls_back_to_record_when_persona_has_none
- deploy_resolver_falls_back_to_global_when_persona_and_record_have_none
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
828f73d to
05d2467
Compare
resolve_deploy_model_provider fn + doc comment adds 6 lines on top of the prior global-agent-config bump. Queued to split with the rest of the file. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
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.
Adds a global agent configuration layer that lets users set default LLM provider, model, and environment variables applied to all agents unless overridden per-agent or per-persona.
What's here
Global config layer
GlobalAgentConfigstruct (provider,model,env_vars) persisted toglobal-agent-config.json.get_global_agent_config,set_global_agent_config(with validation — reserved and derived keys rejected).resolve_effective_agent_env) uses global as the final fallback tier.validate_global_configrejectsGOOSE_PROVIDER,GOOSE_MODEL,BUZZ_AGENT_PROVIDER,BUZZ_AGENT_MODEL— those are derived from structured fields and must not be set in the global env map.Fix 1 (CRITICAL — readiness/spawn divergence)
Extracted
resolve_effective_model_provider(record, personas, global)inglobal_config/mod.rsencoding the agent→persona→global→None precedence chain, and wired it into bothresolve_effective_agent_env(readiness) andspawn_agent_child(runtime). Before the fix, readiness used the full fallback chain but the spawn path read onlyrecord.model/record.provider, so a global-default-only agent reported Ready but spawned without provider/model env. Also fixes the same drift inbuild_deploy_payload. Hoists the global config load out of the readiness scoped block to reuse it for the env-var merge, eliminating a duplicate load.Fix 2 (IMPORTANT — override baseline for global-default agents)
In
resolve_config_surface, when!had_model && persona_model.is_none() && model_overridden, use(global.model, ConfigOrigin::GlobalDefault)as the baseline. Before the fix, baseline was None in this case, so a live model switch on a global-default-only agent had no secondary row to show in the config surface UI.Fix 3 (IMPORTANT — required rows in EnvVarsEditor)
Extracted
isRequiredKeyMissingfromEnvVarsEditor.tsxand taught it to checkinheritedFrom?.[key]in addition to the agent-local value. Before the fix, a globally-satisfied required key still rendered the amber "Required" badge becauseisMissingonly checked the local map.Tests
global_config/tests.rs: 6 new unit tests forresolve_effective_model_providercovering all precedence tiers (record wins, persona fallback, global fallback, no-persona, all-None, independent per-field resolution). Red-before/green-after verified — tests fail on pre-fixruntime.rsspawn path.commands/agent_config.rs: new testglobal_default_live_switch_renders_global_model_as_secondary_global_default— fails on pre-fix code where baseline was None for the!had_model && no-persona && model_overriddencase.envVarsEditorMissing.test.mjs: 7 new unit tests forisRequiredKeyMissingcovering local-set, inherited-set, both-set, neither-set, empty-inherited, empty-local, and wrong-key-in-inherited cases.Stack: #1411 → this PR