Buzz Provider¶
BuzzProvider ¶
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
¶
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
¶
React to a relay event (kind 7) through the shared client.
remove_reaction
async
¶
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
¶
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 ¶
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.
on_owner_command
property
writable
¶
The owner-command callback; settable so a runner can take over.
parse_buzz_event ¶
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 ¶
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 ¶
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. |
required |
sources
|
Sequence[BuzzRelaySource]
|
Buzz relay sources to attach and supervise. Their
|
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 |
None
|
run
async
¶
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).
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.