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 unattachedrun() 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.