Mask hidden harness effort defaults

Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
kenny lopez
2026-07-27 13:32:33 +01:00
parent e525ee1a7d
commit 2a7ab3b542
11 changed files with 95 additions and 34 deletions
@@ -69,7 +69,9 @@ pub(super) fn build_deploy_payload(
// on key collision). Without this, provider-backed agents wouldn't receive
// credentials saved on the persona or the agent itself.
let global_config = crate::managed_agents::load_global_agent_config(app).unwrap_or_default();
let global_env = global_config.env_vars.clone();
let personas = load_personas(app).unwrap_or_default();
let global_env =
crate::managed_agents::global_env_vars_for_record(record, &personas, &global_config);
let persona_env =
crate::managed_agents::resolve_persona_env(app, record.persona_id.as_deref())?;
// Merge: global < persona (persona wins over global).
@@ -78,7 +80,6 @@ pub(super) fn build_deploy_payload(
let merged_env =
crate::managed_agents::merged_user_env(&global_persona_merged, &record.env_vars);
let personas = load_personas(app).unwrap_or_default();
let cfg = crate::managed_agents::effective_config::resolve_effective_config(
record,
&personas,
@@ -33,6 +33,8 @@ use crate::managed_agents::env_vars::{
use crate::managed_agents::storage::{atomic_write_json_restricted, managed_agents_base_dir};
use crate::managed_agents::types::{AgentDefinition, ManagedAgentRecord};
const HARNESS_DEPENDENT_GLOBAL_ENV_KEYS: &[&str] = &["BUZZ_AGENT_THINKING_EFFORT"];
/// The global agent configuration record.
///
/// Shape mirrors the per-agent/persona trio (`env_vars` + `provider` + `model`)
@@ -45,11 +47,12 @@ use crate::managed_agents::types::{AgentDefinition, ManagedAgentRecord};
/// consulted); for a definition-less instance, instance → global.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct GlobalAgentConfig {
/// Global env vars injected into ALL agents unconditionally.
/// Global env vars injected below persona and per-agent overrides.
///
/// Lowest user-settable layer — per-agent and persona values win on any
/// key collision. Reserved and derived keys are rejected at save time and
/// stripped at spawn time.
/// key collision. Harness-dependent effort is masked when a runtime-less
/// definition falls back away from the preferred runtime. Reserved and
/// derived keys are rejected at save time and stripped at spawn time.
#[serde(default)]
pub env_vars: BTreeMap<String, String>,
@@ -207,11 +210,11 @@ pub fn save_global_agent_config(app: &AppHandle, config: &GlobalAgentConfig) ->
atomic_write_json_restricted(&path, &payload)
}
/// Return the global provider/model values that are safe for this record.
/// Whether harness-dependent global defaults are safe for this record.
///
/// A runtime-less definition can be started on a fallback runtime when its
/// saved global preference is hidden or unavailable. In that case the selected
/// command is stored as the record snapshot, and provider/model defaults
/// command is stored as the record snapshot, and harness-dependent defaults
/// belonging to a different preferred runtime must not cross the harness
/// boundary. A non-empty `agent_command_override` keeps normal global
/// inheritance only when it represents an explicit harness selection. An
@@ -220,14 +223,13 @@ pub fn save_global_agent_config(app: &AppHandle, config: &GlobalAgentConfig) ->
/// harness. Explicitly configured definitions and standalone agents keep
/// normal global inheritance. Configs written before `preferred_runtime`
/// existed implicitly belong to Buzz Agent.
pub(crate) fn global_model_provider_for_record<'a>(
fn global_harness_defaults_apply_to_record(
record: &ManagedAgentRecord,
personas: &[AgentDefinition],
global: &'a GlobalAgentConfig,
) -> (Option<&'a str>, Option<&'a str>) {
let global_values = (global.model.as_deref(), global.provider.as_deref());
global: &GlobalAgentConfig,
) -> bool {
if record.persona_id.is_none() {
return global_values;
return true;
}
let definition_runtime = record.runtime.as_deref().or_else(|| {
@@ -238,7 +240,7 @@ pub(crate) fn global_model_provider_for_record<'a>(
.and_then(|persona| persona.runtime.as_deref())
});
if definition_runtime.is_some_and(|runtime| !runtime.trim().is_empty()) {
return global_values;
return true;
}
if !record.agent_command_override_is_implicit
@@ -247,7 +249,7 @@ pub(crate) fn global_model_provider_for_record<'a>(
.as_deref()
.is_some_and(|command| !command.trim().is_empty())
{
return global_values;
return true;
}
let preferred_runtime = global
@@ -258,15 +260,40 @@ pub(crate) fn global_model_provider_for_record<'a>(
let selected_command = crate::managed_agents::record_agent_command(record, personas);
let selected_runtime = crate::managed_agents::known_acp_runtime(&selected_command);
if selected_runtime.is_some_and(|selected| {
selected_runtime.is_some_and(|selected| {
preferred_runtime.is_some_and(|preferred| preferred.id == selected.id)
}) {
global_values
})
}
/// Return the global provider/model values that are safe for this record.
pub(crate) fn global_model_provider_for_record<'a>(
record: &ManagedAgentRecord,
personas: &[AgentDefinition],
global: &'a GlobalAgentConfig,
) -> (Option<&'a str>, Option<&'a str>) {
if global_harness_defaults_apply_to_record(record, personas, global) {
(global.model.as_deref(), global.provider.as_deref())
} else {
(None, None)
}
}
/// Return global env vars with harness-dependent defaults masked when an
/// implicit fallback crosses the preferred-runtime boundary.
pub(crate) fn global_env_vars_for_record(
record: &ManagedAgentRecord,
personas: &[AgentDefinition],
global: &GlobalAgentConfig,
) -> BTreeMap<String, String> {
let mut env_vars = global.env_vars.clone();
if !global_harness_defaults_apply_to_record(record, personas, global) {
for key in HARNESS_DEPENDENT_GLOBAL_ENV_KEYS {
env_vars.remove(*key);
}
}
env_vars
}
/// Resolve the effective model and provider for an agent.
///
/// Delegates to `effective_config::resolve_effective_config` which enforces
@@ -1,8 +1,8 @@
use std::collections::BTreeMap;
use super::{
normalize_global_config_fields, resolve_effective_model_provider, strip_empty_env_vars,
validate_global_config, GlobalAgentConfig,
global_env_vars_for_record, normalize_global_config_fields, resolve_effective_model_provider,
strip_empty_env_vars, validate_global_config, GlobalAgentConfig,
};
use crate::managed_agents::{AgentDefinition, BackendKind, ManagedAgentRecord, RespondTo};
@@ -494,6 +494,10 @@ fn explicit_harness_override_keeps_defaults_from_a_different_preferred_runtime()
global_env_vars_for_record(&record, &personas, &global),
global.env_vars
);
assert_eq!(
global_env_vars_for_record(&record, &personas, &global),
global.env_vars
);
}
#[test]
@@ -505,6 +509,10 @@ fn implicit_fallback_override_masks_defaults_from_a_different_preferred_runtime(
record.agent_command_override_is_implicit = true;
let personas = vec![persona("p1", None, None)];
let global = GlobalAgentConfig {
env_vars: BTreeMap::from([
("BUZZ_AGENT_THINKING_EFFORT".to_string(), "high".to_string()),
("SHARED_API_KEY".to_string(), "kept".to_string()),
]),
model: Some("auto".to_string()),
provider: Some("relay-mesh".to_string()),
preferred_runtime: Some("buzz-agent".to_string()),
@@ -515,6 +523,10 @@ fn implicit_fallback_override_masks_defaults_from_a_different_preferred_runtime(
resolve_effective_model_provider(&record, &personas, &global),
(None, None)
);
assert_eq!(
global_env_vars_for_record(&record, &personas, &global),
BTreeMap::from([("SHARED_API_KEY".to_string(), "kept".to_string())])
);
}
#[test]
+3 -2
View File
@@ -52,8 +52,9 @@ pub use env_vars::*;
pub(crate) use git_bash::git_bash_available;
pub(crate) use git_bash::{discover_git_bash, GitBashPrerequisite};
pub(crate) use global_config::{
global_model_provider_for_record, load_global_agent_config, resolve_effective_model_provider,
save_global_agent_config, validate_global_config, GlobalAgentConfig,
global_env_vars_for_record, global_model_provider_for_record, load_global_agent_config,
resolve_effective_model_provider, save_global_agent_config, validate_global_config,
GlobalAgentConfig,
};
pub(crate) use managed_node_paths::*;
pub use nest::*;
@@ -256,7 +256,9 @@ fn resolve_effective_agent_env_with_def(
// Injected before persona/agent so per-agent values win on collision.
// `merged_user_env` with an empty "lower" map applies reserved/malformed-key
// filtering to the global map for free.
let global_env = merged_user_env(&BTreeMap::new(), &global.env_vars);
let applicable_global_env =
super::global_config::global_env_vars_for_record(record, personas, global);
let global_env = merged_user_env(&BTreeMap::new(), &applicable_global_env);
env.extend(global_env);
// Layer 3b: merged user env — live persona env under the record's own
+3 -2
View File
@@ -94,8 +94,9 @@ with a TypeScript lookup table or an id comparison in a component.
agent that already uses it. If the disabled runtime was the saved global
default, consumers immediately ignore that preference and the defaults
editor persists its visible fallback on the next save. Its dependent
provider/model defaults are also ignored for new implicit fallback agents,
without changing the persisted configuration used by existing agents.
provider/model/effort defaults are also ignored for new implicit fallback
agents, without changing the persisted configuration used by existing
agents.
Legacy global defaults with no saved preferred runtime are treated as
Buzz Agent-owned for this masking boundary, so hiding Buzz Agent cannot
leak its provider/model into another implicit fallback. A persisted
@@ -104,7 +104,10 @@ test("stored runtime visibility is read from the versioned device key", () => {
test("a disabled saved runtime and its dependent defaults are masked", () => {
const config = {
env_vars: {},
env_vars: {
BUZZ_AGENT_THINKING_EFFORT: "high",
SHARED_API_KEY: "kept",
},
provider: "relay-mesh",
model: "auto",
preferred_runtime: "Goose",
@@ -112,6 +115,7 @@ test("a disabled saved runtime and its dependent defaults are masked", () => {
assert.deepEqual(maskDisabledAcpRuntimePreference(config, ["goose"]), {
...config,
env_vars: { SHARED_API_KEY: "kept" },
provider: null,
model: null,
preferred_runtime: null,
@@ -121,7 +125,10 @@ test("a disabled saved runtime and its dependent defaults are masked", () => {
test("legacy defaults without a saved runtime are owned by buzz-agent", () => {
const config = {
env_vars: {},
env_vars: {
BUZZ_AGENT_THINKING_EFFORT: "medium",
SHARED_API_KEY: "kept",
},
provider: "relay-mesh",
model: "auto",
preferred_runtime: null,
@@ -129,6 +136,7 @@ test("legacy defaults without a saved runtime are owned by buzz-agent", () => {
assert.deepEqual(maskDisabledAcpRuntimePreference(config, ["buzz-agent"]), {
...config,
env_vars: { SHARED_API_KEY: "kept" },
provider: null,
model: null,
});
@@ -1,4 +1,5 @@
import * as React from "react";
import { withoutHarnessDependentAgentEnvVars } from "../ui/buzzAgentConfig";
export const ACP_RUNTIME_VISIBILITY_STORAGE_KEY =
"buzz-agent-runtime-visibility.v1";
@@ -166,11 +167,12 @@ export function visibleAcpRuntimeSeedForCreate<T extends { id: string }>(
* The persisted config is left untouched until the user next saves defaults;
* existing agents keep their configuration while new consumers immediately
* fall back through the normal runtime selection path without carrying a
* provider or model selected for the hidden harness. Configs written before
* preferred_runtime existed implicitly belong to Buzz Agent.
* provider, model, or effort selected for the hidden harness. Configs written
* before preferred_runtime existed implicitly belong to Buzz Agent.
*/
export function maskDisabledAcpRuntimePreference<
T extends {
env_vars: Record<string, string>;
model: string | null;
preferred_runtime: string | null;
provider: string | null;
@@ -188,6 +190,7 @@ export function maskDisabledAcpRuntimePreference<
return {
...config,
env_vars: withoutHarnessDependentAgentEnvVars(config.env_vars),
model: null,
preferred_runtime: null,
provider: null,
@@ -2,7 +2,7 @@ import type {
AcpRuntimeCatalogEntry,
GlobalAgentConfig,
} from "@/shared/api/types";
import { BUZZ_AGENT_THINKING_EFFORT } from "./buzzAgentConfig";
import { withoutHarnessDependentAgentEnvVars } from "./buzzAgentConfig";
import type { RuntimeFileConfigSubset } from "@/shared/api/tauri";
// Dialogs import getDefaultPersonaRuntime via this re-export; lib code imports
// directly from lib/resolvePersonaRuntime.
@@ -222,12 +222,9 @@ export function resetConfigForHarnessChange(
config: GlobalAgentConfig,
runtimeId: string,
): GlobalAgentConfig {
const nextEnvVars = { ...config.env_vars };
delete nextEnvVars[BUZZ_AGENT_THINKING_EFFORT];
return {
...config,
env_vars: nextEnvVars,
env_vars: withoutHarnessDependentAgentEnvVars(config.env_vars),
model: null,
preferred_runtime: runtimeId || null,
provider:
@@ -8,6 +8,15 @@
/** Env var key for the thinking/effort level sent to the LLM. */
export const BUZZ_AGENT_THINKING_EFFORT = "BUZZ_AGENT_THINKING_EFFORT";
/** Remove env-backed values whose semantics change with the harness. */
export function withoutHarnessDependentAgentEnvVars(
envVars: Record<string, string>,
): Record<string, string> {
const next = { ...envVars };
delete next[BUZZ_AGENT_THINKING_EFFORT];
return next;
}
/** Env var key for the maximum output token count per turn. */
export const BUZZ_AGENT_MAX_OUTPUT_TOKENS = "BUZZ_AGENT_MAX_OUTPUT_TOKENS";
+1 -1
View File
@@ -1037,7 +1037,7 @@ export type ChannelMessagesPageResponse = {
* Precedence: baked floor < global < persona < per-agent.
*/
export type GlobalAgentConfig = {
/** Global env vars injected into all agents unconditionally. */
/** Global env vars injected below persona and per-agent overrides. */
env_vars: Record<string, string>;
/** Global fallback provider (e.g. "anthropic", "databricks_v2"). Null = no global default. */
provider: string | null;