Skip to content

Message queue

Inbound messages don’t go directly to an agent. They’re written to a durable SQLite queue first. The queue processor picks them up, runs the agent, and handles retries. This means the daemon can restart mid-flight without losing work.

The queue is FIFO per thread with cross-thread parallelism. Two messages in the same thread are processed sequentially. Two messages in different threads can be processed in parallel, up to the configured concurrency limit.

Each item in queue_items tracks:

ColumnDescription
statuspending, claimed, processing, completed, failed, dead_letter
attemptsHow many times processing has been attempted
max_attemptsDefault 3; after this the item moves to dead-letter
next_retry_atWhen the item becomes eligible for the next attempt
errorLast error message

When the daemon starts, it scans for items in claimed or processing status. These were in-flight when the process died. The daemon resets them to pending and requeues them. You don’t need to do anything — this happens automatically on startup.

If an agent run fails, the queue item is retried with exponential backoff:

  • Base interval: 1 second
  • Multiplier: 2x per attempt
  • Max interval: 60 seconds
  • Jitter: added to avoid thundering herd

For example, with three max attempts: first retry at ~1s, second at ~2s, third at ~4s. After the third failure the item is moved to the dead-letter queue.

Items that exhaust their retry budget land in dead_letter status. They stay in the database but are never retried automatically. This gives you a record of what failed and why.

Inspect dead-letter items:

Terminal window
npx talonctl queue-purge --status dead_letter --dry-run

Once you’ve investigated, purge them:

Terminal window
npx talonctl queue-purge --status dead_letter

You can also purge all items regardless of status:

Terminal window
npx talonctl queue-purge --all

Two levels of concurrency control:

queue:
maxConcurrent: 5 # global limit across all threads and personas
personas:
- name: assistant
maxConcurrent: 2 # per-persona limit

The effective concurrency for a persona is min(persona.maxConcurrent, global.maxConcurrent). Set maxConcurrent on latency-sensitive personas to ensure they aren’t starved by a backlog in another persona.

Not all queue items come from channel messages:

TypeSource
messageInbound message from a channel connector
scheduleA due scheduled task
collaborationA2A task from another persona

All types go through the same queue, retry, and dead-letter logic.

Terminal window
npx talonctl status

This reports queue depth broken down by status. A growing pending count with no movement indicates a stuck processor. A growing dead_letter count indicates a systemic agent failure worth investigating.