> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Avatar bot dispatch queue

> How persona-to-room dispatches actually run — a dedicated job queue with deterministic job ids, one token-mint seam for humans and bots, roster verification, and deliberate-refusal handling

# Avatar bot dispatch queue

When you attach a persona to a room and build a join plan (see
[AI video avatar agents](/guides/ai-video-avatar-agents)), the join plan's
dispatch descriptor is not the whole story — a background dispatch runtime
carries it to a real in-room bot participant. The join-plan endpoint enqueues
one **job** onto a dedicated `avatar-bot-dispatch` queue, and a consumer in
the worker fleet resolves the room, mints the bot participant's access
token, and confirms the identity is actually present on the room's live
participant roster before the job completes. This page is the model behind
that loop.

## Why a dedicated queue

Dispatches run on their own queue, not the shared webhook-delivery queue a
consumer would otherwise tap into. A worker bound to a shared queue receives
every job shape on it, and an avatar bot dispatch must never consume — or
silently complete — an unrelated webhook-delivery job. The dedicated queue
keeps the two traffic classes apart and lets the dispatch consumer run a
small bounded concurrency without head-of-line blocking webhook delivery.

Enqueue is **best-effort**: the join-plan endpoint still returns its 200
even when the bus is unavailable, because the operator can re-run *Build
join plan* and the deterministic job id (below) collapses that re-run onto
the same job. A perfect join plan with no bus is plan-only — no dispatch is
queued, and the bot never enters the room.

## Deterministic job ids

Each (persona, room) pair gets a deterministic job id derived from a
bounded, collision-safe hash. Re-running *Build join plan* for the same
persona and room collapses onto the same id instead of enqueueing a second
dispatch, and a worker restart between enqueue and completion can never
mint a duplicate participant. The bot participant itself carries a stable
persona-derived identity, so the media layer keeps one tile per identity
even across retries.

## One token-mint seam

The consumer mints the bot's access token through the **same join seam the
dashboard join path uses**. There is one place in the platform where join
authorization lives, and the dispatch runtime reaches that place rather than
re-implementing it — so a bot can never slip past a gate a human would be
stopped at.

## Gate-consistency guarantee

Because the seam is shared, every admission gate applies to bot joins
exactly as it applies to human joins: the ban list, room lock,
SSO-required rooms, webinar registration, the waiting-room lobby, and
room-ended refusals. Those gates cannot drift apart for bot joins, because
both paths run the identical checks. The bot joins with standard
participant-level publish/subscribe permissions — no elevated grant.

## Roster verification

A minted token alone is not treated as success. The consumer checks the
room's live participant roster and requires the bot identity to be present
there before the job completes:

* A **pre-mint roster check** makes the job idempotent — if a completed job
  lost its result on a worker restart and re-runs, the bot is already on
  the roster and the job finishes without minting again.
* A **post-mint roster re-read** is the delivered-join evidence — the job
  completes only once the identity appears in the room's participant list,
  and it retries until it does or the attempt budget is spent.

If the roster read itself is unavailable, the mint's own gates have still
run and the stable bot identity keeps the append at-most-once.

## Failure modes

The queue is sized for absorbed blips and deliberate refusals, not poison
loops:

* **Transient blips (bus, database, media plane)** — a small retry budget
  with exponential backoff absorbs them without operator action; if all
  attempts exhaust, re-running *Build join plan* re-enqueues the same
  deterministic job id.
* **Bus unavailable at enqueue time** — enqueue is best-effort, so the
  join-plan response is plan-only; re-run once the bus recovers.
* **Deliberate admission refusals (banned, locked, SSO-required,
  webinar-registration required, room ended)** — the job **completes
  cleanly** instead of retrying. A gate that refuses on every attempt is a
  poison loop, and deliberately refusing a bot is a normal operating
  decision, not a fault that should fill the failure queue.
* **Malformed job payload** — fail closed and complete, so a one-off
  corrupt job never loops.

## Scope note

The queue carries video-room join intents only. The bot streams into the
self-hosted WebRTC room over the media layer — no outbound (MT) voice or
SMS leg is ever originated on this path.

## Related

<CardGroup cols={2}>
  <Card title="AI video avatar agents — guide" href="/guides/ai-video-avatar-agents">
    Persona registry, join plans, and render jobs end to end.
  </Card>

  <Card title="The video room model" href="/concepts/video-room-model">
    The room a live avatar persona is dispatched into.
  </Card>

  <Card title="Video room access tokens" href="/guides/video-room-access-tokens">
    The token scope and expiry semantics the dispatch mint inherits.
  </Card>

  <Card title="Async processing model" href="/concepts/async-processing-model">
    How Orbit's queued work is structured across the worker fleet.
  </Card>
</CardGroup>
