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, with each replying channel's end under turns[channel_id] (loop_end_reason, an ACP agent's stop reason, ai_usage).

duplicate class-attribute instance-attribute

duplicate = False

The event is a replay of an existing idempotency key, not a new turn.

unavailable_targets class-attribute instance-attribute

unavailable_targets = None

Explicit addresses absent from the committed delivery plan.

Backfilled from execution by a deferred handle. None means no new plan was resolved (for example a duplicate); availability is not re-read on replay.

cancellation_reason class-attribute instance-attribute

cancellation_reason = None

Terminal delivery cancellation reason; never changes the committed event.

Backfilled by a deferred handle's wait() or cancel() after cleanup. An awaited process_inbound propagates the caller's own cancellation (CancelledError) after draining; a turn cut from elsewhere (the kit's close(), a lane that dropped the delivery) is returned instead, its end under turns, as a deferred caller reads it (RFC §10.1 step 18).

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

report_cascade

report_cascade(cascade)

Report what the caller's completed cascade delivered (RFC §10.1 step 18): its delivery set, the answers it committed, its record, and its error or cancellation unless the result already names an error.

The one report of a cascade, whichever call waited for it: an inbound event, a deferred delivery's handle, a regeneration.

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.

cancel async

cancel(*, reason='caller_cancelled', timeout=5.0)

Cancel this turn and drain its tools, generation and streams.

Returns the original result with cancellation_reason set. Already committed events remain stored. Other turns and shared providers are unaffected. Repeated calls are safe and preserve the first reason.

TimeoutError means cleanup is STILL running: retain the resources it uses and await this handle before disposing them. Cancellation of this call is propagated only after cleanup (or its timeout). Calling from the room's lane or under its lock raises RuntimeError. A completed handle is returned unchanged.

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. 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.

DeliveryOutcome

Bases: BaseModel

Outcome of a proactive RoomKit.deliver() request (RFC §22).

sent means publication or provider acceptance, not agent completion. inbound exposes the text turn's existing result/handle, in-process only. A duplicate identifies an earlier publication without replaying its turn. Keyed realtime injections reuse their recorded per-session outcomes.

session_outcomes class-attribute instance-attribute

session_outcomes = Field(default_factory=dict)

Per-session voice outcomes; child outcomes have no nested session outcomes.

VoiceDeliveryRecord

Bases: BaseModel

One reserved injection, retained until its room is deleted.

An absent outcome means unresolved submission, never permission to retry. attempt_id fences completion from previous owners. Only a recorded retryable failure known to precede submission can be claimed again.

key_hash property

key_hash

Bounded storage key; room isolation remains an explicit SQL predicate.

retryable property

retryable

Whether the completed attempt established a safe retry.

VoiceInjectionResult

Bases: BaseModel

Report what the provider knows after inject_text.

sent means its send operation completed, not that audio was heard. not_sent guarantees no submission or pending submission exists. unknown covers acceptance that cannot be established, including input queued locally for later sending. Only not_sent may allow retry. Exceptions and None returns are conservatively unknown.

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 content is delivered; report its actual outcome.

deliver abstractmethod async

deliver(ctx)

Deliver the request. A custom strategy may leave its outcome unknown.

Immediate

Bases: DeliveryStrategy

Send now. On a voice session, it plays once the playback under way has been sent whole: a response never cuts the agent off (RFC §12.2).

WaitForIdle

WaitForIdle(buffer=1.0, playback_timeout=15.0)

Bases: DeliveryStrategy

Wait for voice idle, then send; a timeout falls back to immediate.

Queued

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

Bases: DeliveryStrategy

Batch compatible unkeyed requests at idle; preserve each caller's outcome.

Keyed requests publish individually, including duplicate keys. One caller owns the drain; followers wait for their own request to be processed.

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)

Consume a terminal item (sent, blocked or unknown); removes it.

nack abstractmethod async

nack(item_id, error=None)

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

An item with retries left is attempted again after :func:retry_delay, not at once: a destination that is briefly unreachable (a voice session reconnecting) would otherwise spend every retry within milliseconds.

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 up to limit items of the dead-letter queue, the most recently dead-lettered first.

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.

max_retries class-attribute instance-attribute

max_retries = 3

Retries after the first attempt, as a binding's RetryPolicy counts them (RFC §13.2): 3 means four attempts at most.

DeliveryItemStatus

Bases: StrEnum

Lifecycle status of a delivery item.

InMemoryDeliveryBackend

InMemoryDeliveryBackend(max_queue_size=1000, max_dead_letter_size=1000, *, retry_delay=1.0, max_retry_delay=60.0)

Bases: DeliveryBackend

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

Items flow through _queue → _in_flight → acked/dead-lettered; a nacked item with retries left waits in _delayed before it is queued again. A background worker task drains the queue and executes deliveries.

Parameters:

Name Type Description Default
max_queue_size int

Bound on the queue.

1000
max_dead_letter_size int

Bound on the dead-letter queue (oldest dropped).

1000
retry_delay float

Seconds before the first retry; each next one waits twice as long, up to max_retry_delay. 0 retries at once.

1.0
max_retry_delay float

Cap on the wait before a retry.

60.0

get_queue_depth async

get_queue_depth()

Pending items, those waiting out a retry delay included.

start async

start(kit)

Start the background worker loop.

close async

close()

Stop the worker loop. In-flight items, and those waiting out a retry delay, are re-enqueued.