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

# Prompt queue

> Queue, merge into the current turn, send now — and why queue state is a full snapshot rather than a delta.

The prompt queue lets you type follow-up instructions while the agent is still
working.

There are only four actions on the queue bar, and that is deliberate. This page covers
what each one does, plus the one design decision behind them: **queue state is a full
snapshot, not a delta**. It happens to be a good illustration of how Kova handles
frontend state in general.

## Isolated per thread

<Note>
  Queues are **isolated per thread**: one FIFO per `threadId`, and one serial chain per
  thread. Turns in different threads run in parallel and never block each other.
</Note>

That isolation fixes the messiest class of early problems: when a new prompt arrived
while the previous turn was still running and went straight to `agent.prompt()`, it
hit the `Agent is already processing` guard. Now it enters that thread's FIFO and is
executed in order by that thread's serial chain.

So "the queue" is not one global box — it is **one queue per conversation**. Queueing
in session A never blocks session B.

## The four actions

<CodeGroup>
  ```text Queue theme={null}
  Enter sends it to the queue; it is dispatched in order once the current turn ends.

  Steer
  Does not interrupt the running turn, but hands the message to the agent already working.

  Promote
  Jumps the queue and dispatches immediately.

  Cancel
  Removes it from the queue.
  ```
</CodeGroup>

Editing, pause/resume, and failure circuit-breaking were **removed on purpose** during
the refactor — they are not oversights:

* **No pause**: a dispatched item has already left the snapshot, so there is no locked
  state to speak of; when the previous turn wraps up the chain automatically takes the
  head of the queue and runs
* **No circuit breaker**: a failed item is reflowed into a bubble when the stream ends,
  and whether to retry is your call — not an automatic gate deciding that the next few
  should stop too

## Where the earlier problems came from

<Columns cols={2}>
  <Column title="Symptoms and root cause">
    Queue state was not persisted, the source of truth was scattered, and the frontend
    performed multiple surgeries on the message array. After a refresh or restart,
    queued messages vanished and the queue bar turned into a ghost; the message array
    needed extract/refill/restore/suppress paths, plus recovery races, duplicate
    reconciliation appends, and ordering churn; any side stream finishing reset the
    session status to ready, making the button flicker.
  </Column>

  <Column title="The answer now">
    The queue was rewritten as a pure state machine, `QueueEngine` — queue, auto-increment
    id, and dispatch settlement all live in one class with no I/O. **Every change**
    appends a full-snapshot line to the session transcript, so it can be restored after a
    restart, a thread switch, or tree navigation; the same full snapshot is broadcast to
    the thread, where the frontend applies "last snapshot wins".
  </Column>
</Columns>

<Note>
  The reason for full snapshots over deltas is plain: a queue rarely exceeds five items,
  so full is the simplest option, has no reconciliation logic, and broadcasts rarely.
</Note>

### How a snapshot travels

* **Persisted**: every change appends a `queue_state` full-snapshot line to the session
  JSONL
* **Restored**: after a sidecar restart it is replayed via `get_queue_state`, and it
  **no longer auto-pauses**
* **Broadcast**: every change sends `data-queue-state` to that thread via
  `sendEventChunk`
* **Conflict**: the frontend applies "last snapshot wins". Snapshots are dropped silently
  when the thread has no active request — idle-state changes are initiated by the
  frontend's own invoke, so the frontend updates itself from the reply

<Note>
  The "same id updated in place across phases" incremental chunk shape has been
  **removed entirely**. Enqueue, position, and dispatch are all carried by full
  snapshots; there are no per-item lifecycle chunks.
</Note>

## Three frontend sync rules

Every operation the frontend performs on the message array collapses to three
idempotent rules:

| Rule | Trigger | What it does |
| - | - | - |
| R1 queued | The snapshot contains it | The message lives only in the queue bar (extracted from the Chat array to prevent duplicates) |
| R2 dequeued | It disappears from the snapshot (the turn actually starts) | The message is appended to the end of the list; if there is no original bubble after a refresh, it is rebuilt from the snapshot text |
| R3 steered | Merged into the current turn | Keeps "refill on host-turn wrap-up" — refilling immediately would grow duplicates and reorder messages inside the host turn's write window |

Retired paths — restore race compensation, cancelled flags, direction branching in
reconciliation — are all replaced by "snapshot state plus three rules".

## Stable ids

Items carry a **persisted auto-increment id** that never repeats across restarts, and
both dispatch and cancellation address items by that id.

This is not a detail: addressing by array index meant that after a restart an index
mismatch would cancel the wrong item.

## Attachments survive

Early snapshots persisted text only, so bubbles rebuilt after a refresh lost their
attachments. That was fixed — snapshot entries now carry image attachments (protocol
shape `{ name, mimeType, data | path }`, identical to a prompt frame), so restore after
a refresh or restart, relay-pump re-dispatch, and queue bar thumbnails all keep the
image.

<Note>
  There is a trap worth remembering here: the sidecar originally inferred the shape from
  a `type: "image"` field **the frontend never sent**, so it silently dropped every
  image — meaning no historical `queue_state` line carries an attachment, and nothing
  errored. The lesson: align protocol shapes with the ones actually in use rather than
  inventing a new field.
</Note>

## Known boundary

Refreshing between "sent" and "confirmed": the registry lives in frontend memory, so a
refresh clears it. The item is then rebuilt from the snapshot (attachments included),
which is acceptable.

## Next

<CardGroup cols={2}>
  <Card title="Modes overview" icon="sliders" href="/en/features/modes">
    Five different "modes" in one app — do not conflate them.
  </Card>

  <Card title="Conversation workspace" icon="message" href="/en/features/conversation">
    Where the queue bar sits in the conversation stream.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.