fix(gateway): /queue is now a true FIFO — each invocation gets its own turn - #16175
Conversation
…n turn Repeated /queue commands now each produce a full agent turn, in order, with no merging. Previously the second /queue overwrote the first because the handler wrote directly into the adapter's single-slot _pending_messages dict. - GatewayRunner grows a _queued_events overflow buffer (dict of list). - /queue puts new items in the adapter's next-up slot when free, otherwise appends to the overflow. After each run's drain consumes the slot, the next overflow item is promoted so the recursive run picks it up. - /new and /reset clear the overflow. - /status now reports queue depth when non-zero. - Ack message shows the depth once it exceeds 1. Helpers (_enqueue_fifo, _promote_queued_event, _queue_depth) use the getattr default-fallback pattern so existing tests that build bare GatewayRunner instances via object.__new__ keep working.
Excellence Contribution —
|
| Behavior | Before | After |
|---|---|---|
/queue A; /queue B; /queue C on gateway |
Only C runs | A, B, C each get their own turn |
/new after queueing |
Overflow leaked into new conversation | Overflow cleared |
/status |
No queue visibility | Shows Queued follow-ups: N |
| CLI behavior | Correct | Unchanged |
Implications
- Sequential batch workflows on all gateway platforms now work correctly
/queuegraduates from "unreliable on gateway" to a first-class workflow primitive- The
Queued follow-upsvisibility in/statusmakes queue state transparent — eliminating the silent-drop failure mode
…n turn (NousResearch#16175) Repeated /queue commands now each produce a full agent turn, in order, with no merging. Previously the second /queue overwrote the first because the handler wrote directly into the adapter's single-slot _pending_messages dict. - GatewayRunner grows a _queued_events overflow buffer (dict of list). - /queue puts new items in the adapter's next-up slot when free, otherwise appends to the overflow. After each run's drain consumes the slot, the next overflow item is promoted so the recursive run picks it up. - /new and /reset clear the overflow. - /status now reports queue depth when non-zero. - Ack message shows the depth once it exceeds 1. Helpers (_enqueue_fifo, _promote_queued_event, _queue_depth) use the getattr default-fallback pattern so existing tests that build bare GatewayRunner instances via object.__new__ keep working.
…n turn (NousResearch#16175) Repeated /queue commands now each produce a full agent turn, in order, with no merging. Previously the second /queue overwrote the first because the handler wrote directly into the adapter's single-slot _pending_messages dict. - GatewayRunner grows a _queued_events overflow buffer (dict of list). - /queue puts new items in the adapter's next-up slot when free, otherwise appends to the overflow. After each run's drain consumes the slot, the next overflow item is promoted so the recursive run picks it up. - /new and /reset clear the overflow. - /status now reports queue depth when non-zero. - Ack message shows the depth once it exceeds 1. Helpers (_enqueue_fifo, _promote_queued_event, _queue_depth) use the getattr default-fallback pattern so existing tests that build bare GatewayRunner instances via object.__new__ keep working.
…n turn (NousResearch#16175) Repeated /queue commands now each produce a full agent turn, in order, with no merging. Previously the second /queue overwrote the first because the handler wrote directly into the adapter's single-slot _pending_messages dict. - GatewayRunner grows a _queued_events overflow buffer (dict of list). - /queue puts new items in the adapter's next-up slot when free, otherwise appends to the overflow. After each run's drain consumes the slot, the next overflow item is promoted so the recursive run picks it up. - /new and /reset clear the overflow. - /status now reports queue depth when non-zero. - Ack message shows the depth once it exceeds 1. Helpers (_enqueue_fifo, _promote_queued_event, _queue_depth) use the getattr default-fallback pattern so existing tests that build bare GatewayRunner instances via object.__new__ keep working.
…n turn (NousResearch#16175) Repeated /queue commands now each produce a full agent turn, in order, with no merging. Previously the second /queue overwrote the first because the handler wrote directly into the adapter's single-slot _pending_messages dict. - GatewayRunner grows a _queued_events overflow buffer (dict of list). - /queue puts new items in the adapter's next-up slot when free, otherwise appends to the overflow. After each run's drain consumes the slot, the next overflow item is promoted so the recursive run picks it up. - /new and /reset clear the overflow. - /status now reports queue depth when non-zero. - Ack message shows the depth once it exceeds 1. Helpers (_enqueue_fifo, _promote_queued_event, _queue_depth) use the getattr default-fallback pattern so existing tests that build bare GatewayRunner instances via object.__new__ keep working.
…n turn (NousResearch#16175) Repeated /queue commands now each produce a full agent turn, in order, with no merging. Previously the second /queue overwrote the first because the handler wrote directly into the adapter's single-slot _pending_messages dict. - GatewayRunner grows a _queued_events overflow buffer (dict of list). - /queue puts new items in the adapter's next-up slot when free, otherwise appends to the overflow. After each run's drain consumes the slot, the next overflow item is promoted so the recursive run picks it up. - /new and /reset clear the overflow. - /status now reports queue depth when non-zero. - Ack message shows the depth once it exceeds 1. Helpers (_enqueue_fifo, _promote_queued_event, _queue_depth) use the getattr default-fallback pattern so existing tests that build bare GatewayRunner instances via object.__new__ keep working.
Summary
Repeated
/queuecommands now each produce a full agent turn, in FIFO order, with no merging. Previously the second/queuesilently overwrote the first because the handler wrote directly into the adapter's single-slot_pending_messagesdict — CLI users got stacking (realqueue.Queue) but every gateway platform dropped everything but the last item.Changes
gateway/run.py:GatewayRunnergrows a_queued_events: Dict[str, List[MessageEvent]]overflow buffer./queueuses the adapter's next-up slot when free, otherwise appends to overflow; drain promotes the overflow head into the slot after each consumed turn so the recursion sees it._handle_reset_command(/new,/reset) clears the overflow for that session./modeland other mid-session operations preserve the queue._handle_status_command(/status) reportsQueued follow-ups: Nwhen non-zero./queueack now surfaces depth once it exceeds 1 (Queued for the next turn. (3 queued))._enqueue_fifo,_promote_queued_event,_queue_depthhelpers. All usegetattr(self, '_queued_events', None)fallback so existing tests usingobject.__new__(GatewayRunner)(pitfall Add support for Atropos Agentic RL environments (requires branch tool… #17) keep working.Validation
/queue A; /queue B; /queue Con gateway/queueitemsmerge_pending_message_event/newafter queueing/statusoutputAdded 5 new tests to
tests/gateway/test_queue_consumption.pycovering FIFO enqueue, drain-site promotion across 3 consecutive turns, interrupt-follow-up staging, depth counting, and no-merge invariant. Removed the staletest_multiple_queues_last_one_winstest which documented the old broken behavior.CLI behavior is unchanged —
self._pending_input = queue.Queue()already did this correctly.