feat(desktop): personas-as-events foundation with config pinned at create#939
Draft
wpfleger96 wants to merge 14 commits into
Draft
feat(desktop): personas-as-events foundation with config pinned at create#939wpfleger96 wants to merge 14 commits into
wpfleger96 wants to merge 14 commits into
Conversation
This was referenced Jun 10, 2026
wpfleger96
added a commit
that referenced
this pull request
Jun 10, 2026
Ties the secrets-exclusion rule to the env_vars field removed in #939, making the doc-to-code mapping unambiguous. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
added a commit
that referenced
this pull request
Jun 11, 2026
…Body PersonaEventContent is the public struct serialized in kind:30175 events. PR #939 removed env_vars from it (secrets must not travel in plaintext events). This commit removes the re-addition from #945 and instead carries env_vars in PersonaEngramBody, which is NIP-44 encrypted inside the agent's mem/persona engram. Co-authored-by: Will Pfleger <wpfleger@block.xyz> Signed-off-by: Will Pfleger <wpfleger@block.xyz>
wpfleger96
added a commit
that referenced
this pull request
Jun 11, 2026
…Body PersonaEventContent is the public struct serialized in kind:30175 events. PR #939 removed env_vars from it (secrets must not travel in plaintext events). This commit removes the re-addition from #945 and instead carries env_vars in PersonaEngramBody, which is NIP-44 encrypted inside the agent's mem/persona engram. Co-authored-by: Will Pfleger <wpfleger@block.xyz> Signed-off-by: Will Pfleger <wpfleger@block.xyz>
9b8924a to
6c2e650
Compare
wpfleger96
added a commit
that referenced
this pull request
Jun 16, 2026
The Relay E2E job (new in #939) is the first to run these previously-skipped tests. Two asserted against `/channels/.../threads` and `/channels/.../messages` REST routes the relay never served (permanent 404); a third raced a live kind:44100 fan-out that a sibling subscription's drain silently discarded. Rewrite the thread read-backs against POST /query — the depth_limit + #e extension routes to get_thread_replies, the relay's real thread surface — and reorder the DM test to subscribe after create_dm so the persisted membership and discovery events are served deterministically from history. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
added a commit
that referenced
this pull request
Jun 17, 2026
Persona-created agents previously re-read the live persona catalog at every spawn, so a template edit silently changed running agents on restart. Snapshot the persona's system_prompt/model/provider/env_vars plus its content hash onto ManagedAgentRecord at create, and switch the spawn and provider-deploy call sites to read that snapshot. Restart reuses the record so it stays pinned; delete+respawn re-runs create and rewrites the snapshot. env_vars are pinned too — without that, persona credential edits would leak into a running agent on restart. The shared resolve_effective_prompt_model_provider body is left intact: its summary and ModelPicker callers intentionally read the live persona, so only the spawn/deploy call sites move to the snapshot. The Agents menu gets a drift indicator: build_managed_agent_summary compares the record's pinned source_version against the persona's current hash. A deleted persona is orphaned (never out-of-date, since there is nothing to respawn into). The summary now displays the pinned snapshot — what the agent actually runs — so the badge never contradicts the fields beside it. The mem/persona engram and fleet-update subsystem are removed: with the record authoritative, fleet-update became a self-referential write loop nothing read at spawn. Deletes fleet_update.rs, its launch and persona-save triggers, the engram-at-creation write, and the engram-only helpers in persona_events. The public persona catalog primitives (#939) are untouched — they remain the source the record snapshots from. Pre-existing agents are backfilled from the live persona once at launch, logging loudly if the linked persona is gone. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
added a commit
that referenced
this pull request
Jun 17, 2026
Persona-created agents previously re-read the live persona catalog at every spawn, so a template edit silently changed running agents on restart. Snapshot the persona's system_prompt/model/provider/env_vars plus its content hash onto ManagedAgentRecord at create, and switch the spawn and provider-deploy call sites to read that snapshot. Restart reuses the record so it stays pinned; delete+respawn re-runs create and rewrites the snapshot. env_vars are pinned too — without that, persona credential edits would leak into a running agent on restart. The shared resolve_effective_prompt_model_provider body is left intact: its summary and ModelPicker callers intentionally read the live persona, so only the spawn/deploy call sites move to the snapshot. The Agents menu gets a drift indicator: build_managed_agent_summary compares the record's pinned source_version against the persona's current hash. A deleted persona is orphaned (never out-of-date, since there is nothing to respawn into). The summary now displays the pinned snapshot — what the agent actually runs — so the badge never contradicts the fields beside it. The mem/persona engram and fleet-update subsystem are removed: with the record authoritative, fleet-update became a self-referential write loop nothing read at spawn. Deletes fleet_update.rs, its launch and persona-save triggers, the engram-at-creation write, and the engram-only helpers in persona_events. The public persona catalog primitives (#939) are untouched — they remain the source the record snapshots from. Pre-existing agents are backfilled from the live persona once at launch, logging loudly if the linked persona is gone. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
added a commit
that referenced
this pull request
Jun 17, 2026
Persona-created agents previously re-read the live persona catalog at every spawn, so a template edit silently changed running agents on restart. Snapshot the persona's system_prompt/model/provider/env_vars plus its content hash onto ManagedAgentRecord at create, and switch the spawn and provider-deploy call sites to read that snapshot. Restart reuses the record so it stays pinned; delete+respawn re-runs create and rewrites the snapshot. env_vars are pinned too — without that, persona credential edits would leak into a running agent on restart. The shared resolve_effective_prompt_model_provider body is left intact: its summary and ModelPicker callers intentionally read the live persona, so only the spawn/deploy call sites move to the snapshot. The Agents menu gets a drift indicator: build_managed_agent_summary compares the record's pinned source_version against the persona's current hash. A deleted persona is orphaned (never out-of-date, since there is nothing to respawn into). The summary now displays the pinned snapshot — what the agent actually runs — so the badge never contradicts the fields beside it. The mem/persona engram and fleet-update subsystem are removed: with the record authoritative, fleet-update became a self-referential write loop nothing read at spawn. Deletes fleet_update.rs, its launch and persona-save triggers, the engram-at-creation write, and the engram-only helpers in persona_events. The public persona catalog primitives (#939) are untouched — they remain the source the record snapshots from. Pre-existing agents are backfilled from the live persona once at launch, logging loudly if the linked persona is gone. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
added a commit
that referenced
this pull request
Jun 17, 2026
The Relay E2E job (new in #939) is the first to run these previously-skipped tests. Two asserted against `/channels/.../threads` and `/channels/.../messages` REST routes the relay never served (permanent 404); a third raced a live kind:44100 fan-out that a sibling subscription's drain silently discarded. Rewrite the thread read-backs against POST /query — the depth_limit + #e extension routes to get_thread_replies, the relay's real thread surface — and reorder the DM test to subscribe after create_dm so the persisted membership and discovery events are served deterministically from history. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
93a291e to
289bfc2
Compare
wpfleger96
added a commit
that referenced
this pull request
Jun 17, 2026
The Relay E2E job (new in #939) is the first to run these previously-skipped tests. Two asserted against `/channels/.../threads` and `/channels/.../messages` REST routes the relay never served (permanent 404); a third raced a live kind:44100 fan-out that a sibling subscription's drain silently discarded. Rewrite the thread read-backs against POST /query — the depth_limit + #e extension routes to get_thread_replies, the relay's real thread surface — and reorder the DM test to subscribe after create_dm so the persisted membership and discovery events are served deterministically from history. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
289bfc2 to
2fb370f
Compare
wpfleger96
added a commit
that referenced
this pull request
Jun 17, 2026
Persona-created agents previously re-read the live persona catalog at every spawn, so a template edit silently changed running agents on restart. Snapshot the persona's system_prompt/model/provider/env_vars plus its content hash onto ManagedAgentRecord at create, and switch the spawn and provider-deploy call sites to read that snapshot. Restart reuses the record so it stays pinned; delete+respawn re-runs create and rewrites the snapshot. env_vars are pinned too — without that, persona credential edits would leak into a running agent on restart. The shared resolve_effective_prompt_model_provider body is left intact: its summary and ModelPicker callers intentionally read the live persona, so only the spawn/deploy call sites move to the snapshot. The Agents menu gets a drift indicator: build_managed_agent_summary compares the record's pinned source_version against the persona's current hash. A deleted persona is orphaned (never out-of-date, since there is nothing to respawn into). The summary now displays the pinned snapshot — what the agent actually runs — so the badge never contradicts the fields beside it. The mem/persona engram and fleet-update subsystem are removed: with the record authoritative, fleet-update became a self-referential write loop nothing read at spawn. Deletes fleet_update.rs, its launch and persona-save triggers, the engram-at-creation write, and the engram-only helpers in persona_events. The public persona catalog primitives (#939) are untouched — they remain the source the record snapshots from. Pre-existing agents are backfilled from the live persona once at launch, logging loudly if the linked persona is gone. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
added a commit
that referenced
this pull request
Jun 17, 2026
Persona-created agents previously re-read the live persona catalog at every spawn, so a template edit silently changed running agents on restart. Snapshot the persona's system_prompt/model/provider/env_vars plus its content hash onto ManagedAgentRecord at create, and switch the spawn and provider-deploy call sites to read that snapshot. Restart reuses the record so it stays pinned; delete+respawn re-runs create and rewrites the snapshot. env_vars are pinned too — without that, persona credential edits would leak into a running agent on restart. The shared resolve_effective_prompt_model_provider body is left intact: its summary and ModelPicker callers intentionally read the live persona, so only the spawn/deploy call sites move to the snapshot. The Agents menu gets a drift indicator: build_managed_agent_summary compares the record's pinned source_version against the persona's current hash. A deleted persona is orphaned (never out-of-date, since there is nothing to respawn into). The summary now displays the pinned snapshot — what the agent actually runs — so the badge never contradicts the fields beside it. The mem/persona engram and fleet-update subsystem are removed: with the record authoritative, fleet-update became a self-referential write loop nothing read at spawn. Deletes fleet_update.rs, its launch and persona-save triggers, the engram-at-creation write, and the engram-only helpers in persona_events. The public persona catalog primitives (#939) are untouched — they remain the source the record snapshots from. Pre-existing agents are backfilled from the live persona once at launch, logging loudly if the linked persona is gone. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Squashed for rebase — original commits: - feat(desktop): add persona event kind with client publish/read/retain - fix(desktop): sign every migrated persona event and drop the sentinel file - fix(relay): validate kind:30175 persona d-tag slug grammar on ingest - fix(desktop): drop env_vars from public PersonaEventContent - docs: add NIP-AP spec for kind:30175 persona events - feat(ci): add relay E2E testing job and persona event tests
Missed during rebase: sprout_core→buzz_core_pkg imports in persona_events.rs and migration.rs, sprout-desktop→buzz-desktop in log messages, and cargo fmt on e2e_persona.rs.
cargo fmt requires buzz_core_pkg imports before nostr (alphabetical). Relay E2E git tests need git-credential-nostr built alongside the relay.
…ay behavior The relay now has a generic file upload path that accepts PDFs, random bytes, and unrecognised formats as application/octet-stream downloads. SVG with XML declaration is not detected by `infer` and also routes through the generic path. Updated four content validation tests from expecting rejection (400/415) to expecting acceptance (200). Additionally, three WebSocket imeta tests hit /api/events but the relay route is at /events (no /api prefix). Fixed the URL in all three. Co-authored-by: Will Pfleger <wpfleger@squareup.com> Signed-off-by: Will Pfleger <wpfleger@squareup.com>
infer detects XML-based SVG as text/xml (not image/svg+xml), which is not in the blocked list, so the relay accepts it through the generic file path with that MIME type. Co-authored-by: Will Pfleger <wpfleger96@gmail.com> Signed-off-by: Will Pfleger <wpfleger96@gmail.com>
Same class of issues as e2e_media_extended — tests assumed image-only policy and wrong API path. The relay ignores Content-Type headers (uses magic bytes) and the route is /events not /api/events. Co-authored-by: Will Pfleger <wpfleger96@gmail.com> Signed-off-by: Will Pfleger <wpfleger96@gmail.com>
The live_split_model_completes test is a manual runbook test requiring multiple serve nodes. Replace panic!() with println+return so it skips gracefully when CI runs --ignored tests. Co-authored-by: Will Pfleger <wpfleger96@gmail.com> Signed-off-by: Will Pfleger <wpfleger96@gmail.com>
The Relay E2E job (new in #939) is the first to run these previously-skipped tests. Two asserted against `/channels/.../threads` and `/channels/.../messages` REST routes the relay never served (permanent 404); a third raced a live kind:44100 fan-out that a sibling subscription's drain silently discarded. Rewrite the thread read-backs against POST /query — the depth_limit + #e extension routes to get_thread_replies, the relay's real thread surface — and reorder the DM test to subscribe after create_dm so the persisted membership and discovery events are served deterministically from history. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
test_nip10_thread_reply_not_in_top_level asserted only that a reply threads under its root — a correlate, not the relay's actual top-level rule. get_channel_messages_top_level surfaces a depth-1 reply iff broadcast = true, so a broadcast=1 depth-1 reply satisfied every old assertion yet IS surfaced at top level: the test greened by data accident. The relay exposes no top-level-queryable surface over POST /query (feed_types routes to feed queries that never read thread_metadata.depth/broadcast), so the rule is pinned via its two test-observable inputs — recorded depth and the broadcast tag — in both directions: a non-broadcast depth-1 reply is excluded, a broadcast=1 depth-1 reply is surfaced. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
cargo fmt --all --check failed Rust Lint on the assertions added in the prior commit (lines 925, 938). Whitespace/reflow only; no assertion or logic change. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The relay's clock-skew guard rejects events with created_at too far from server time. Replace hardcoded Nov 2023 timestamps (1_700_000_000) with Timestamp::now()-relative values that stay within the skew window while preserving the older-vs-newer replacement semantics. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…2e_relay tests
Tests assumed HTTP REST endpoints (/api/events, /api/users/{pubkey}/profile,
/api/users/me/channel-add-policy) that the relay does not serve. The relay
router only exposes /events, /query, /count, and a few other paths.
Three fix patterns applied:
- POST /api/events → POST /events (the relay's actual HTTP bridge)
- GET /api/users/{pubkey}/profile → POST /query with kind:0 filter
- PUT /api/users/me/channel-add-policy → submit kind:10100 event via POST /events
Also fixed self-add test that hit nostr crate's default p-tag stripping
(EventBuilder removes p tags matching the signer unless allow_self_tagging
is called), and added a 1s sleep in kind0_nip05_sync to ensure the
replacement event gets a strictly newer created_at timestamp.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The relay-e2e job (introduced on this branch, absent on main) ran the full `--test '*'` glob across all buzz-test-client e2e suites. That swept in three suites targeting a REST `/api/*` surface that no longer exists in the relay binary — the surface was migrated to the Nostr HTTP bridge and torn out, leaving e2e_rest_api (37), e2e_tokens (19), and e2e_workflows (7) as 63 tests that 404 against a route the relay does not mount. This job was added to exercise the persona/interop work, so scope it to the two suites it covers: e2e_persona and e2e_nostr_interop. Reimplementing the REST surface is separate, non-gating work and should not block this merge base from going green. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
e6a44d7 to
7c98207
Compare
wpfleger96
added a commit
that referenced
this pull request
Jun 18, 2026
Persona-created agents previously re-read the live persona catalog at every spawn, so a template edit silently changed running agents on restart. Snapshot the persona's system_prompt/model/provider/env_vars plus its content hash onto ManagedAgentRecord at create, and switch the spawn and provider-deploy call sites to read that snapshot. Restart reuses the record so it stays pinned; delete+respawn re-runs create and rewrites the snapshot. env_vars are pinned too — without that, persona credential edits would leak into a running agent on restart. The shared resolve_effective_prompt_model_provider body is left intact: its summary and ModelPicker callers intentionally read the live persona, so only the spawn/deploy call sites move to the snapshot. The Agents menu gets a drift indicator: build_managed_agent_summary compares the record's pinned source_version against the persona's current hash. A deleted persona is orphaned (never out-of-date, since there is nothing to respawn into). The summary now displays the pinned snapshot — what the agent actually runs — so the badge never contradicts the fields beside it. The mem/persona engram and fleet-update subsystem are removed: with the record authoritative, fleet-update became a self-referential write loop nothing read at spawn. Deletes fleet_update.rs, its launch and persona-save triggers, the engram-at-creation write, and the engram-only helpers in persona_events. The public persona catalog primitives (#939) are untouched — they remain the source the record snapshots from. Pre-existing agents are backfilled from the live persona once at launch, logging loudly if the linked persona is gone. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96
added a commit
that referenced
this pull request
Jun 18, 2026
Persona-created agents previously re-read the live persona catalog at every spawn, so a template edit silently changed running agents on restart. Snapshot the persona's system_prompt/model/provider/env_vars plus its content hash onto ManagedAgentRecord at create, and switch the spawn and provider-deploy call sites to read that snapshot. Restart reuses the record so it stays pinned; delete+respawn re-runs create and rewrites the snapshot. env_vars are pinned too — without that, persona credential edits would leak into a running agent on restart. The shared resolve_effective_prompt_model_provider body is left intact: its summary and ModelPicker callers intentionally read the live persona, so only the spawn/deploy call sites move to the snapshot. The Agents menu gets a drift indicator: build_managed_agent_summary compares the record's pinned source_version against the persona's current hash. A deleted persona is orphaned (never out-of-date, since there is nothing to respawn into). The summary now displays the pinned snapshot — what the agent actually runs — so the badge never contradicts the fields beside it. The mem/persona engram and fleet-update subsystem are removed: with the record authoritative, fleet-update became a self-referential write loop nothing read at spawn. Deletes fleet_update.rs, its launch and persona-save triggers, the engram-at-creation write, and the engram-only helpers in persona_events. The public persona catalog primitives (#939) are untouched — they remain the source the record snapshots from. Pre-existing agents are backfilled from the live persona once at launch, logging loudly if the linked persona is gone. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the personas-as-events architecture: defines a new Nostr event kind for persona definitions, adds client-side serialization, a local SQLite retention store for offline boot, migrates existing
personas.jsonentries to events, and pins each agent's spawn config onto its record at create time so editing a template never silently mutates running agents.Changes
Relay side
crates/buzz-core/src/kind.rs: RegisterKIND_PERSONA = 30175as NIP-33 parameterized replaceable, keyed by(pubkey, kind, d_tag)whered_tagis the plaintext persona slug. Compile-time assertion verifies range membership.crates/buzz-relay/src/handlers/ingest.rs: Allowlist kind:30175 underScope::UsersWrite, mark as global-only (never channel-scoped). New tests verify scope and global-only classification.Desktop client side
desktop/src-tauri/src/managed_agents/persona_events.rs(new): SerializePersonaRecord↔ kind:30175 event. JSON content body containsdisplay_name,system_prompt,avatar_url,runtime,model,provider,name_pool,env_vars. Publish/fetch functions via relay HTTP API.desktop/src-tauri/src/managed_agents/retention.rs(new): SQLite retention store withINSERT OR REPLACEon(kind, pubkey, d_tag)for NIP-33 latest-wins semantics. Pending-sync queue for deferred relay publish. Enables offline boot when relay is unreachable.desktop/src-tauri/src/managed_agents/personas.rs: Addload_from_retentionhelper alongside the existingpersonas.jsonread path.desktop/src-tauri/src/migration.rs:migrate_personas_to_eventsruns after identity resolution, signs every retained event with the owner's real keys (no placeholder events). Idempotent via retention-row-exists check (the data is the sentinel). Core logic extracted intomigrate_personas_in_dirfor unit testing.desktop/src-tauri/src/lib.rs: Migration call moved afterresolve_persisted_identityso keys are always available. Owner keys cloned fromAppStateand passed to migration.desktop/src-tauri/Cargo.toml: Addrusqlitedependency with bundled feature.Spawn config pinned to the agent record
system_prompt/model/provider/env_varsplus a content hash onto theManagedAgentRecord(persona_snapshotinpersona_events.rs). New record fieldsproviderandpersona_source_version, both#[serde(default)].runtime.rs::spawn_agent_child,agents.rs::build_deploy_payload) — a running agent stays on the config it was created with across restarts.build_managed_agent_summarycompares the pinned hash vs the persona's current hash).restore.rs::backfill_persona_snapshots);#[serde(default)]means oldmanaged-agents.jsonfiles deserialize cleanly, no migration needed.mem/personaengram and thefleet_update.rsself-referential loop.Key design decisions
INSERT OR REPLACEkeyed on(kind, pubkey, d_tag)in SQLite. NIP-33 replaceable semantics handle relay-side idempotency.ManagedAgentRecordsnapshot is the source of truth for spawn/deploy, so template edits never silently mutate running agents.