FabricFabricHarness
EcosystemChannels

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 buzz

The 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 — the ws: or wss: 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/events route.
  • BUZZ_SHARED_SECRET — an independently generated secret shared only by the tail and route.
  • BUZZ_AGENT — the persistent agent name, defaulting to assistant.
  • BUZZ_CONSUMER_ID — stable identity for this deployed tail and its durable state.
  • DATABASE_URL or standard PG* 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 --live

The 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 --json

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