22 KiB
Buzz Communication Adapter
Veritas Kanban can map a Buzz community channel to Squad Chat and move signed
root messages and replies in both directions. The integration uses Buzz's
native Nostr HTTP and WebSocket contracts. It does not spawn buzz,
buzz-acp, or buzz-agent for delivery.
Buzz is a communication adapter, not an AgentProvider. It does not create a
Veritas task, start an ACP agent, synchronize DMs or forums, or read Buzz
Desktop's internal state. An operator may separately materialize selected
public Buzz persona and team definitions as disabled Veritas profiles and
roster members. Definition import is data-only and never starts a process.
Task execution is a separate seam. A disabled-by-default buzz-agent
configuration uses provider acp-stdio and the generic ACP client. It never
turns relay delivery into task completion and never launches buzz-acp.
Selected task tools are exposed only through the provider-neutral
veritas-run bridge, with an opaque task/attempt/catalog/manifest binding;
native server credentials and the global Veritas MCP inventory are not passed
to Buzz.
See Buzz Agent ACP.
Supported contract
The adapter is fixture-pinned to:
- Buzz release
0.4.24 - Buzz commit
710ed9fff57878a1d69f809b80a6ee0416c53fc4 - Veritas probe revision
1 - Relay software identity
https://github.com/block/buzz - Required NIPs
11,29, and42 - Optional enforced relay membership advertised as NIP
43
The initial event projection is:
| Buzz surface | Veritas behavior |
|---|---|
Kind 9 root message |
Creates one Squad Chat message in the mapped target. |
Kind 9 reply |
Creates one threaded Squad Chat reply using Buzz root/reply tags. |
| Veritas root | Signs and publishes one kind 9 event with the mapped h channel tag. |
| Veritas reply | Publishes a direct or nested reply with the exact Buzz root/reply markers. |
Kind 40003 edit |
Records bounded audit metadata. Existing Squad Chat text is unchanged. |
Kind 9005 or NIP-09 kind 5 delete |
Records bounded deletion metadata. Local content is not removed. |
| Unknown or malformed kind | Ignores it with a redacted delivery audit entry. |
| Reactions, files, canvas, forums, DMs | Not projected. |
An unknown Buzz version is unsupported. Veritas may still read public NIP-11
metadata, but it will not connect the worker or send messages until the pinned
compatibility contract passes.
Identity and least privilege
Use a dedicated Buzz/Nostr identity. Add that public identity only to the community and channels that Veritas must bridge. Veritas does not need channel creation, moderation, desktop storage, or broad community administration.
Keep the private key in the Veritas server environment and store only its environment-variable reference:
BUZZ_PRIVATE_KEY=<set outside source control>
BUZZ_AUTH_TAG=<optional NIP-OA owner attestation>
The signing key may be 64-character private-key hex or nsec. It must match
the configured 64-character public-key hex. BUZZ_AUTH_TAG is needed only
when an agent identity receives membership through a NIP-OA owner.
Never put an nsec, private-key hex, auth tag, token, authorization header, or
raw signed event in a Settings field, API response, task, log, screenshot, or
support packet.
Configure and map a channel
In Settings -> Notifications -> Buzz Connection, configure:
- Relay HTTP URL, such as
https://community.example.com - Optional matching WebSocket URL; Veritas derives it when omitted
- Expected community host and optional non-default port
- Buzz channel UUID to map to Squad Chat
- Public-key hex
env:BUZZ_PRIVATE_KEY- Optional
env:BUZZ_AUTH_TAG - Explicit localhost/private-network allowances when required
- Optional
buzz,buzz-acp, orbuzz-agentexecutable for version diagnostics
HTTP and WebSocket endpoints must have the same host, port, path, and TLS posture. Credentials, query strings, and fragments are rejected. A configured path and non-default port are preserved because Buzz binds the community to the request authority.
The Settings save writes the reference-only adapter first, then writes the Squad Chat channel mapping. Changing a channel disables the old mapping before enabling the new one. Conflicting enabled mappings for the same target are rejected.
API setup
settings:write is required to configure, map, send, reconcile, disable, or
disconnect. settings:read can read adapters, mappings, health, and delivery
history.
Configure the connection:
curl -X PUT http://localhost:3001/api/integrations/communication/adapters/buzz-default \
-H 'Content-Type: application/json' \
-H 'X-API-Key: <veritas-api-key>' \
--data '{
"kind": "buzz",
"displayName": "Buzz",
"enabled": true,
"relayHttpUrl": "https://community.example.com",
"expectedCommunity": "community.example.com",
"publicKey": "<64-hex-public-key>",
"credentialRef": "env:BUZZ_PRIVATE_KEY",
"authTagRef": "env:BUZZ_AUTH_TAG"
}'
Map one Buzz channel to Squad Chat:
CHANNEL_ID=123e4567-e89b-42d3-a456-426614174000
curl -X PUT \
"http://localhost:3001/api/integrations/communication/adapters/buzz-default/buzz/channels/${CHANNEL_ID}" \
-H 'Content-Type: application/json' \
-H 'X-API-Key: <veritas-api-key>' \
--data '{
"target": { "kind": "squad" },
"enabled": true,
"actor": "operator"
}'
Run the compatibility probe:
curl \
http://localhost:3001/api/integrations/communication/adapters/buzz-default/health \
-H 'X-API-Key: <veritas-api-key>'
vk doctor --json
POST .../buzz-default/test runs the same read-only probe. It never sends a
message.
Import persona and team definitions
In Settings -> Agents -> Buzz Persona and Team Definitions, an operator can list, preview, and explicitly import public Buzz definition heads:
- kind
30175persona definitions keyed by(author, kind, d tag); - kind
30176team definitions keyed the same way; and - the deterministic NIP-33 head with the greatest
created_at, using the lowest event ID to break a tie.
The importer reuses the signed, DNS-pinned Buzz /query transport and requires
current healthy compatibility evidence. Every candidate has bounded Nostr
shape, tags, content, JSON depth, arrays, strings, and batch size. Its
signature is reconstructed and verified before it can appear in Settings.
Invalid envelopes contribute only to a rejected count. A signature-valid
current head with rejected content appears as a non-importable coordinate and
field-level validation reason, so Veritas never silently falls back to an older
definition. Unsafe source values are not echoed into the UI or logs.
Preview classifies each field before mutation:
| Buzz definition field | Import behavior |
|---|---|
Persona display_name |
Source-owned profile display name. |
Persona system_prompt |
Source-owned profile prompt when present. |
Persona avatar_url |
Validated public metadata only. Veritas does not fetch it. |
Persona runtime, model, provider |
Source preferences only, never runtime evidence or active provider configuration. |
Persona name_pool |
Bounded source metadata only. |
| Persona reserved response fields | Source-only. Veritas does not apply them. |
Team name, description |
Source-owned roster fields for create or refresh. |
Team persona_ids |
Same-author persona slugs resolved to linked profiles and disabled roster members. |
| Unknown fields | Ignored with a field-level forward-compatibility explanation. |
| Secrets, environment, commands, paths, managed process state, MCP, hooks, skills, engrams | Rejected. |
The available actions are:
create: use the deterministicbuzz-<slug>profile ID orbuzz-team-<slug>roster ID;link: attach provenance to an explicitly selected existing profile or roster without replacing local fields;refresh: replace only the saved source-owned fields after a new preview; andskip: record no local mutation.
Create, link, and refresh require collision-free preview. A preview returns an optimistic local revision and exact source event ID. Import rejects a changed local target or replaced source, so the operator must review the current diff instead of overwriting concurrent edits. Native profile/roster fields remain authoritative; refresh preserves local-only fields and existing routing rules.
New persona profiles, new rosters, and imported roster members are disabled.
Import never launches, enables, routes, installs, fetches, or writes back to
Buzz. Removing or replacing a Buzz definition changes its linked-source status
to missing or changed; it does not delete the materialized local object.
Definition API:
GET /api/integrations/communication/adapters/:adapterId/buzz/definitions
GET /api/integrations/communication/adapters/:adapterId/buzz/definitions/links
POST /api/integrations/communication/adapters/:adapterId/buzz/definitions/preview
POST /api/integrations/communication/adapters/:adapterId/buzz/definitions/import
Reads and preview require settings:read. Import requires settings:write.
There is no continuous synchronization and no Buzz write-back endpoint.
Trigger a Veritas workflow
A buzz-workflow-trigger/v1 rule can bind one active channel mapping to one
Veritas workflow. The first supported event is a root kind 9
message.posted. Replies, edits, deletes, reactions, adapter-originated
echoes, disabled rules, and predicate mismatches do not launch a run.
Create a rule with the mapping ID returned by the channel-mapping API:
curl -X POST \
http://localhost:3001/api/integrations/communication/adapters/buzz-default/buzz/workflow-triggers \
-H 'Content-Type: application/json' \
-H 'X-API-Key: <veritas-api-key>' \
--data '{
"mappingId": "buzz_map_example",
"workflowId": "triage-external-request",
"contentIncludes": "help"
}'
An exact 64-character author public key may be supplied as authorPubkey.
Content matching is a bounded, case-insensitive substring check. There is no
regex, expression, code, shell, or arbitrary Nostr-kind filter.
Rule creation requires settings:write and execute permission on the
destination workflow. The mapping alone does not grant workflow or task
mutation rights.
Veritas persists the accepted causal key
buzz:{community}:{eventId}:{ruleId} before dispatch through the
provider-neutral workflow.pre-external-trigger hook. Workflow context
retains the community, channel, event, author, mapping, rule, and sanitized
message. A replay returns the existing run. After restart, Veritas searches
the destination workflow's run context for the causal key before launching
another run.
List or disable rules and inspect bounded disposition history:
GET /api/integrations/communication/adapters/:adapterId/buzz/workflow-triggers
POST /api/integrations/communication/adapters/:adapterId/buzz/workflow-triggers/:ruleId/disable
GET /api/integrations/communication/adapters/:adapterId/buzz/workflow-trigger-audits
Disabling a rule retains its prior audits and linked workflow runs.
Send roots and replies
Send a root and associate it with a local Squad Chat message:
curl -X POST \
http://localhost:3001/api/integrations/communication/adapters/buzz-default/send \
-H 'Content-Type: application/json' \
-H 'X-API-Key: <veritas-api-key>' \
--data '{
"target": {
"kind": "squad",
"squadMessageId": "msg_local_root"
},
"message": "Root message from Veritas",
"actor": "VERITAS"
}'
Send a reply by identifying both the new local message and the local parent:
curl -X POST \
http://localhost:3001/api/integrations/communication/adapters/buzz-default/send \
-H 'Content-Type: application/json' \
-H 'X-API-Key: <veritas-api-key>' \
--data '{
"target": {
"kind": "squad",
"squadMessageId": "msg_local_reply"
},
"replyToSquadMessageId": "msg_local_root",
"message": "Reply from Veritas",
"actor": "VERITAS"
}'
The parent must already have a durable Buzz event mapping. A missing parent is blocked instead of publishing a detached root.
Each outbound event includes the mapped h tag, a client=veritas-kanban
marker, and a stable veritas-id delivery marker. Veritas persists the signed
event and event ID before submitting it to /events.
Inbound subscription and replay
One supervised WebSocket worker runs per enabled, compatible Buzz connection. It:
- resolves and pins the configured relay address through the outbound network policy;
- answers the NIP-42 challenge with kind
22242and the optional NIP-OA auth tag; - subscribes only to enabled mapped channel UUIDs and kinds
9,40003,9005, and5; - resumes from the persisted cursor with a five-second overlap;
- verifies each Nostr event signature, channel, kind, timestamp, and size;
- projects and audits the Squad Chat message; and
- commits the total-order cursor
(created_at, event_id)only after the projection and audit are durable.
Deduplication uses (community, event_id), never timestamp alone. Squad Chat
uses the deterministic local ID msg_buzz_<event-id>, so a crash after the
chat write but before adapter-state persistence replays safely. The original
Buzz author public key, source timestamp, event kind, channel, community,
event ID, and buzz://message link remain attached as external metadata.
An out-of-order reply is persisted but does not advance the cursor past its
missing root. When the root arrives, queued replies are replayed in
(created_at, event_id) order. A root that never arrives remains bounded
pending state rather than becoming a detached Squad Chat message.
Adapter-originated event IDs are retained and ignored when echoed by the subscription, preventing a reply loop.
Ambiguous delivery recovery
Network failure after a write can leave delivery status unknown. Veritas does not blindly retry:
- the delivery remains visible as
delivery_unknown; POST .../buzz-default/pollqueries/queryby the signed event ID;- if the event exists, the original delivery becomes
success; - if the relay definitively reports absence, Veritas resubmits the exact persisted signed event; and
- if the query is inconclusive, the delivery remains unresolved.
Before query or resubmission, the persisted event is re-verified against its signature, configured public identity, mapped community/channel, and event ID. A corrupted record is failed and never retried.
Health and audit
Health separates:
- compatibility and authorization checks;
- relay transport connection;
- active subscription state;
- mapped-channel count;
- reconnect attempts and last connection;
- last inbound event;
- cursor lag;
- last send time and status; and
- the latest redacted worker error.
Compatibility can be healthy while runtime status is degraded, such as when
no channel is mapped or the subscription is still connecting. canSend
requires an enabled adapter, current healthy compatibility evidence, and at
least one mapped channel. canReceiveReplies additionally requires an active
subscription.
Delivery history exposes queued, success, delivery_unknown, replayed,
ignored, failed, blocked, and skipped. It retains bounded coordinates
and redacted details, not credentials or raw authorization material.
Network policy
Public HTTPS/WSS is the default. Plain HTTP/WS requires an explicit localhost or private-network allowance. Localhost and RFC1918/IPv6 ULA ranges are denied unless their matching setting is enabled. Link-local, cloud metadata, and CGNAT ranges remain blocked. DNS is resolved and pinned, redirects are disabled, payloads are bounded, and requests have fixed timeouts.
Enable only the narrow network class required by the relay.
Credential-free release gate
Run the composed Buzz gate from the repository root:
pnpm test:buzz:compatibility
This command runs the existing credential-free fixtures for:
- pinned relay/community/identity compatibility and secret non-retention;
- signed root/reply communication, reconnect replay, dedupe, loop prevention, and ambiguous delivery recovery;
buzz-agentthrough generic ACP;- the opaque run-scoped Veritas MCP bridge;
- persona/team preview, import, refresh, provenance, and unsafe-source rejection;
- the typed root-message workflow trigger and causal replay; and
- the machine-readable compatibility record.
The canonical support record is still
GET /api/config/harness-compatibility. Its Buzz entry pins release 0.4.24,
commit 710ed9fff57878a1d69f809b80a6ee0416c53fc4, buzz-agent 0.1.0,
provider probe revision, fixture revision, and every seam fixture path. A
provider build, protocol, capability, probe, configuration, or fixture change
invalidates prior certification.
The green aggregate gate means only the following:
| Capability | Disposition |
|---|---|
| Relay/community/identity diagnostics | Supported at the pinned baseline |
| Mapped roots/replies, replay, dedupe, and loop prevention | Supported |
buzz-agent through generic ACP |
Supported at the pinned ACP contract |
| Run-scoped Veritas MCP | Supported through the provider-neutral bridge |
| Public persona/team import | Supported as explicit one-way materialization |
| One typed root message to a Veritas workflow | Supported |
buzz-acp as a Veritas provider |
Rejected; it is the inverse Buzz-owned harness |
| Buzz workflow definition execution and cross-system approvals | Deferred |
| NIP-AE memory sync and automatic NIP-34 task mirroring | Deferred or rejected |
| Desktop internals, DMs, forums, canvas, moderation, huddles, and mobile | Not implied by this gate |
An unknown or changed Buzz build remains unsupported or degraded until the baseline, evidence digest, fixtures, and documentation are explicitly updated.
Optional live smoke
Live smoke is supplemental and must target the exact candidate build with a dedicated least-privilege identity. It is not normal CI:
- Run
vk doctor --jsonand retain only redacted public build/status fields. - Publish one Veritas root to a dedicated mapped test channel.
- Reply from Buzz and verify the reply appears in the correct Squad Chat
thread, not merely as an HTTP success or ACP
end_turn. - Trigger one allowlisted test workflow and verify its causal event/run link.
- Disable the test mapping, rule, and profile.
Do not upload raw events, private messages, authorization headers, auth tags, private keys, provider keys, or unredacted logs as evidence.
Disable, upgrade, and rollback
Disabling a channel mapping closes and rebuilds the worker without deleting the mapping, cursor, event coordinates, or delivery audit. Disconnecting the adapter closes the worker and disables delivery while retaining reference-only configuration and recovery state.
Remove environment secrets separately only when retiring the identity. Veritas never removes relay membership, changes a Buzz community, or modifies Buzz Desktop state.
After a Buzz upgrade, run vk doctor --json. A version/build change
invalidates prior compatibility evidence and must pass the pinned contract
and pnpm test:buzz:compatibility before workers, sends, or ACP dispatch
resume. A baseline change updates the release/commit constants, matrix
evidence digest, fixtures, and this guide together.
If a candidate fails, keep the prior baseline and report the failing facet. Disable the affected adapter, mapping, profile, or trigger rule without deleting redacted configuration, mappings, cursors, import provenance, trigger audits, or workflow-run evidence.