From 048e8fdc004322e5feccc4fd76c8fc0dc87d91f0 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Wed, 24 Jun 2026 23:32:40 -0400 Subject: [PATCH] feat(desktop): re-snapshot persona config on every agent spawn (#1268) Signed-off-by: Will Pfleger Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co> --- desktop/scripts/check-file-sizes.mjs | 4 ++- desktop/src-tauri/src/commands/agents.rs | 23 +++++++++++++ .../src-tauri/src/managed_agents/restore.rs | 34 ++++++++++++++++++- .../features/agents/ui/ManagedAgentRow.tsx | 4 +-- 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/desktop/scripts/check-file-sizes.mjs b/desktop/scripts/check-file-sizes.mjs index 79cd0e664..58fe3e2cc 100644 --- a/desktop/scripts/check-file-sizes.mjs +++ b/desktop/scripts/check-file-sizes.mjs @@ -34,7 +34,9 @@ const overrides = new Map([ // read-time relay-URL workspace fallback while keeping the create-time env // pin (the credential-leak guard). Load-bearing feature growth from the // rebase, queued to split with the rest of this list. - ["src-tauri/src/commands/agents.rs", 1350], + // persona-refresh-on-spawn: re-snapshot + retain_managed_agent_pending call + // in start_local_agent_with_preflight adds ~23 lines. Queued to split. + ["src-tauri/src/commands/agents.rs", 1380], // Residual repos_dir integration in ensure_nest_at: REPOS is provisioned // outside NEST_DIRS (it may be a symlink), so it needs its own create + // chmod-only-when-real-dir handling plus integration test coverage. The diff --git a/desktop/src-tauri/src/commands/agents.rs b/desktop/src-tauri/src/commands/agents.rs index 5f34e2e8f..b8b45c31e 100644 --- a/desktop/src-tauri/src/commands/agents.rs +++ b/desktop/src-tauri/src/commands/agents.rs @@ -255,8 +255,31 @@ async fn start_local_agent_with_preflight( if record.backend != BackendKind::Local { return Err(format!("agent {pubkey} is no longer a local agent")); } + // Re-snapshot the persona onto the record at every spawn so the agent always + // starts with the current persona config (system_prompt, model, provider, + // env_vars). This clears the "out of date" drift badge without requiring a + // delete+recreate. Agent-level env_vars overrides still win (persona_snapshot + // layers persona env under agent overrides). + if let Some(persona_id) = record.persona_id.clone() { + let personas = load_personas(app).unwrap_or_default(); + if let Some(persona) = personas.iter().find(|p| p.id == persona_id) { + let snapshot = + crate::managed_agents::persona_events::persona_snapshot(persona, &record.env_vars); + if let Some(prompt) = snapshot.system_prompt { + record.system_prompt = Some(prompt); + } + record.model = snapshot.model; + record.provider = snapshot.provider; + record.env_vars = snapshot.env_vars; + record.persona_source_version = Some(snapshot.source_version); + record.updated_at = crate::util::now_iso(); + } + } start_managed_agent_process(app, record, &mut runtimes, Some(owner_hex))?; save_managed_agents(app, &records)?; + if let Some(saved_record) = records.iter().find(|r| r.pubkey == pubkey) { + retain_managed_agent_pending(app, state, saved_record); + } let record = records .iter() .find(|record| record.pubkey == pubkey) diff --git a/desktop/src-tauri/src/managed_agents/restore.rs b/desktop/src-tauri/src/managed_agents/restore.rs index df0da0c30..afbf8a997 100644 --- a/desktop/src-tauri/src/managed_agents/restore.rs +++ b/desktop/src-tauri/src/managed_agents/restore.rs @@ -91,7 +91,7 @@ pub async fn restore_managed_agents_on_launch( let state = app.state::(); // ── Phase A (under lock): housekeeping + collect agents to restore ── - let agents_to_start: Vec; + let mut agents_to_start: Vec; { let _store_guard = state .managed_agents_store_lock @@ -151,6 +151,38 @@ pub async fn restore_managed_agents_on_launch( } agents_to_start = to_start; + // Re-snapshot persona config for agents about to be restored, matching + // the interactive spawn path so auto-start agents also pick up the + // current persona on app launch. + let personas_for_snapshot = super::load_personas(app).unwrap_or_default(); + for record in records.iter_mut() { + if !agents_to_start.iter().any(|r| r.pubkey == record.pubkey) { + continue; + } + let Some(persona_id) = record.persona_id.clone() else { + continue; + }; + let Some(persona) = personas_for_snapshot.iter().find(|p| p.id == persona_id) else { + continue; + }; + let snapshot = super::persona_events::persona_snapshot(persona, &record.env_vars); + if let Some(prompt) = snapshot.system_prompt { + record.system_prompt = Some(prompt); + } + record.model = snapshot.model; + record.provider = snapshot.provider; + record.env_vars = snapshot.env_vars; + record.persona_source_version = Some(snapshot.source_version); + record.updated_at = util::now_iso(); + changed = true; + } + // Re-collect to_start from the updated records so Phase B spawns the refreshed config. + agents_to_start = records + .iter() + .filter(|r| agents_to_start.iter().any(|s| s.pubkey == r.pubkey)) + .cloned() + .collect(); + if changed { save_managed_agents(app, &records)?; } diff --git a/desktop/src/features/agents/ui/ManagedAgentRow.tsx b/desktop/src/features/agents/ui/ManagedAgentRow.tsx index 696003e35..8769737c6 100644 --- a/desktop/src/features/agents/ui/ManagedAgentRow.tsx +++ b/desktop/src/features/agents/ui/ManagedAgentRow.tsx @@ -276,8 +276,8 @@ function AgentSummary({ {agent.personaOutOfDate ? (

- Persona updated since this agent was created. Delete and respawn - to apply the new configuration. + Persona updated since this agent was created. Respawn to apply the + new configuration.

) : null} {channelNames.length > 0 ? (