Skip to content

fix(desktop): republish agent identity records when a persona rename propagates#2607

Open
SeanGearin wants to merge 2 commits into
block:mainfrom
SeanGearin:fix-2423-agent-identity-republish
Open

fix(desktop): republish agent identity records when a persona rename propagates#2607
SeanGearin wants to merge 2 commits into
block:mainfrom
SeanGearin:fix-2423-agent-identity-republish

Conversation

@SeanGearin

Copy link
Copy Markdown

Problem

Part of #2423 (renaming personal agents desynchronises identity).

Renaming an agent definition (persona) propagates the new display name to its
linked agent instances (propagate_persona_name_rename in
desktop/src-tauri/src/commands/personas/mod.rs) and saves
managed-agents.json — but, unlike the instance-rename path
(update_managed_agent), it never re-retains the renamed instances' kind:30177
managed-agent identity records. record.name is part of the published identity
projection (agent_event_content), so after a persona rename:

  • managed-agents.json says the NEW name,
  • the retained kind:30177 row (retention.db → relay flush loop) still carries
    the OLD name, with the OLD created_at.

The stale identity record stays live on the relay until the next app launch,
when the boot-time reconcile (reconcile_agents_in_dir) finally notices the
content diff and republishes. Until that restart, any surface that resolves
agents from kind:30177 records (second desktop of the same owner, CLI, other
NIP-AP clients) sees the OLD name bound to the agent pubkey while the kind:0
profile already shows the NEW one — the name→identity binding desync described
in #2423, and consistent with the report's observation that repairing state
required "a separate restart".

Fix

  • Extract the per-record retain body of the boot reconcile into
    managed_agents::reconcile::retain_agent_record(conn, keys, record) -> Result<bool, String>
    — one shared content-diff + monotonic-created_at-bump engine (returns
    whether a row was rewritten). reconcile_agents_in_dir now calls it per
    record (behavior unchanged; existing reconcile tests still pass).
  • commands::agents::retain_managed_agent_pending delegates to the shared
    engine instead of carrying a duplicate implementation (same semantics:
    projection-equality no-op guard, monotonic bump, pending_sync = 1).
  • update_persona (Phase 1, still under the store lock, after
    save_managed_agents): call retain_managed_agent_pending for every record
    the rename propagated to — mirroring update_managed_agent. Avatar-only
    edits are deliberately excluded (the avatar is not part of the kind:30177
    projection; retaining would be a guaranteed no-op).

No new events, kinds, or APIs — this uses the existing signed-event retention
and flush pipeline, per CONTRIBUTING's guidance to prefer a signed Nostr event
and the existing ingest path over endpoint-specific JSON APIs.

Out of scope (deliberately)

Test evidence

Two new unit tests in desktop/src-tauri/src/managed_agents/reconcile/tests.rs
(same harness as the existing reconcile tests — tempdir + retention.db + fresh
keys, no AppHandle):

  • rename_re_retains_identity_record_with_new_name — retain "Fizz", confirm
    flush, rename to "Spark", re-retain: row keeps the pubkey coordinate,
    carries the new name only, is pending_sync, and its created_at is
    strictly past the retained head (replaceable-event acceptance).
  • retain_agent_record_is_noop_when_unchanged — an unchanged projection does
    not rewrite the row and produces zero pending_sync churn.

Ran scoped per CONTRIBUTING build discipline (from desktop/src-tauri):

cargo fmt -p buzz-desktop                                  # applied, clean
cargo clippy -p buzz-desktop --lib --tests -- -D warnings  # exit 0, no warnings
cargo test -p buzz-desktop --lib                           # full lib suite

Full buzz-desktop lib suite: 1562 passed, 0 failed, 13 ignored
including all 12 managed_agents::reconcile tests (10 pre-existing, all
unmodified in behavior, plus the 2 new regression tests above).

Links

@SeanGearin
SeanGearin requested a review from a team as a code owner July 23, 2026 20:13
…propagates

Signed-off-by: Sean Gearin <sgearin@gmail.com>
@SeanGearin
SeanGearin force-pushed the fix-2423-agent-identity-republish branch from 81be1c0 to 63a53dc Compare July 23, 2026 20:20

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 This is a nice fix — the root cause is exactly right (update_persona's rename path propagates the new name and saves managed-agents.json but never re-retains the kind:30177 identity records, so the relay carries the stale name→pubkey binding until the next boot reconcile), and I like that the fix extracts the duplicated retain logic from retain_managed_agent_pending and reconcile_agents_in_dir into one shared retain_agent_record engine instead of adding a third copy — production code is net negative here. The two tests are discriminating (rename re-retains with a monotonic created_at bump past the retained head; unchanged record is a true no-op with no pending_sync churn), and the avatar-only exclusion is correct since the avatar isn't part of the published projection.

One blocking issue, and it's mechanical:

Desktop Core is red on the file-size ratchet. The check fails with src-tauri/src/commands/personas/mod.rs: 985 lines (limit 984) — that file has a TEMP per-file ceiling of exactly 984 in desktop/scripts/check-file-sizes.mjs, and this PR's 13 added lines in update_persona land it 1 line over. Rather than bumping the override (the policy comment in that script says not to), the easiest fix is to trim the comment above the new retain loop — it's 10 lines for a 3-line loop, and 2-3 of those lines (the #2423 reference and the avatar-exclusion rationale) carry all the content.

One non-blocking thought: the engine itself is well-tested, but nothing pins that update_persona actually calls it on a rename — a thin test at the propagation layer would close the #2423 path end-to-end and protect against a future refactor silently dropping the call. Fine as a follow-up.

Happy to approve once the size gate is green.

check-file-sizes reported personas/mod.rs at 985 against its 984 TEMP
ceiling, so Desktop Core and Desktop were red. The script's own policy
says not to add to the override list, so this trims the comment instead
of raising the limit.

The 10-line block above the retain loop is now 6 and keeps what carries
the content: the block#2423 reference, that record.name is in the published
identity projection, the stale-binding consequence, and why avatar-only
edits are excluded. 981 lines; check-file-sizes passes locally.

Signed-off-by: Sean Gearin <sgearin@gmail.com>
@SeanGearin

Copy link
Copy Markdown
Author

Fixed, and thanks for pointing at the comment rather than the override — the policy note in check-file-sizes.mjs is explicit that the list is not the place to absorb growth, and it would have been the wrong precedent to set on a file already queued to split.

The block above the retain loop is 10 lines down to 6, keeping what actually carries weight: the #2423 reference, that record.name is part of the published identity projection, the stale name→pubkey consequence, and why avatar-only edits are excluded. What went was the restatement of the failure mode and the pointer to the instance-rename path, both recoverable from the code itself. personas/mod.rs is now 981 lines and check-file-sizes passes locally.

On the propagation test — you are right that it is the real gap. The engine is covered and the call is not, so a refactor could silently drop it and every existing test would stay green.

The awkwardness is that the natural home for it is personas/mod.rs, which is exactly the file under the ratchet: a meaningful test there would re-cross 984 immediately and put us back where this review started. So rather than trade the comment for the test, I would rather do it properly as a follow-up — either alongside the split this file is already queued for, or in a sibling test module if you would prefer it sooner and have a placement in mind.

Happy to file the issue so it does not get lost. Say the word if you would rather see it in this PR and I will find the lines elsewhere.

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.

2 participants