Skip to content

[3/4] buzz personas: publish agent definitions from the CLI - #5844

Open
Maxwellimus wants to merge 1 commit into
maxwell/sdk-definition-text-rulesfrom
maxwell/cli-personas
Open

[3/4] buzz personas: publish agent definitions from the CLI#5844
Maxwellimus wants to merge 1 commit into
maxwell/sdk-definition-text-rulesfrom
maxwell/cli-personas

Conversation

@Maxwellimus

@Maxwellimus Maxwellimus commented Aug 14, 2026

Copy link
Copy Markdown

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, 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

--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.

Test plan

  • just ci
  • Live against a local relay: inline pass-through (70-byte PNG → 118-char data
    URL 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.
  • Runbook rows added to 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_upload before
    uploading. 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 CLI
    would risk drifting from the relay's validator.

  • No pagination past list_owned's 500-event cap. It warns on stderr when it
    hits the cap rather than truncating silently.

  • The global --format flag isn't threaded through these subcommands, matching
    19 of the 24 existing dispatch arms.

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
Maxwellimus marked this pull request as ready for review August 14, 2026 06:22
@Maxwellimus
Maxwellimus requested a review from a team as a code owner August 14, 2026 06:22
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