Skip to content

Buzz Provider

BuzzProvider

BuzzProvider(source)

Bases: BuzzRelayProvider

Outbound Buzz delivery via a shared :class:BuzzRelaySource client.

The provider delegates every send to the buzzkit.BuzzClient owned by the paired source. It does not manage the connection lifecycle — that stays with the source. Sends use the HTTP bridge, so they succeed even when the inbound WebSocket is mid-reconnect.

send async

send(event, to)

Post event to Buzz channel to (a relay channel UUID).

channel_data.thread_id (the thread-root Nostr event id, as set by the inbound parser) threads the message as a NIP-10 reply — the same provider-native contract Discord and Teams use.

send_reaction async

send_reaction(target_event_id, emoji)

React to a relay event (kind 7) through the shared client.

remove_reaction async

remove_reaction(reaction_event_id)

Retract our own reaction (kind 5) through the shared client.

BuzzConfig

Bases: BaseModel

Buzz relay agent configuration.

private_key is the agent's Nostr secret (nsec… or hex); it signs the agent's events and authenticates it to the relay (NIP-42/98).

from_env classmethod

from_env(**overrides)

Build a config from Buzz's reserved identity environment variables.

Reads BUZZ_PRIVATE_KEY (or its alias NOSTR_PRIVATE_KEY), BUZZ_RELAY_URL, and the optional BUZZ_AUTH_TAG — the exact env triplet every Buzz launcher hands its agents, so a RoomKit agent is launchable by the same bash script, systemd unit, or container entrypoint as any other Buzz agent. Identity is fail-closed: a missing or empty key or relay URL raises instead of building an identityless agent. overrides are passed through to the model (e.g. owner_pubkey=..., leave_on_stop=True).

MockBuzzProvider

MockBuzzProvider()

Bases: BuzzRelayProvider

Records sent messages for verification in tests.

Carries no buzzkit dependency and no relay client, so it can drive the delivery path without a live connection.

BuzzRelaySource

BuzzRelaySource(config, channel_id='buzz', *, relay_channel_id, parser=None, kinds=None, on_event=None, on_owner_command=None)

Bases: BaseSourceProvider

Persistent Buzz relay connection emitting one channel's messages.

Owns the :class:buzzkit.BuzzClient and exposes it via :attr:client so the paired provider can send through the same identity. Subscribes to a single relay channel (relay_channel_id); register one source per Buzz channel and bind each to its RoomKit room.

kinds selects the Nostr event kinds to subscribe to (default: chat messages, kind 9). Pass other kinds — e.g. huddle announcements, kind 48100 — together with a parser that knows how to convert them; the default parser only understands text messages.

on_event surfaces reaction lifecycle events (kind 7 add, kind 5 remove) as normalised dicts, outside the message pipeline — matching how Discord and WhatsApp-personal handle reactions. Providing it widens the default subscription to kinds 9, 7 and 5; requires a relay that scopes reactions to their target's channel (buzzkit>=0.2.0).

on_owner_command receives the owner's control commands ("shutdown", "cancel", "rotate") when config.obey_owner_commands is armed; when provided it owns the response to every command — including "shutdown", for which the source otherwise stops itself. See :class:BuzzAgent, which wires this to a whole-process graceful shutdown.

client property

client

Expose the underlying BuzzClient for outbound use.

channel_id property

channel_id

The RoomKit channel id this source feeds (attach_source key).

on_owner_command property writable

on_owner_command

The owner-command callback; settable so a runner can take over.

stop async

stop()

Stop receiving and close the relay connection.

parse_buzz_event

parse_buzz_event(event, channel_id, *, own_pubkey=None, ignore_own=True)

Convert a Nostr event dict into an :class:InboundMessage.

Duck-typed on a plain dict so it can be unit-tested without a relay. Returns None to skip the agent's own events (echo guard) and events with no text content.

huddle_announcement_parser

huddle_announcement_parser(channel_id, *, started_after=None)

Parser for huddle announcements (kind 48100).

Emits one :class:InboundMessage per announcement with the ephemeral huddle id in metadata["ephemeral_channel_id"]. Subscribe the source with kinds=[KIND_HUDDLE_STARTED].

started_after (unix seconds) drops announcements replayed from relay history — the subscription replays recent events before EOSE, and a restarted agent must not chase long-dead huddles.

Agent lifecycle

BuzzAgent

BuzzAgent(kit, sources, *, exit_after_inactivity=None, on_owner_command=None)

Lifecycle runner turning a RoomKit app into a conforming Buzz agent.

The agent owns waiting and dying, not wiring: rooms, channels and hooks stay the app's job, and the sources are handed over unattached — run() attaches them (with the callbacks already in place) so no owner command can slip through before the takeover.

Parameters:

Name Type Description Default
kit RoomKit

The configured RoomKit. run() closes it on exit.

required
sources Sequence[BuzzRelaySource]

Buzz relay sources to attach and supervise. Their on_owner_command is taken over by the agent (a warning is logged if one was already set).

required
exit_after_inactivity float | None

Optional idle bound in seconds — the agent stops itself after that long with no inbound dispatched and no broadcast in any room (the platform's opt-in self-stop; default off, and deliberately not named like the per-turn timeouts).

None
on_owner_command BuzzOwnerCommandCallback | None

Optional passthrough for "cancel"/"rotate" ("shutdown" is the agent's, always).

None

run async

run()

Serve until the owner, a signal, or the inactivity bound stops us.

Single-shot: the kit is closed on the way out, whatever the cause, so every exit is the same graceful path — sources stopped (presence offline published while the socket is up), channels drained and closed (RFC 12.10.4). Raises whatever kit.close() raises, after the rest of the shutdown ran to completion.

A failure during startup takes the same exit: whatever had already been started is stopped and the kit is closed before the exception reaches the caller.

BuzzAgentStopCause

Bases: StrEnum

Why :meth:BuzzAgent.run returned. Every cause exits gracefully.

Voice (huddles)

BuzzHuddleBackend

BuzzHuddleBackend(*, silence_fill=True, provider_input_rate=16000, provider_output_rate=24000, end_when_alone=True, empty_huddle_grace=90.0)

Bases: VoiceBackend

Realtime voice transport backed by a Buzz huddle.

The connection given to :meth:accept must be a connected buzzkit.HuddleClient. The backend owns it from that point on: it runs the client's event loop, and :meth:disconnect leaves the huddle and closes the socket.

Outbound pacing (one Opus frame per 20 ms) and the wire protocol live in buzzkit; this class only moves PCM bytes and session state.

silence_fill (default on) streams silence frames to the pipeline whenever no huddle audio is arriving — huddle senders go quiet between utterances (Opus DTX), but a realtime provider's server VAD needs to hear the post-speech silence to close the user's turn, exactly as it would from a continuously open microphone.

provider_input_rate / provider_output_rate are the realtime provider's PCM rates (defaults match Gemini Live: 16 kHz in, 24 kHz out). The backend resamples huddle audio (48 kHz) to/from those rates internally — leave the channel's transport_sample_rate unset.

end_when_alone (default on) ends the session when the last remote peer leaves the huddle. The relay keeps a huddle alive while ANY member is connected — this agent included — so without it the huddle and the provider session run forever. empty_huddle_grace is how long to wait for a first peer in a huddle that is empty at join time (the announcement can precede the creator's audio socket).

accept async

accept(session, connection)

Bind a connected buzzkit.HuddleClient to this session.

send_audio async

send_audio(session, audio)

Resample provider PCM to 48 kHz and hand it to the outbound pacer.

interrupt

interrupt(session)

Drop queued + in-flight outbound audio (barge-in).

end_of_response

end_of_response(session)

Signal the pacer that the current response is fully delivered.

BuzzHuddleWatcher

BuzzHuddleWatcher(kit, *, voice_channel, config, parent_channel_id, room_id, participant_id='buzz-agent', events_channel_id='buzz-huddle-events', join_attempts=3, client_factory=None)

Bridge every huddle announced on a Buzz channel to a voice channel.

Owns the whole announcement→call lifecycle so an application only builds its voice channel::

voice = RealtimeVoiceChannel("buzz-voice", provider=..., transport=BuzzHuddleBackend())
kit.register_channel(voice)
await kit.create_room(room_id="huddles")
await kit.attach_channel("huddles", "buzz-voice")

watcher = BuzzHuddleWatcher(
    kit,
    voice_channel=voice,
    config=BuzzConfig(relay_url=..., private_key=...),
    parent_channel_id=parent_uuid,
    room_id="huddles",
)
await watcher.start()

start() subscribes to huddle announcements (kind 48100) on the parent channel through a :class:~roomkit.sources.buzz.BuzzRelaySource attached with auto_restart=True — relay reconnection is the framework's job — and dials every announced huddle. The transport ends each call on its own (end_when_alone, or the relay dropping the socket); the watcher reads session.metadata["buzz_end_reason"] to choose between rejoining the same huddle (connection loss) and waiting for the next one (call over).

bridge(huddle_id) dials one known huddle directly (no watching). Calls are bridged one at a time; announcements that arrive mid-call are ignored. client_factory is injectable for tests.

start async

start()

Register the announcement channel + hook and attach the source.

bridge async

bridge(huddle_id)

Dial one huddle and keep the call bridged until it is over.