Skip to content

Commit 2e426b2

Browse files
wpfleger96npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7
andauthored
fix: propagate persona harness edits to live agent instances (#1244)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
1 parent 45067ec commit 2e426b2

18 files changed

Lines changed: 652 additions & 58 deletions

File tree

desktop/scripts/check-file-sizes.mjs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,27 @@ const overrides = new Map([
3737
// self-contained repos_dir functions and their unit tests live in repos.rs;
3838
// this is the seam that must stay in nest.rs. Approved override; still queued
3939
// to split with the rest of this list.
40-
["src-tauri/src/managed_agents/nest.rs", 1447],
41-
["src-tauri/src/managed_agents/runtime.rs", 1953],
40+
["src-tauri/src/managed_agents/nest.rs", 1448],
41+
// harness-persona-sync: persona-runtime resolution threaded into the spawn
42+
// path here. Load-bearing feature growth; queued to split in the resolver
43+
// unify refactor followup.
44+
["src-tauri/src/managed_agents/runtime.rs", 1966],
4245
["src-tauri/src/managed_agents/personas.rs", 1080],
4346
["src-tauri/src/managed_agents/persona_card.rs", 1050],
4447
// applyWorkspace reposDir parameter plus the validateReposDir binding,
45-
// threaded through Tauri invokes for configurable repos_dir — a 4-line
46-
// overage from load-bearing parameter plumbing, not generic debt growth.
47-
// Approved override; still queued to split.
48-
["src/shared/api/tauri.ts", 1199],
48+
// threaded through Tauri invokes for configurable repos_dir, plus the
49+
// harness-persona-sync `harnessOverride` create-input bit — load-bearing
50+
// parameter plumbing, not generic debt growth. Approved override; still
51+
// queued to split.
52+
["src/shared/api/tauri.ts", 1202],
53+
// harness-persona-sync feature growth, queued to split in the resolver-unify
54+
// refactor followup. discovery.rs is dominated by the new test module
55+
// (the effective_agent_command / divergent / create-time override matrix);
56+
// types.rs adds the persona/instance harness fields; migration_tests.rs adds
57+
// the harness-sync migration coverage. Load-bearing, not generic debt.
58+
["src-tauri/src/managed_agents/discovery.rs", 1043],
59+
["src-tauri/src/managed_agents/types.rs", 1010],
60+
["src-tauri/src/migration_tests.rs", 1033],
4961
["src-tauri/src/nostr_convert.rs", 1126],
5062
["src/shared/api/relayClientSession.ts", 1022],
5163
["src-tauri/src/migration.rs", 1295],

desktop/src-tauri/src/commands/agent_models.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,21 @@ pub async fn get_agent_models(
4949
let resolved = resolve_command(&record.acp_command)
5050
.ok_or_else(|| missing_command_message(&record.acp_command, "ACP harness command"))?;
5151

52-
let args = normalize_agent_args(&record.agent_command, record.agent_args.clone());
52+
// Resolve the effective harness from the linked persona (mirrors spawn),
53+
// so model discovery runs against the persona's current harness, not the
54+
// frozen record snapshot. An explicit per-agent override wins.
55+
let personas = load_personas(&app).unwrap_or_default();
56+
let effective_command = crate::managed_agents::effective_agent_command(
57+
record.persona_id.as_deref(),
58+
&personas,
59+
record.agent_command_override.as_deref(),
60+
);
61+
62+
let args = normalize_agent_args(&effective_command, record.agent_args.clone());
5363

54-
let resolved_agent = resolve_command(&record.agent_command)
64+
let resolved_agent = resolve_command(&effective_command)
5565
.map(|p| p.display().to_string())
56-
.unwrap_or_else(|| record.agent_command.clone());
66+
.unwrap_or_else(|| effective_command.clone());
5767

5868
// Same env layering as runtime spawn: persona env < agent env.
5969
// Model discovery needs the user's credentials. Fail closed on
@@ -65,7 +75,6 @@ pub async fn get_agent_models(
6575

6676
// Resolve the effective model from the linked persona so the ModelPicker
6777
// dropdown shows the current persona model as selected.
68-
let personas = load_personas(&app).unwrap_or_default();
6978
let (_prompt, effective_model, _provider) = resolve_effective_prompt_model_provider(
7079
record.persona_id.as_deref(),
7180
&personas,
@@ -197,8 +206,18 @@ pub async fn update_managed_agent(
197206
if let Some(acp_command) = input.acp_command {
198207
record.acp_command = acp_command;
199208
}
209+
// Harness edit: the persona's runtime is authoritative, so we persist an
210+
// explicit `agent_command_override` ONLY when the user picks a command
211+
// that diverges from the persona. An empty/whitespace value (the
212+
// "Inherit from persona" sentinel) clears the pin back to `None`. A
213+
// name-only edit (`agent_command == None`) leaves the pin intact.
200214
if let Some(agent_command) = input.agent_command {
201-
record.agent_command = agent_command;
215+
let personas = load_personas(&app).unwrap_or_default();
216+
record.agent_command_override = crate::managed_agents::divergent_agent_command_override(
217+
record.persona_id.as_deref(),
218+
&personas,
219+
Some(&agent_command),
220+
);
202221
}
203222
if let Some(agent_args) = input.agent_args {
204223
record.agent_args = agent_args;
@@ -253,10 +272,19 @@ pub async fn update_managed_agent(
253272
&relay_ws_url_with_override(&state),
254273
);
255274
let display_name = record.name.clone();
275+
// Avatar fallback derives from the EFFECTIVE harness (persona-wins),
276+
// not the frozen snapshot, so an inherited harness picks the right
277+
// default avatar.
278+
let personas = load_personas(&app).unwrap_or_default();
279+
let effective_command = crate::managed_agents::effective_agent_command(
280+
record.persona_id.as_deref(),
281+
&personas,
282+
record.agent_command_override.as_deref(),
283+
);
256284
let avatar_url = record
257285
.avatar_url
258286
.clone()
259-
.or_else(|| managed_agent_avatar_url(&record.agent_command));
287+
.or_else(|| managed_agent_avatar_url(&effective_command));
260288
let auth_tag = record.auth_tag.clone();
261289
Some((agent_keys, relay_url, display_name, avatar_url, auth_tag))
262290
} else {

desktop/src-tauri/src/commands/agents.rs

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,9 @@ fn build_deploy_payload(
158158

159159
// Resolve effective model/provider from the persona's structured fields.
160160
// Agent record's model takes precedence (user override via UI).
161+
let personas = load_personas(app)
162+
.map_err(|e| format!("failed to load personas for deploy payload resolution: {e}"))?;
161163
let (effective_model, effective_provider) = if let Some(ref pid) = record.persona_id {
162-
let personas = load_personas(app).map_err(|e| {
163-
format!("failed to load personas for deploy payload model resolution: {e}")
164-
})?;
165164
let persona = personas.iter().find(|p| p.id == *pid);
166165
let model = record
167166
.model
@@ -173,6 +172,17 @@ fn build_deploy_payload(
173172
(record.model.clone(), None)
174173
};
175174

175+
// Resolve the effective harness (persona-wins, override-honored) so the
176+
// remote provider runs the same harness a local spawn would — derive args
177+
// from it rather than the frozen record snapshot.
178+
let effective_command = crate::managed_agents::effective_agent_command(
179+
record.persona_id.as_deref(),
180+
&personas,
181+
record.agent_command_override.as_deref(),
182+
);
183+
let effective_args =
184+
crate::managed_agents::normalize_agent_args(&effective_command, record.agent_args.clone());
185+
176186
Ok(serde_json::json!({
177187
"name": &record.name,
178188
// Resolve the per-agent pin against the active workspace relay here:
@@ -186,8 +196,8 @@ fn build_deploy_payload(
186196
),
187197
"private_key_nsec": &record.private_key_nsec,
188198
"auth_tag": &record.auth_tag,
189-
"agent_command": &record.agent_command,
190-
"agent_args": &record.agent_args,
199+
"agent_command": &effective_command,
200+
"agent_args": &effective_args,
191201
"system_prompt": &record.system_prompt,
192202
"model": effective_model,
193203
"provider": effective_provider,
@@ -461,13 +471,31 @@ pub async fn create_managed_agent(
461471
None
462472
};
463473

464-
let agent_command = input
465-
.agent_command
466-
.as_deref()
467-
.map(str::trim)
468-
.filter(|value| !value.is_empty())
469-
.map(str::to_string)
470-
.unwrap_or_else(crate::managed_agents::default_agent_command);
474+
// Load personas once for harness/pack/avatar resolution below.
475+
let personas = load_personas(&app).unwrap_or_default();
476+
477+
// Harness resolution: the persona's runtime is authoritative. A
478+
// persona-backed create stores an `agent_command_override` ONLY when the
479+
// user deliberately picked a divergent runtime (`harness_override`) —
480+
// e.g. AddChannelBotDialog's runtime selector. A divergence WITHOUT that
481+
// flag is a missing-runtime fallback from `resolvePersonaRuntime`, not a
482+
// pin, and must inherit so it doesn't freeze on the fallback harness once
483+
// the persona's runtime is installed. A persona-less create always
484+
// preserves the picked command as a real pin.
485+
let agent_command_override = crate::managed_agents::create_time_agent_command_override(
486+
requested_persona_id.as_deref(),
487+
&personas,
488+
input.agent_command.as_deref(),
489+
input.harness_override,
490+
);
491+
// The create-time snapshot used for arg/mcp/avatar derivations and
492+
// legacy reconcile. Authoritative spawn resolution re-derives this via
493+
// `effective_agent_command` at use-time.
494+
let agent_command = crate::managed_agents::effective_agent_command(
495+
requested_persona_id.as_deref(),
496+
&personas,
497+
agent_command_override.as_deref(),
498+
);
471499
let agent_args = normalize_agent_args(
472500
&agent_command,
473501
input
@@ -496,7 +524,6 @@ pub async fn create_managed_agent(
496524
// matches on this internal name, NOT display_name.
497525
let pack_metadata: Option<(std::path::PathBuf, String)> =
498526
requested_persona_id.as_deref().and_then(|pid| {
499-
let personas = load_personas(&app).ok()?;
500527
let persona = personas.iter().find(|p| p.id == pid)?;
501528
let team_id = persona.source_team.as_deref()?;
502529
let slug = persona.source_team_persona_slug.as_deref()?;
@@ -512,11 +539,11 @@ pub async fn create_managed_agent(
512539
// fallback. Storing it lets reconciliation compare against what was
513540
// actually published instead of re-deriving it.
514541
let persona_avatar_url = requested_persona_id.as_ref().and_then(|persona_id| {
515-
load_personas(&app)
516-
.ok()?
517-
.into_iter()
542+
personas
543+
.iter()
518544
.find(|persona| persona.id == *persona_id)?
519545
.avatar_url
546+
.clone()
520547
});
521548
let resolved_avatar_url = resolve_created_avatar_url(
522549
input.avatar_url.as_deref(),
@@ -540,6 +567,7 @@ pub async fn create_managed_agent(
540567
.unwrap_or(DEFAULT_ACP_COMMAND)
541568
.to_string(),
542569
agent_command,
570+
agent_command_override,
543571
agent_args,
544572
mcp_command,
545573
turn_timeout_seconds: input
@@ -797,14 +825,24 @@ pub async fn start_managed_agent(
797825

798826
let record = find_managed_agent_mut(&mut records, &pubkey)?;
799827

828+
// Resolve the effective harness for the avatar-fallback derivation in
829+
// profile reconcile (the create-time snapshot may be empty or stale for
830+
// a persona-inherited harness).
831+
let reconcile_personas = load_personas(&app).unwrap_or_default();
832+
let reconcile_effective_command = crate::managed_agents::effective_agent_command(
833+
record.persona_id.as_deref(),
834+
&reconcile_personas,
835+
record.agent_command_override.as_deref(),
836+
);
837+
800838
let reconcile = ProfileReconcileData {
801839
private_key_nsec: record.private_key_nsec.clone(),
802840
name: record.name.clone(),
803841
relay_url: record.relay_url.clone(),
804842
avatar_url: record.avatar_url.clone(),
805843
auth_tag: record.auth_tag.clone(),
806844
pubkey: record.pubkey.clone(),
807-
agent_command: record.agent_command.clone(),
845+
agent_command: reconcile_effective_command,
808846
persona_id: record.persona_id.clone(),
809847
};
810848

0 commit comments

Comments
 (0)