Skip to content

Delivery

Delivery Models

InboundMessage

Bases: BaseModel

A message received from an external provider.

For stateful channels (voice, persistent WebSocket), set session to the session object. After the hook pipeline passes, process_inbound will call channel.connect_session() to bind the long-lived session to the room.

InboundResult

Bases: BaseModel

Result of processing an inbound message.

error carries a generation/transport failure raised while consuming the intelligence channel's streaming response, so a headless caller (no streaming target to render an error card) can observe it and react — instead of the failure vanishing after ON_ERROR fires. None on success. Interactive callers ignore it; the ON_ERROR hooks still fire.

response_metadata is the turn's own record, for the same reader and the same reason. It also rides each MESSAGE segment the turn persisted, but only segments that had text to carry: a turn ending on a tool call persists nothing after it, so the room cannot be asked how such a turn ended. The caller is handed the record instead of hunting for it.

response_metadata class-attribute instance-attribute

response_metadata = Field(default_factory=ResponseMetadata)

The turn's response-metadata record; empty when no turn ran.

response_events class-attribute instance-attribute

response_events = Field(default_factory=list)

Persisted, delivered response events belonging to this call's cascade.

Contains reentry responses and streamed segments, excluding the original inbound, blocked events and unrelated turns in the same room. A consumer reads this collection to attribute an answer to its call; a timeline read after the inbound index can include a later or concurrent call's answer. Deferred callers must await delivery.wait() for the complete collection. Events retain their stored indices, visibility and post-hook content.

delivery_results class-attribute instance-attribute

delivery_results = Field(default_factory=dict)

Per-channel outcome of this event's delivery set, keyed by channel id (RFC §10.1 step 18). process_inbound waits for that set to complete, so this is populated by the time it returns — for the caller's own event only, never for a reentry's, which is a separate event with its own result. A deferred call returns before the set executes: there it is backfilled by delivery.wait() instead.

delivery class-attribute instance-attribute

delivery = None

Set only by process_inbound(..., defer_delivery=True): the handle on the in-flight delivery (RFC §10.1 step 18 detached completion). None on the waiting path, where the result already reports the completed set — and on a deferred call refused before the locked region (rate limited, pre-commit timeout, identity block), which has no delivery to follow. Whenever blocked is False the handle is there; a hook refusal, decided inside the locked region, gets one too (its near-empty cascade resolves at once).

DeliveryHandle

DeliveryHandle(cascade, consumer, result)

A deferred caller's grip on its in-flight delivery (RFC §10.1 step 18).

process_inbound(..., defer_delivery=True) returns at the commit; this handle, on :attr:InboundResult.delivery, is what remains of step 18: the event's delivery set, the reentry passes it transitively spawns (an AI reply included) and the consumption of streamed responses, all running in the room's delivery lane. wait() resolves once that whole tail has run — not merely the cascade, because a streamed reply is only generated while its stream is consumed, which starts after the cascade completes.

The cascade is structurally typed (:class:_CascadeLike): models do not import from :mod:roomkit.core.

done property

done

Whether the deferred delivery will make no further progress.

True once the turn finished — but also for a consumer cancelled by close(), where the turn was abandoned mid-flight: this reports "nothing more will happen", not "everything ran". wait()'s backfill is where the two read differently.

wait async

wait()

Wait for the deferred delivery to complete, then report it.

Backfills delivery_results, error and response_metadata on the result this handle belongs to — after this the result reads exactly like a non-deferred call's — and returns that result. Never raises: a consumer cancelled by close() resolves the wait too, with whatever the cascade recorded by then.

Called from a context that must not wait on this room's delivery — the room's own lane executor (a tool handler) or under the room lock (a sync hook), where the lane cannot progress past the caller — it returns the result immediately, unwaited and un-backfilled: the same short-circuit the waiting path's step 18 applies, with delivery following in lane order.

DeliveryResult

Bases: BaseModel

The outcome of delivering one event to one channel (RFC §5.13).

ProviderResult

Bases: BaseModel

Result from a provider delivery attempt.

Delivery Strategies

DeliveryStrategy

Bases: ABC

Controls when and how content is delivered to a channel.

deliver abstractmethod async

deliver(ctx)

Deliver the content according to this strategy.

Immediate

Bases: DeliveryStrategy

Send now. May interrupt ongoing TTS playback.

WaitForIdle

WaitForIdle(buffer=1.0, playback_timeout=15.0)

Bases: DeliveryStrategy

Wait for TTS/speech to finish, then send.

Parameters:

Name Type Description Default
buffer float

Extra seconds to wait after playback ends (default 1.0).

1.0
playback_timeout float

Max seconds to wait for playback (default 15.0).

15.0

Queued

Queued(buffer=1.0, playback_timeout=15.0, separator='\n\n')

Bases: DeliveryStrategy

Add to queue, deliver at next idle window.

Multiple deliveries are batched into a single message.

Parameters:

Name Type Description Default
buffer float

Extra seconds to wait after playback ends (default 1.0).

1.0
playback_timeout float

Max seconds to wait for playback (default 15.0).

15.0
separator str

Text between batched items (default newline).

'\n\n'

Delivery Backend

DeliveryBackend

Bases: ABC

ABC for persistent delivery queue backends.

Implementations provide a durable queue with at-least-once semantics via the enqueue / dequeue / ack / nack lifecycle.

enqueue abstractmethod async

enqueue(item)

Add a delivery item to the queue.

dequeue abstractmethod async

dequeue(worker_id, batch_size=1, timeout=5.0)

Claim up to batch_size items. Blocks up to timeout seconds.

ack abstractmethod async

ack(item_id)

Acknowledge successful delivery — removes the item.

nack abstractmethod async

nack(item_id, error=None)

Negative-acknowledge. Re-enqueues or dead-letters the item.

dead_letter abstractmethod async

dead_letter(item_id, error)

Move an item to the dead-letter queue.

get_queue_depth abstractmethod async

get_queue_depth()

Return the number of pending items (observability).

get_dead_letter_items abstractmethod async

get_dead_letter_items(limit=50)

Return items in the dead-letter queue.

start async

start(kit)

Called by RoomKit on startup. Override to start a worker loop.

close async

close()

Called by RoomKit.close(). Override for graceful shutdown.

DeliveryItem

Bases: BaseModel

Serializable delivery request — the unit of work in the queue.

DeliveryItemStatus

Bases: StrEnum

Lifecycle status of a delivery item.

InMemoryDeliveryBackend

InMemoryDeliveryBackend(max_queue_size=1000, max_dead_letter_size=1000)

Bases: DeliveryBackend

Asyncio-queue-based delivery backend (single process, no persistence).

Items flow through _queue_in_flight → acked/dead-lettered. A background worker task drains the queue and executes deliveries.

start async

start(kit)

Start the background worker loop.

close async

close()

Stop the worker loop. In-flight items are re-enqueued.