[3/4] buzz personas: publish agent definitions from the CLI - #5844
Open
Maxwellimus wants to merge 1 commit into
Open
[3/4] buzz personas: publish agent definitions from the CLI#5844Maxwellimus wants to merge 1 commit into
Maxwellimus wants to merge 1 commit into
Conversation
Personas could only be created by clicking through Buzz Desktop, so nothing scriptable could stand up an agent roster — including the agents themselves. `buzz personas create|list|get|delete` writes the same kind:30175 coordinates Desktop reads, from a flag set or straight from a `.agent.json` export. These are owner-authored events, so the signing key IS the owner: no NIP-OA auth tag is involved, and running with a different key publishes to a coordinate space the owner's Desktop never reads. Publishing a definition does not start an agent; launching one mints key material and stays a Desktop operation. Two relay behaviors shape the implementation: - A write must be stamped past the coordinate's current head. NIP-33 breaks a `created_at` tie by lowest event id, so a same-second rewrite can otherwise lose to the event it was replacing. - `soft_delete_by_coordinate` matches `created_at <= tombstone`, and its result only feeds a debug log. A tombstone older than its target is accepted, deletes nothing, and reports OK — so delete stamps from the head it just read and then re-reads the coordinate to confirm. The re-read only raises a conflict for a head strictly newer than the tombstone, since a lagging replica can still return the deleted head. `--from` refuses rather than repairs a snapshot that would publish a persona Desktop can't mint from: `respondTo=allowlist` with no pubkeys, an unknown `respondTo`, or definition text carrying invisible characters. Avatars follow Desktop's reader rather than a CLI-only rule. `--avatar` takes a local image and carries it inline as a data URL when it fits the bounds Desktop renders inline — 8 KiB for SVG, 256 KiB for raster, both inside the relay's 256 KiB content cap — and uploads to media storage otherwise, publishing the returned URL. `--from` carries a snapshot's inlined avatar through that same path, so a Desktop export round-trips with its image. The upload runs after the `--replace` conflict check: an upload that a rejected write would strand leaves an orphan blob behind. Signed-off-by: Max Lampert <maxwell@squareup.com>
Maxwellimus
marked this pull request as ready for review
August 14, 2026 06:22
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.
Personas could only be created by clicking through Buzz Desktop, so nothing
scriptable could stand up an agent roster — including the agents themselves.
buzz personas create|list|get|deletewrites the same kind:30175 coordinatesDesktop reads, from a flag set or straight from a
.agent.jsonexport.These are owner-authored events, so the signing key is the owner: no NIP-OA
auth tag is involved, and running with a different key publishes to a coordinate
space the owner's Desktop never reads. Publishing a definition does not start an
agent — launching one mints key material and stays a Desktop operation.
Two relay behaviors shape the implementation
created_attie by lowest event id, so a same-second rewrite can otherwiselose to the event it was replacing.
soft_delete_by_coordinatematchescreated_at <= tombstone, and its resultonly feeds a debug log. A tombstone older than its target is accepted, deletes
nothing, and reports OK — so delete stamps from the head it just read, then
re-reads the coordinate to confirm. The re-read only raises a conflict for a
head strictly newer than the tombstone, since a lagging replica can still
return the deleted head.
Snapshots and avatars
--fromrefuses rather than repairs a snapshot that would publish a personaDesktop can't mint from:
respondTo=allowlistwith no pubkeys, an unknownrespondTo, or definition text carrying invisible characters.Avatars follow Desktop's reader rather than a CLI-only rule.
--avatartakes alocal image and carries it inline as a data URL when it fits the bounds Desktop
renders inline — 8 KiB for SVG, 256 KiB for raster, both inside the relay's
256 KiB content cap — and uploads to media storage otherwise, publishing the
returned URL.
--fromcarries a snapshot's inlined avatar through that samepath, so a Desktop export round-trips with its image. The upload runs after the
--replaceconflict check: an upload that a rejected write would strand leavesan orphan blob behind.
Test plan
just ciURL in the event), oversized upload (469 KiB PNG → media URL whose hash
matches the file's sha256), non-image → exit 1 with nothing published,
--avatar+--avatar-url→ exit 1.crates/buzz-cli/TESTING.md§6.13.Deferred
The CLI does not sanitize images before upload. Media storage rejects
images carrying metadata (EXIF, colour profiles, comments) as an identity
channel, and Desktop strips it via
sanitize_image_for_uploadbeforeuploading. The CLI has no equivalent, so an oversized metadata-bearing
avatar — a camera photo, most exported PNGs — fails the upload with a 422.
This PR makes that failure name its cause and the two workarounds rather than
surfacing a bare relay error; it does not make the upload succeed.
Deferred rather than fixed here because the sanitizer is ~300 lines spanning
animated PNG/WebP, GIF metadata stripping, and EXIF-orientation re-encoding,
and it belongs in a shared crate — the same extraction this stack's [1/4] and
[2/4] perform — where it also closes the identical pre-existing gap in
buzz upload file. Reimplementing the policy a second time inside the CLIwould risk drifting from the relay's validator.
No pagination past
list_owned's 500-event cap. It warns on stderr when ithits the cap rather than truncating silently.
The global
--formatflag isn't threaded through these subcommands, matching19 of the 24 existing dispatch arms.