feat(desktop): re-snapshot persona config on every agent spawn (#1268)

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
Will Pfleger
2026-06-24 23:32:40 -04:00
committed by GitHub
co-authored by npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw
parent 0cee0435f7
commit 048e8fdc00
4 changed files with 61 additions and 4 deletions
+3 -1
View File
@@ -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
+23
View File
@@ -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)
@@ -91,7 +91,7 @@ pub async fn restore_managed_agents_on_launch(
let state = app.state::<AppState>();
// ── Phase A (under lock): housekeeping + collect agents to restore ──
let agents_to_start: Vec<super::ManagedAgentRecord>;
let mut agents_to_start: Vec<super::ManagedAgentRecord>;
{
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)?;
}
@@ -276,8 +276,8 @@ function AgentSummary({
</div>
{agent.personaOutOfDate ? (
<p className="mt-1.5 text-xs text-amber-600 dark:text-amber-400">
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.
</p>
) : null}
{channelNames.length > 0 ? (