Buzz
Connect a Buzz Nostr relay to a durable Fabric agent with authenticated forwarding.
Buzz is a preview first-party channel adapter. A reconnecting Nostr tail subscribes to the relay and forwards events to the ordinary Fabric HTTP channel route through a timestamped HMAC envelope. The route verifies both that envelope and the inner Schnorr-signed event before dispatching.
fh add channel buzzThe recipe creates .fabricharness/channels/buzz.ts and
.fabricharness/state/buzz.ts, and .fabricharness/transports/buzz-tail.ts. Recipe v3 wires
content-free lifecycle and operational evidence in addition to durable state. Start the Fabric
server first, then start the tail from the same trusted deployment boundary:
import { createBuzzTail } from './.fabricharness/transports/buzz-tail.js';
const tail = createBuzzTail();
process.once('SIGTERM', () => tail.close());
process.once('SIGINT', () => tail.close());Configure:
BUZZ_RELAY_URL— thews:orwss:relay endpoint.BUZZ_PRIVATE_KEY— the adapter identity's 64-character lowercase hexadecimal secret key.BUZZ_COMMUNITY— the tenant boundary forwarded with each event.BUZZ_FORWARD_URL— the absolute Fabric/channels/buzz/eventsroute.BUZZ_SHARED_SECRET— an independently generated secret shared only by the tail and route.BUZZ_AGENT— the persistent agent name, defaulting toassistant.BUZZ_CONSUMER_ID— stable identity for this deployed tail and its durable state.DATABASE_URLor standardPG*variables — PostgreSQL/Databricks Lakebase connection.
Run the first-class integration preflight before starting or deploying the tail:
fh doctor --target buzz --json
fh doctor --target buzz --liveThe default check is network-free and fails closed for unsafe transport, placeholder credentials,
empty or duplicate channel scope, process-memory state, and obsolete recipe wiring. --live adds a
bounded, read-only certification pass: NIP-11 capability discovery, signed NIP-98 queries, NIP-01
integrity verification, relay and configured-channel membership, plus Lakebase/PostgreSQL cursor,
dead-letter, and decision-delivery health. Reports redact all private keys, forwarding secrets,
database connection strings, passwords, and event content. Configure the SLO with
BUZZ_MAX_RELAY_LAG_SECONDS, BUZZ_MAX_CURSOR_AGE_SECONDS, and BUZZ_MAX_DEAD_LETTERS.
Use the same server-owned channel allowlist in the tail and route. Applications that post governed
decision cards must inject the lookup view of the same durable delivery store used by
postBuzzDecisionCard() into the route:
import { buzzPublicKey, createBuzzChannel } from '@fabric-harness/channels/buzz';
import { buzzState } from '../state/buzz.js';
const channels = (process.env.BUZZ_CHANNELS ?? '').split(',').filter(Boolean);
export default createBuzzChannel({
agent: process.env.BUZZ_AGENT ?? 'assistant',
sharedSecret: process.env.BUZZ_SHARED_SECRET ?? '',
selfPubkey: buzzPublicKey(process.env.BUZZ_PRIVATE_KEY ?? ''),
community: process.env.BUZZ_COMMUNITY ?? 'local',
channels,
decisionReceiptStore: buzzState.deliveryStore,
onLifecycleEvent(notice) {
console.info(JSON.stringify({ integration: 'buzz', ...notice }));
},
});Use the built-in PostgreSQL/Lakebase implementation for production bridge state:
import { createPostgresBuzzPersistence } from '@fabric-harness/channels/buzz-postgres';
import { Pool } from 'pg';
export const buzzState = createPostgresBuzzPersistence({
client: new Pool({ connectionString: process.env.DATABASE_URL }),
consumerId: process.env.BUZZ_CONSUMER_ID ?? 'buzz-primary',
});It owns the monotonic tail cursor, unresolved dead-letter backlog, and exact signed
decision-card deliveries. It works with pg.Pool and Lakebase clients through a narrow
query() contract, without adding PostgreSQL or Databricks dependencies to core channel
authoring. The generated recipe uses this store and refuses to start its tail without a
database. Applications whose migration system owns DDL can set initialize: false and
call ensurePostgresBuzzTables() during migration.
inspectPostgresBuzzHealth() provides the same content-free, read-only evidence surface used by
the live doctor. It never selects stored event or card payloads, making it suitable for a
least-privilege Lakebase certification principal.
Protocol lifecycle
The default tail observes stock Buzz message edits, NIP-09/NIP-29 event deletions, and channel
deletions along with messages and reactions. These lifecycle events never enter agent or model
input. The optional onLifecycleEvent callback receives only event ids, author pubkey, kind,
channel scope, target ids, and decisionEffect: "none". A removed reaction is therefore audit
evidence; it cannot reverse a decision already committed by Fabric Platform.
BUZZ_PROTOCOL_SUPPORT is the machine-readable compatibility policy. Channel/forum messages and
reactions dispatch; edits and deletions are observe-only; membership is re-checked by the governed
application action at decision time. Direct messages, media, voice, and free-form approval editing
remain unsupported.
The stable instance key is (community, channel, thread root). Relay event IDs become durable
deduplication keys, author public keys become candidate actors, and events authored by the adapter
identity are ignored to prevent reply loops. Candidate identities do not grant authority: resolve an
active application binding and re-check the applicable capability grant before any governed action.
Outbound helpers include postInBuzzChannel(), replyInBuzzThread(), addBuzzReaction(), and
postBuzzDecisionCard(). Decision-card posting first persists the exact signed event and
receipt, retries that event id after failure, and queries the relay after ambiguous I/O.
In channels 0.19+, pass deliveryStore rather than the former receiptStore when creating
this tool. Stock Buzz Desktop reactions contain an e target but no h channel tag.
The route accepts that shape only when the target resolves to exactly one persisted card receipt in
the configured channels, then derives the channel from the receipt. Missing and ambiguous matches are
ignored, while receipt-store failures remain retryable delivery failures. This correlation recovers
transport scope only; it grants no authority. The application must still resolve the attested pubkey,
check membership and capability, validate expiry and pinned request state, and commit through its
governed compare-and-swap action.
This receipt-aware path is also the supported external handoff for approval requests raised by Databricks App-principal schedules, which are intentionally invisible to user-tenant approval discovery. The application's governed approval record associated with the card receipt must retain the exact scheduler session and approval ids and resolve the approved reaction through the normal stored approval boundary. A card is not a second authorization system, and a free-form reply is never an approval.
Keep the private key and forwarding secret out of agent definitions, model context, logs, and
persistent state. Use TLS in production and close the tail during shutdown so reconnect attempts stop
cleanly. The tail pages complete restart snapshots, enforces relay-advertised limits, propagates
cancellation and request timeouts, and fails closed when durable state or backfill is unavailable.
Persist poison events through deadLetterStore and run reconcileBuzzDeadLetters() from a bounded
scheduled worker; successful replay is acknowledged only after the normal ingress/governed path returns.
The tail's onOperationalEvent callback reports content-free backfill counts, forwarding outcomes,
authentication renewal, reconnect attempts, and shutdown. Callback failures go to onError and do
not interrupt the durable consumer. The live Buzz doctor adds cursor lag/age, dead-letter depth, and
decision-card delivery counts from PostgreSQL/Lakebase.
Reconcile the dead-letter backlog
Preview a bounded batch, then replay it through the ordinary HMAC-signed, Schnorr-verified ingress route:
fh buzz reconcile --dry-run --limit 100
fh buzz reconcile --limit 100 --jsonThe command uses BUZZ_CONSUMER_ID, the configured PostgreSQL/Lakebase connection, relay/community,
forward URL, and shared secret. It stops on the first unsuccessful replay, leaves that and later
records unresolved, and acknowledges each earlier record only after a 2xx response. Its report
contains counts and the consumer id, never stored event content. The limit must be between 1 and
1,000, and each HTTP request is time-bounded.
Databricks is first-class for the Fabric application side: use Lakebase for bridge state, Databricks service-principal/OBO boundaries for application access, and the existing Harness Databricks deployment/certification paths. The stock Buzz relay itself remains a separately pinned OCI/Kubernetes workload; Databricks Apps is not a compatible host for its NIP-42/WebSocket and image lifecycle requirements.