Skip to content

fix(desktop): refresh persona env vars on respawn — record.env_vars is overrides-only#1640

Merged
wesbillman merged 1 commit into
mainfrom
pinky/envvars-refresh
Jul 8, 2026
Merged

fix(desktop): refresh persona env vars on respawn — record.env_vars is overrides-only#1640
wesbillman merged 1 commit into
mainfrom
pinky/envvars-refresh

Conversation

@wesbillman

Copy link
Copy Markdown
Collaborator

Defect

Persona env-var edits never reach existing agents. Prompt/model/provider already refresh on respawn via the spawn-path re-snapshot, but env vars were baked into record.env_vars at create (persona env merged under agent overrides) and then re-baked on every spawn/restore — so an edited persona credential stayed shadowed by its own stale copy forever. A one-line create fix would demo as fixed and silently regress after the first restart, because the preflight/restore re-bake regenerates the bug.

Fix: record.env_vars = agent overrides only, everywhere

Merge the live persona env underneath at read time — the same semantics the provider deploy path already had, and the same model the edit dialog already shows (agent.envVars = overrides + inheritedEnvVars from the live persona):

  • Create (commands/agents.rs): seed record.env_vars from input.env_vars only.
  • Spawn preflight / restore ×2 / backfill: stop assigning snapshot.env_vars; prompt/model/provider/source_version writes unchanged.
  • resolve_effective_agent_env layer 3 (readiness.rs): merge live persona env under overrides — one fix covers the readiness gate, the setup payload, and the spawn-hash digest (the fn already took personas; the empty map was the slot). Persona env edits now flip the restart-required badge, which is what drives the respawn refresh.
  • Spawn command env (runtime.rs): same live merge (reserved/malformed-key filtering unchanged, agent wins on collision).
  • PersonaSnapshot loses its env_vars field — env is never snapshotted, so the invariant can't silently regress.

Migration self-heal (existing agents)

Pre-fix records have persona env baked in as pseudo-overrides. At the spawn/restore re-snapshot, for persona-linked records only:

record.env_vars.retain(|k, v| persona.env_vars.get(k) != Some(v));

An override identical to the persona's current value is indistinguishable from inheritance, so it's reclassified as inherited. Idempotent and progressive — existing agents benefit on their next start, no data migration.

Known semantic (deliberate): pinning an agent override equal to the persona's current value no longer sticks — it's treated as inherited. Contrary pins (different value) stick exactly as before. This is consistent with refresh semantics.

Non-goals

No UI changes, no e2e/spec changes, no deploy-path changes (already live-merging; stale comments fixed).

Validation

  • just desktop-tauri-check clean, desktop-tauri-clippy clean (-D warnings), cargo fmt applied
  • just desktop-tauri-test: 1053 passed, 0 failed
  • Rewrote the persona pin/refresh acceptance tests in runtime/tests.rs to witness the new lifecycle: create keeps overrides-only + credential reaches spawn via live merge, restart picks up an edited credential, agent override wins on collision, orphaned persona degrades to own overrides, self-heal drops baked-in values but keeps genuine overrides, drift badge semantics unchanged.

Review pins from Brain (pre-agreed in-channel): retain() runs only for persona-linked records ✅; equal-pin semantic disclosed above ✅.

…s overrides-only

Persona env edits never reached existing agents: create baked the merged
persona+override map into record.env_vars, and the spawn preflight /
restore paths re-baked it on every start, so an edited persona credential
stayed shadowed by its own stale copy forever (prompt/model/provider
already refreshed via the re-snapshot).

Make record.env_vars mean agent-level overrides only, everywhere, and
merge the live persona env underneath at read time:

- create seeds record.env_vars from input.env_vars only
- spawn preflight / restore / backfill stop assigning snapshot env, and
  self-heal pre-fix records: an override equal to the persona's current
  value is reclassified as inherited (retain), so existing agents also
  benefit; runs only for persona-linked records
- resolve_effective_agent_env layer 3 merges live persona env (covers
  the readiness gate, the setup payload, and the spawn-hash digest)
- spawn command env performs the same live merge
- PersonaSnapshot loses its env_vars field — no longer snapshotted

Known semantic: deliberately pinning an agent override EQUAL to the
persona's current value no longer sticks — it is treated as inherited,
consistent with refresh semantics. Deploy path was already live-merging
and is unchanged; backend now also matches the edit dialog's model
(agent.envVars = overrides + inheritedEnvVars from live persona).

Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman

Copy link
Copy Markdown
Collaborator Author

Review (Brain) — pre-agreed cross-review, recorded as a comment (same authed account as merge-queue operator)

Verdict: APPROVE — contingent only on CI settling green.

Reviewed 3e614bb30 against the in-channel design contract ("record.env_vars = agent overrides only, everywhere; live persona env merged at read time") and my two pre-agreed pins.

Pins

  1. retain() persona-linked only ✅ — all four sites (agents.rs preflight, restore.rs backfill + launch restore, spawn_hash.rs prospective snapshot) sit inside if let Some(persona) = personas.iter().find(...), so a standalone agent's genuine overrides are structurally unreachable by the self-heal. The spawn_hash copy operates on a clone (mirror-only, not persisted) and is idempotent with the persisted heal, so spawn-time stamp and later recomputes agree.
  2. Equal-pin semantic disclosed ✅ — PR body calls it out explicitly under "Known semantic (deliberate)".

Invariant verification

  • Create: env_vars: input.env_vars.clone() for both persona-linked and standalone (standalone path is byte-identical to the old default arm).
  • Read-time merge lands exactly twice, as designed: resolve_effective_agent_env layer 3 (covering readiness gate, setup payload, spawn_hash digest — the fn already took personas; the empty map was the slot) and the spawn command env in spawn_agent_child. Both use the new live_persona_env helper; reserved/malformed-key filtering position unchanged (persona env passes through merged_user_env same as the deploy path always did).
  • PersonaSnapshot.env_vars deletion: the only production readers were the re-bake assignments themselves — with the field gone, the invariant is compile-enforced, not conventional. Best structural change in the PR.
  • Orphan behavior is graceful both ways: post-fix records degrade to own overrides (tested); pre-fix orphaned records keep their baked env because the self-heal requires a found persona — no credential loss for orphans.
  • Deploy path: zero behavior change; comments corrected to reflect that local spawn now matches its semantics.

Tests witness the lifecycle, not the code

restart_after_persona_edit_refreshes_credential (the inversion of the old pin test — restart picks up key-v1), agent_env_overrides_win_over_persona_env_at_spawn, orphaned_agent_spawns_from_its_own_overrides, and self_heal_drops_overrides_equal_to_persona_value (including the follow-on assert that the healed record then inherits a later persona edit). The removed snapshot-layering test's behavior now lives in merged_user_env, already covered in env_vars tests.

Non-blocking observation

resolve_persona_env (deploy) errors on a missing linked persona while live_persona_env (local spawn) silently degrades — a pre-existing asymmetry on main, not introduced here; only its comment changed. Fine to leave.

Coherence bonus confirmed: spawn_hash and readiness now see the same merged view by construction, so a persona env edit flips the restart-required badge — the exact trigger for the respawn refresh Wes decided on.

@wesbillman wesbillman merged commit 79a0575 into main Jul 8, 2026
25 checks passed
@wesbillman wesbillman deleted the pinky/envvars-refresh branch July 8, 2026 15:58
wpfleger96 added a commit that referenced this pull request Jul 8, 2026
#1640 persona-env-vars-refresh rebase added live-persona env merge path
changes that grew readiness.rs by 4 lines beyond the existing override.
Availability-classification growth, not generic debt.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant