Skip to content

Channel ABC

Channel

Channel(channel_id)

Bases: ABC

Base class for all channels.

sender_is_participant class-attribute instance-attribute

sender_is_participant = False

Whether this channel's sender_id is a room Participant.id.

An identity resolver maps an address — a number, an email, a handle — to an Identity. Most channels carry one: what arrives on sender_id is how the sender is reachable, and who that is remains to be looked up. A channel that sets this declares the opposite: its senders are named by the room itself, so there is no address to look up and identity resolution (RFC §11) is skipped for its messages.

Declaring it wrongly is not a small mistake: a channel that does carry addresses would stop resolving them, and every sender would stay unidentified.

trace_enabled property

trace_enabled

Whether any trace observers are registered.

provider_name property

provider_name

Provider or backend name for event attribution.

info property

info

Return channel metadata. Override in subclasses.

active_turns property

active_turns

How many turns this channel is running right now.

What a caller retiring the object needs to know: a channel taken out of the registry (displaced by a rebuild, or removed with the thing it served) may still be answering for a turn that captured it, and close on an intelligence channel ends that turn rather than waiting for it — an ACP channel cancels it on both sides of the wire, an AI channel tears the provider down under it. A channel that counts its turns answers here so the caller can wait until it is idle; one that does not count them answers 0 and is treated as idle.

recent_events_window property

recent_events_window

How many recent room events this channel reads per turn.

Drives how many events the framework loads into RoomContext for a room. Transport-only channels (WebSocket, realtime voice) don't consume room history, so the default is 0; AI channels override it with their memory provider's window.

supports_streaming_delivery property

supports_streaming_delivery

Whether this channel can accept streaming text delivery.

on_trace

on_trace(callback, *, protocols=None)

Register a protocol trace observer.

Parameters:

Name Type Description Default
callback TraceCallback

Called with each :class:ProtocolTrace. May be sync or async (coroutines are scheduled as tasks).

required
protocols list[str] | None

Optional allowlist of protocol names (e.g. ["sip"]). None means all protocols.

None

emit_trace

emit_trace(trace)

Emit a protocol trace to all registered observers.

resolve_trace_room

resolve_trace_room(session_id)

Resolve a room ID for a trace with the given session.

Override in session-based channels (voice, realtime voice) to map session IDs to room IDs. Returns None by default.

normalize_address

normalize_address(address)

address in the one form this channel compares and stores it in.

The framework reads every inbound sender through it before routing, and every address a host gives a binding or a member, so the same person is the same string wherever the room meets them (RFC §10.4). Unchanged by default; a channel addressed by phone number writes E.164 (+15550000001).

recipient_address

recipient_address(binding)

The correspondent's address binding delivers to, normalized; None if none.

On a channel whose replies go to the address the correspondent writes from, or to a chat (whose id is its conversation's address), a binding's recipient names its conversation as much as its participant_id does: the router admits no one else through it. None on any other channel (a webhook URL names no one).

recipient_metadata

recipient_metadata(address)

The binding metadata that makes address this channel's recipient.

The framework records an inbound sender as a binding's correspondent (RFC §10.4); on a channel whose replies go to the address the sender writes from (a phone number, an email address), the same address is where its replies go, and this is how the framework writes it where the channel reads it. Any other channel (an intelligence channel, a WebSocket, a webhook, a group chat) records nothing.

conversation_address

conversation_address(message)

The address naming the conversation message belongs to (RFC §10.4).

The sender's own by default. On a chat channel it is the chat the message was posted in: a private chat is its user's, a group chat the group's, which all its members share. The framework routes a message, records a binding's correspondent and admits a sender by it; the message's author stays the sender.

reply_metadata

reply_metadata(message)

The binding metadata that makes a room reply where message came from.

The sender's own address on a channel that replies to it (:meth:recipient_metadata), the chat or conversation the message was posted in on one that replies there (Telegram, Teams, Discord); nothing on a channel with no addressee of its own.

handle_inbound abstractmethod async

handle_inbound(message, context)

Process an inbound message into a RoomEvent.

deliver abstractmethod async

deliver(event, binding, context)

Deliver an event to this channel.

on_event async

on_event(event, binding, context)

React to an event. Default: no-op for transport channels.

supports_streaming_delivery_for

supports_streaming_delivery_for(room_id)

Whether this channel can stream into a specific room.

Defaults to the channel-wide answer, which is right for a channel whose capability does not vary by room — a voice session, a terminal. A channel that holds per-room client connections overrides this, so a room whose clients cannot stream does not take the streaming path only to fall back at the end.

deliver_stream async

deliver_stream(text_stream, event, binding, context)

Deliver a streaming text response to this channel.

Default: accumulate text, deliver as complete event. A response that fails is not delivered again to the channel that streamed it (RFC §12.2 step 13s), so what was accumulated is delivered before the failure propagates.

connect_session async

connect_session(session, room_id, binding)

Accept a long-lived session after inbound processing.

Called by process_inbound when message.session is present and hooks did not block. Override in session-based channels (voice, persistent WebSocket, etc.). Default: no-op.

disconnect_session async

disconnect_session(session, room_id)

Clean up a session on remote disconnect.

Override in session-based channels to release resources. Default: no-op.

update_binding

update_binding(room_id, binding)

Notify the channel that a room's binding has changed.

Called by the framework after mute(), unmute(), or set_access() update the store. Override in session-based channels (voice, realtime voice) to update cached binding state used for audio gating. Default: no-op.

on_room_attached async

on_room_attached(room_id, binding)

Establish whatever the new binding claims exists.

Awaited by attach_channel() after the binding is written and before anything has observed it — no system event, no hook. This is where a channel does the outside-world work an attachment implies: a conference channel creates the SFU room here (RFC §12.10.4 step 1).

Raising cancels the attachment. The framework takes the binding back and re-raises, so the caller learns that the channel refused rather than receiving a binding to something that was never built. Which is why this is not a hook: a lifecycle hook is observation, its errors are logged and never raised, and the room would go on believing it was attached. Default: no-op.

on_room_detached async

on_room_detached(room_id)

Take down what :meth:on_room_attached established.

Awaited by detach_channel() before the ON_CHANNEL_DETACHED hooks, so an integrator's handler runs after the channel has finished letting go rather than alongside it.

Nothing is rolled back if this raises — the binding is already gone and the detach already announced — but the error reaches the caller instead of disappearing into a log. Default: no-op.

capabilities

capabilities()

Return channel capabilities.

close async

close()

Close the channel and its provider.

extract_text staticmethod

extract_text(event)

Extract plain text from an event's content.

RealtimeModelHost

RealtimeModelHost

Bases: ABC

A channel whose realtime model's sessions take text injected into them.

A proactive instruction meant for the model, a background result handed back included, is injected into one of its sessions with the system intent (RFC §23.3 step 8), never published as the channel's own words. A realtime voice channel (audio-video included) and a conference with a realtime model plugged in are such channels; inheriting is the opt-in.

realtime_model_plugged property

realtime_model_plugged

Whether a realtime model is plugged in now, so its sessions take text.

get_room_sessions abstractmethod

get_room_sessions(room_id)

The model's sessions in room_id.

inject_text abstractmethod async

inject_text(session, text, *, role='user', silent=False, start_audio_stream=False, chain_depth=0)

Inject text into session with the intent role (RFC §12.4).

Not sent (not_sent, realtime_session_gone) when session is not one the host holds or the provider ended it. A host replaces a copy of a runtime mark in text before its provider takes it (:func:~roomkit.channels._realtime_host_hooks.injected_text, RFC §6.4): what comes this way (an instruction, a worker's hand-back) may quote others.

wait_idle abstractmethod async

wait_idle(room_id, timeout=15.0, *, session_ids=None)

Wait until the model's sessions in room_id are idle (RFC §22.2): no answer in flight and nobody heard speaking. session_ids restricts the wait to those sessions; raises TimeoutError.

hosts_realtime_model

hosts_realtime_model(channel)

Whether channel hosts a realtime model whose sessions take text now.