Skip to content

Add tryDequeueEvent(queueName) to orchestration context #8

@affandar

Description

@affandar

Problem

Duroxide currently exposes only blocking dequeueEvent(queueName) for orchestration event queues.

Some orchestrations need to greedily drain already-queued events into their own replay-safe buffer before processing. With the current API, there is no way to ask "is the queue empty right now?" or "pop one event only if something is already queued".

The only available workaround is heuristic:

  • repeatedly race dequeueEvent("messages") against a short timer such as 3s
  • if a message wins, buffer it and race again
  • if the timer wins, assume the queue is drained enough for this pass

That works, but it is not exact:

  • it adds arbitrary latency
  • it depends on a magic timeout
  • one burst can get split across multiple rounds
  • the orchestration cannot distinguish a truly empty queue from a temporarily quiet queue

Concrete use case

PilotSwarm has parent orchestrations that receive a mixed stream of child updates, user prompts, and management messages on the same "messages" queue.

The parent wants to:

  1. drain already-queued events into a replay-safe KV work buffer
  2. process that buffered work one item at a time
  3. avoid continue_as_new() with a large unmatched-event backlog, because carry-forward overflow can drop messages

Without a non-blocking dequeue primitive, we have to simulate drain completion with a quiet-window timer. That makes correctness and latency depend on the timeout instead of actual queue state.

Proposal

Add a native orchestration primitive such as:

const evt = yield* ctx.tryDequeueEvent("messages");
// evt is null if the queue is currently empty

Semantics:

  • returns the next already-queued event if one exists
  • returns null immediately if the named queue is empty
  • does not block waiting for future messages
  • preserves normal queue ordering

Why this matters

  • enables exact drain-until-empty logic
  • removes heuristic quiet timers such as 3s races
  • lowers latency for bursty workloads
  • simplifies queue-to-KV buffering patterns
  • makes orchestration behavior more predictable under load

Alternative

If tryDequeueEvent() is not desirable, exposing queue depth / queue-empty state as an orchestration property would also enable this pattern in user code. But a native tryDequeueEvent() would be simpler and safer.

Summary

Today the workaround is: race(dequeueEvent(queue), shortTimer) and assume the queue is drained when the timer wins.

A native tryDequeueEvent() would replace that heuristic with exact semantics.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions