Files
Max LampertandGitHub 582e993daf [3/4] buzz personas: publish agent definitions from the CLI
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 — 8 KiB for SVG, 256 KiB for raster, both inside
the relay's 256 KiB content cap. Anything larger is downscaled to 512px
and re-encoded, then re-checked against the bound: flat art typically
lands back inside it and never reaches media storage at all, and only
what still doesn't fit is uploaded.

Re-encoding is also what makes that upload work. Media storage refuses
images carrying metadata — EXIF, colour profiles, comments — as an
identity channel, so a photo straight off a camera failed outright.
Decoding and re-encoding drops metadata by construction rather than by
stripping known chunks, so it cannot drift from the relay's allowlist the
way a structural stripper would. EXIF orientation is baked into the
pixels first, because dropping the tag without applying it publishes the
avatar sideways. GIF passes through untouched, since re-encoding would
flatten animation, and WebP re-encodes to PNG because `image`'s WebP
encoder is lossless-only and would inflate a lossy source.

`--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>
2026-08-17 14:27:13 -07:00
..