Skip to main content

Expert Queue โ€” Item States

Reference: the four states an Expert Queue item moves through, and what each one means. Last updated: 2026-06-02 Audience: Experts, Engineering, AM, QA Surface: /workspace/queue (Expert workspace) Related: Expert Queue feature, Conversation Status Lifecycle, ADR-007 Expert Access Scope


TL;DR โ€” the one question that distinguishes themโ€‹

Every state answers "who is being waited on right now?"

UI labelDB valueWho's it waiting on?Terminal?
PendingpendingThe Expert (review + reply)No
Awaitingawaiting_clientThe client (their reply)No
ResolvedresolvedNobody โ€” closedYes
DeferredsnoozedNobody for now โ€” snoozedNo

One-liner: Pending = your turn ยท Awaiting = client's turn ยท Resolved = done ยท Deferred = look at it later.


State machineโ€‹

        new escalation / client reply / defer expiry
โ”‚
โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Pending โ”‚ โ† waiting on Expert
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
Expert replies Defer Resolve
โ”‚ โ”‚ โ”‚
โ–ผ โ–ผ โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Awaiting โ”‚ โ”‚ Deferred โ”‚ โ”‚ Resolved โ”‚
โ”‚ (client) โ”‚ โ”‚ (snoozed)โ”‚ โ”‚ (final) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚ โ”‚
client replies defer TTL expires
(automatic) (cron, automatic)
โ”‚ โ”‚
โ””โ”€โ”€โ”€โ–บPendingโ—„โ”€โ”˜

1. Pending โ€” pendingโ€‹

Meaning: the ball is in the Expert's court. The item is waiting for an Expert to review the AI draft and respond.

Entry triggers (three ways in):

  1. A conversation is escalated into the queue (createQueueItem())
  2. A client replies while the item was awaiting_client โ†’ auto-flips back to pending
  3. A deferred item's timer expires โ†’ cron resurfaces it to pending

Transitions out:

  • โ†’ Awaiting when the Expert sends a reply (POST /expert-queue/:id/respond)
  • โ†’ Resolved when the Expert resolves it (POST /expert-queue/:id/resolve)
  • โ†’ Deferred when the Expert defers it (POST /expert-queue/:id/defer)

2. Awaiting โ€” awaiting_clientโ€‹

Meaning: the ball is in the client's court. The Expert has already replied and is waiting for the client to respond. (UI may label this "Awaiting Reply".)

Entry trigger: an Expert sends a reply via POST /expert-queue/:id/respond. The atomic claim flips the item to awaiting_client and updates the underlying conversation.

Key constraint: an Expert cannot respond again to an awaiting_client item. They must wait for the client to re-trigger it back to pending (a force=true path is planned but not implemented). This prevents double-sends.

Transitions out:

  • โ†’ Pending automatically as soon as the client sends any reply
  • โ†’ Resolved / Deferred via explicit Expert action

3. Resolved โ€” resolvedโ€‹

Meaning: the conversation is closed. The Expert has taken final action; no further work is expected. This is a terminal state.

Entry trigger: an Expert explicitly resolves via POST /expert-queue/:id/resolve (or PATCH /expert-queue/:id/status). Stamps resolvedBy + resolvedAt.

Terminal guard: responding to a resolved item returns 409 Conflict โ€” re-opening it would overwrite the resolved state and lose the audit trail. Only an admin can manually move it back via PATCH /status (rare).

Transitions out: none automatic (terminal).


4. Deferred โ€” snoozedโ€‹

Meaning: temporarily set aside. The Expert wants to revisit it later, so it's hidden from the active queue until the snooze timer expires.

โš ๏ธ Naming mismatch โ€” read this. The UI says Deferred, the action endpoint is /defer, but the DB value is snoozed. When inspecting logs or the database, snoozed == the UI's "Deferred".

Entry trigger: Expert calls POST /expert-queue/:id/defer with optional deferHours (default 24h). Sets deferredUntil and removes it from the active list.

Auto-resurfacing: a BullMQ scheduled job (snooze-resurface) periodically finds items whose deferredUntil < now() and flips them back to Pending.

Transitions out:

  • โ†’ Previous status when the Expert manually reverts (POST /expert-queue/:id/revert). The status held immediately before deferral is captured in previous_status at defer time and restored exactly โ€” a deferred awaiting_client item reverts to awaiting_client, not pending. Falls back to Pending when previous_status is null (items deferred before the column existed). This is the only manual status change the Expert workspace UI offers (#2700).
  • โ†’ Pending automatically when the defer TTL expires (cron). Auto-expiry intentionally lands on Pending regardless of previous_status โ€” after the snooze window the item resurfaces for Expert attention.

Manual status changes & the PATCH /status override (#2700)โ€‹

The Expert workspace deliberately exposes no free-form status picker. Every transition happens through a dedicated, state-machine-validated action:

To changeโ€ฆUseAllowed from
โ†’ AwaitingPOST /respondPending (automatic on Expert reply)
โ†’ ResolvedPOST /resolve or PATCH /status (status=resolved)Pending / Awaiting
โ†’ DeferredPOST /deferPending / Awaiting
Deferred โ†’ previousPOST /revertSnoozed only

PATCH /expert-queue/:id/status validates the transition server-side: a non-admin caller may only move an item โ†’ resolved (the Resolve button). Invalid manual transitions โ€” pending โ†” awaiting_client, anything out of resolved (terminal), or out of snoozed (use /revert) โ€” return 409 Conflict. SuperAdmin bypasses the validation for the rare documented override (e.g. reopening a resolved item). Before #2700 the UI offered a dropdown that allowed all of these; the dropdown was removed and the server now enforces the rules regardless of client.


Naming reference (UI โ†” endpoint โ†” DB)โ€‹

UI labelAction endpointDB value
Pending(default on create)pending
AwaitingPOST /respondawaiting_client
ResolvedPOST /resolveresolved
DeferredPOST /defersnoozed

Key endpointsโ€‹

EndpointEffect
GET /expert-queue?status=pending|awaiting_client|resolved|snoozed|allList by status
POST /expert-queue/:id/respondPending โ†’ Awaiting
POST /expert-queue/:id/resolvePending/Awaiting โ†’ Resolved
POST /expert-queue/:id/deferโ†’ Deferred (snoozed), default 24h; captures previous_status
POST /expert-queue/:id/revertDeferred โ†’ previous status (manual un-defer); 409 if not snoozed
PATCH /expert-queue/:id/statusValidated: non-admin โ†’ resolved only; SuperAdmin override bypasses (see #2700 above)

Source of truthโ€‹

  • Entity + status column: api/src/common/entities.ts (ExpertQueueItem)
  • State transitions: api/src/expert-queue/expert-queue.service.ts
  • Client-reply auto-reopen: api/src/conversations/conversations.service.ts
  • UI labels: frontend/src/components/ui/TicketStatusBadge.tsx, frontend/src/app/workspace/queue/