---
title: Queues (ScaiQueue)
path: concepts/queues
status: published
---

# Queues (ScaiQueue)

ScaiQueue is the async message bus that powers HITL workflows + general inter-flow messaging. ScaiFlow surfaces it as first-class node kinds (`queue_publish`, `queue_consume`, `queue_escalation`) plus a flow-level topology declaration.

## The flow-level topology

`flow.config.scaiqueue` declares the **scope + queues** this flow depends on, in one place:

```jsonc
"scaiqueue": {
  "scope": "finance-approvals",
  "queues": [
    { "slug": "expense-review", "purpose": "review", "consumer_mode": "fifo", "max_retries": 3 },
    { "slug": "expense-escalation", "purpose": "escalation", "consumer_mode": "priority" }
  ]
}
```

- **`scope`** — namespacing for all queues this flow uses. Inherited by any `queue_*` / `hitl_review` node that omits its own `scope` (the compiler back-fills it).
- **`queues`** — declared up-front so the deploy step can pre-provision them in ScaiQueue (idempotent — find-or-create).

### Queue field shape

| Field | Purpose |
|---|---|
| `slug` | Stable identifier referenced from node configs. |
| `display_name`, `description` | UX strings; not interpreted. |
| `consumer_mode` | `fifo` (default), `priority`, `deadline`, `broadcast`. |
| `max_retries` | 0–100; default 5. |
| `purpose` | `intake`, `processing`, `review`, `escalation`, `completed`, `dead-letter`, `custom` — used by the canvas Scope Designer for visual grouping. |

## Pre-deploy provisioning

When you deploy a flow whose `manifest.scaiqueue` is set, ScaiFlow's `DeployService` runs a provisioning pass against ScaiQueue **before** creating the Core in ScaiGrid:

1. `ensure_scope(slug)` — find-or-create the scope (idempotent, tolerates 409 races).
2. For each declared queue: `ensure_queue(scope_id, slug, ...)`.

If provisioning fails (missing ScaiQueue creds, permission errors, network failure), the deploy aborts with a 502 (`error: "scaiqueue_auth"` or `"scaiqueue_provision_failed"`) — saves you from a half-deployed Core that immediately crashes when it tries to publish.

Permissions required on the caller's principal: `scaiqueue:view` (to list existing scopes/queues) and `scaiqueue:manage` (to create missing ones).

## Per-node configs

### `queue_publish`

```jsonc
{ "scope": "finance-approvals",   // optional — inherited from flow scope
  "queue": "expense-review",
  "message_type": "decision-made",
  "payload": { /* arbitrary object */ } }
```

Compiles to `scaiqueue.publish(scope, queue, message_type, payload)`.

### `queue_consume`

```jsonc
{ "scope": "finance-approvals",
  "queue": "expense-review",
  "consumer_id": "approver-bot" }
```

Compiles to `scaiqueue.consume(scope, queue, consumer_id)`.

### `queue_escalation`

```jsonc
{ "scope": "finance-approvals",
  "escalation_queue": "expense-escalation",
  "reason": "timeout-exceeded" }
```

Compiles to `scaiqueue.escalate(scope, queue, reason)`.

### `hitl_review`

The big one. See [HITL and checkpoints](./hitl-and-checkpoints) — it compiles to a single `@checkpoint` with `hitl_target: {scope, queue, hitl_spec}`, NOT to a `queue_publish` followed by a checkpoint. The runtime owns the publish-and-resolve cycle.

## Canvas Scope Designer

In the Flow properties panel, the ScaiQueue Topology Editor lets you:

- Declare a scope.
- Add/remove queues (slug, consumer mode, retries, purpose).
- Import an existing scope (lists tenant scopes via `GET /v1/scaiqueue/scopes` and pulls their queues into the flow in one shot).

Queue-referencing nodes' property panels then switch from free-text to a queue-slug dropdown sourced from the topology — typo-safe.

## Reading state from the canvas

- `GET /v1/scaiqueue/scopes` — list all scopes visible to the tenant.
- `GET /v1/scaiqueue/scopes/{scope_id}/queues` — list queues in a scope.

Both proxied through `/v1/scaiqueue/*` so the canvas never sees per-tenant ScaiGrid credentials.
