You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bug report: user text input is delayed by a long time (minutes) under heavy concurrent load — enqueued silently behind a busy agent turn (queue/backpressure), with no "queued" status surfaced to the user
Environment
DeepSeek Harness Web GUI (dsh web, reachable at 127.0.0.1:3080)
HEAD 47f943859b
Scenario: while a single GUI session is running a long agent turn that spawns ~20 in-process subagents in (near-)parallel, plus a burst of web RPC (e.g. workspace.create, session creation), the user typed a message in the composer and pressed Enter / clicked Send. The input textarea cleared immediately (optimistic), but the message was not shown in the session transcript for many minutes; after the burst drained, the message "finally" appeared and was processed.
Observed behavior
The message is not permanently lost; it is effectively enqueued and processed only once the busy turn fully drains, so it can appear minutes later ("現在才發出來").
During the wait the user gets no visible "pending / queued" state for that follow-up, and no error — the composer shows the input cleared as if it was sent.
This matters because it looks like a lost input, when in fact it is silently behind background work.
Investigation — the send and queue path (all line numbers at HEAD 47f9438)
Frontend (input → RPC) — pessimistic draft-clear, then enqueue.
Enter / Send → keyboard.submit(resolveSubmitMode(...)) (line 335) or inputActions.submit() (line 554); submit() defaults to mode 'queue' (facade.ts line 80, InputActions.submit).
facade.tssubmit() → machine enter → defaultSink(draft, imageIds, mode) (lines 198–209, then sinkSerialized → defaultSink lines 416–449).
hub.tssink() (packages/client/ui-conversation/src/client/input/hub.ts lines 149–168): optimistically clears the draft first via shell.commitSend(imageIds) (line 158), then calls conversation().sendSession(...) and only restores the draft on rejection (lines 159–167). So the writer sees their text cleared before any host confirmation of delivery.
session.tsprompt() (packages/client/runtime/src/client/sessions/session.ts lines 190–260) → unary RPC api.sessions.prompt(...) (fetch, 30 s default timeout in apiproxy/src/fetch/client.ts lines 228, 313–316, 421). Transport failures fold into promptError (session.ts line 242) which InputBar surfaces as a transient toast (InputBar.tsx lines 98–103).
Host (RPC handler → agent queue).
api-proxy.tssessions.prompt handler (packages/host/apiproxy/src/api-proxy.ts lines 2461–2517). For a text message it calls agent.followup(message) (mode queue, line 2499) and immediately returns ok { accepted: true } (line 2514) — without waiting for the turn to be processed. An exception in the enqueue surfaces as agent-busy (lines 2508–2512).
agent.followup() → send(input,'next-turn',true) (packages/core/agent-loop/src/agent.ts lines 122–123) → splice into inbox.nextTurn plus wakeDriver() (agent.ts lines 113–120).
The agent run loop is strictly serial per session (agent-loop/src/agent.ts): wakeDriver() only opens a turn/step boundary when this.phase.kind === 'idle'; while the agent is mid-turn it only latches wakeRequested = true (lines 172–181). The queued nextTurn message is only claimed and appended to the log (user/message, line 283) when the current turn fully drains (turn() loop, lines 246–330). With ~20 subagents awaited inside the current turn, this can take minutes.
The inbox has no length cap and no timeout — inbox.ts (packages/core/agent/src/inbox.ts) merely splices (append, lines 86–88; claim only at a step boundary, lines 71–78). A queued message can therefore sit indefinitely behind a long turn.
Downlink confirmations.
The "queued" signal for the user's message is broadcast by the host as a session/queue mux frame when the inbox splice lands (api-proxy.tsqueueItems lines 1326–1355; placement:'queued'). The client only mirrors the queue from that mux frame (queue-mirror.tsreplace, lines 49–58).
That frame, like all ~20 subagents' chunk/tool frames, travels the same single per-tab mux downlink (websocket-downlink.ts, serial pump that await send(...) per frame, lines 118–137; the browser side web-api-client.tsreadWebSocket buffers frames, lines 34–90). Under load this downlink is a shared backpressure point, so even the "queued" echo can arrive late.
Candidate root causes (ranked by likelihood for this "delayed minutes" symptom)
Per-session serial agent turn + immediate accepted:true (most likely). session.prompt returns success the instant the message is enqueued into inbox.nextTurn (api-proxy.ts 2499/2514), and the loop only processes that message after the current long (subagent-dense) turn ends (agent.ts 172–181, 283). The composer clears optimistically (hub.ts 158), so the writer believes it was sent while it is really behind background work. Observed gap: minutes.
No user-visible "queued" feedback during the wait. The only pending signal is the session/queue echo (queue-mirror.ts 49–58), which is neither immediate nor always rendered as an explicit pending message surface for a next-turn follow-up; the draft is already cleared, so the user cannot tell the message is parked behind the busy turn.
Mux downlink backpressure delaying the confirmation/echo. The session/queue echo and event pushes share one serial WebSocket pump (websocket-downlink.ts 118–137, web-api-client.ts inbox). A dense stream of ~20 subagent frames can delay the echo and any pending indicator, compounding #1.
Unary timeout / reconnect window (lower likelihood for a delay, contributes if the connection drops). Unary session.prompt has a 30 s deadline (fetch/client.ts 228/313–316); under sustained load a dropped/reconnecting downlink delays event delivery, and the 30 s unary deadline on the POST can produce a client-side abort that maps to promptError (session.ts 239/242). If the abort races a successful host enqueue, the message is still queued host-side but the UI shows no clear outcome.
Note: a follow-up typed while the same agent is busy is routed per the busyEnter preference, whose default is 'queue' (ui-conversation/src/submission-settings.ts lines 12–18), i.e. plain Enter queues rather than steers — the intended design, but there is no surfaced estimate or timeout for how long the queue will wait.
Expected behavior
User follow-up text entered in the GUI should be acknowledged with a clear pending / queued state (not an immediate clear with no feedback) when it entered the agent's inbox but is waiting behind background work, including — ideally — an indication that a prompt turn is already running.
Deliver user-initiated interactive text with strong priority / prompt handling, or at minimum surface a visible "your message is queued (#N behind the running turn)" indicator with the ability to raise priority (steer) or cancel.
Never silently accept-and-clear a message whose model turn will only run minutes later. Either process it promptly, show the pending state, or reject with a visible error.
Consider a queue-length / wait-time guard so a follow-up cannot sit indefinitely behind a very long turn (e.g. surface a warning after a threshold).
Suggested areas to look at
packages/core/agent-loop/src/agent.ts (wakeDriver latches behind a running turn; turn() only drains nextTurn after the current turn) — priority/fairness for user-origin prompts.
packages/host/apiproxy/src/api-proxy.ts lines 2461–2517 — return the enqueue as accepted without a "queued, not processed" signal.
packages/client/ui-conversation/src/client/input/hub.ts lines 149–168 — optimistic draft-clear with only a rejection-restore, no queued-state surface.
packages/client/ui-conversation/src/client/skeleton/InputBar.tsx + queue-mirror.ts — surfacing a "queued/pending" state to the writer.
packages/client/connection/src/websocket-downlink.ts — serial await per frame is a shared backpressure point for the confirmation echo.
Feel free to ask for the session-log excerpts or a reduced repro (one GUI session running a long turn while a follow-up is sent) if helpful.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Bug report: user text input is delayed by a long time (minutes) under heavy concurrent load — enqueued silently behind a busy agent turn (queue/backpressure), with no "queued" status surfaced to the user
Environment
dsh web, reachable at127.0.0.1:3080)47f943859bworkspace.create, session creation), the user typed a message in the composer and pressed Enter / clicked Send. The input textarea cleared immediately (optimistic), but the message was not shown in the session transcript for many minutes; after the burst drained, the message "finally" appeared and was processed.Observed behavior
Investigation — the send and queue path (all line numbers at HEAD 47f9438)
Frontend (input → RPC) — pessimistic draft-clear, then enqueue.
InputBar.tsx(packages/client/ui-conversation/src/client/skeleton/InputBar.tsx):keyboard.submit(resolveSubmitMode(...))(line 335) orinputActions.submit()(line 554);submit()defaults to mode'queue'(facade.tsline 80,InputActions.submit).facade.tssubmit()→ machineenter→defaultSink(draft, imageIds, mode)(lines 198–209, thensinkSerialized→defaultSinklines 416–449).hub.tssink()(packages/client/ui-conversation/src/client/input/hub.tslines 149–168): optimistically clears the draft first viashell.commitSend(imageIds)(line 158), then callsconversation().sendSession(...)and only restores the draft on rejection (lines 159–167). So the writer sees their text cleared before any host confirmation of delivery.service.tssendSession()(packages/client/ui-conversation/src/client/service.tslines 142–157) →session.prompt(content, mode).session.tsprompt()(packages/client/runtime/src/client/sessions/session.tslines 190–260) → unary RPCapi.sessions.prompt(...)(fetch, 30 s default timeout inapiproxy/src/fetch/client.tslines 228, 313–316, 421). Transport failures fold intopromptError(session.ts line 242) whichInputBarsurfaces as a transient toast (InputBar.tsx lines 98–103).Host (RPC handler → agent queue).
api-proxy.tssessions.prompthandler (packages/host/apiproxy/src/api-proxy.tslines 2461–2517). For a text message it callsagent.followup(message)(modequeue, line 2499) and immediately returnsok { accepted: true }(line 2514) — without waiting for the turn to be processed. An exception in the enqueue surfaces asagent-busy(lines 2508–2512).agent.followup()→send(input,'next-turn',true)(packages/core/agent-loop/src/agent.tslines 122–123) → splice intoinbox.nextTurnpluswakeDriver()(agent.ts lines 113–120).agent-loop/src/agent.ts):wakeDriver()only opens a turn/step boundary whenthis.phase.kind === 'idle'; while the agent is mid-turn it only latcheswakeRequested = true(lines 172–181). The queuednextTurnmessage is only claimed and appended to the log (user/message, line 283) when the current turn fully drains (turn()loop, lines 246–330). With ~20 subagents awaited inside the current turn, this can take minutes.inbox.ts(packages/core/agent/src/inbox.ts) merely splices (append, lines 86–88;claimonly at a step boundary, lines 71–78). A queued message can therefore sit indefinitely behind a long turn.Downlink confirmations.
session/queuemux frame when the inbox splice lands (api-proxy.tsqueueItemslines 1326–1355;placement:'queued'). The client only mirrors the queue from that mux frame (queue-mirror.tsreplace, lines 49–58).websocket-downlink.ts, serialpumpthatawait send(...)per frame, lines 118–137; the browser sideweb-api-client.tsreadWebSocketbuffers frames, lines 34–90). Under load this downlink is a shared backpressure point, so even the "queued" echo can arrive late.Candidate root causes (ranked by likelihood for this "delayed minutes" symptom)
accepted:true(most likely).session.promptreturns success the instant the message is enqueued intoinbox.nextTurn(api-proxy.ts2499/2514), and the loop only processes that message after the current long (subagent-dense) turn ends (agent.ts172–181, 283). The composer clears optimistically (hub.ts158), so the writer believes it was sent while it is really behind background work. Observed gap: minutes.session/queueecho (queue-mirror.ts49–58), which is neither immediate nor always rendered as an explicit pending message surface for anext-turnfollow-up; the draft is already cleared, so the user cannot tell the message is parked behind the busy turn.session/queueecho and event pushes share one serial WebSocket pump (websocket-downlink.ts118–137,web-api-client.tsinbox). A dense stream of ~20 subagent frames can delay the echo and any pending indicator, compounding #1.session.prompthas a 30 s deadline (fetch/client.ts228/313–316); under sustained load a dropped/reconnecting downlink delays event delivery, and the 30 s unary deadline on the POST can produce a client-side abort that maps topromptError(session.ts239/242). If the abort races a successful host enqueue, the message is still queued host-side but the UI shows no clear outcome.Note: a follow-up typed while the same agent is busy is routed per the
busyEnterpreference, whose default is'queue'(ui-conversation/src/submission-settings.tslines 12–18), i.e. plain Enter queues rather than steers — the intended design, but there is no surfaced estimate or timeout for how long the queue will wait.Expected behavior
Suggested areas to look at
packages/core/agent-loop/src/agent.ts(wakeDriverlatches behind a running turn;turn()only drainsnextTurnafter the current turn) — priority/fairness for user-origin prompts.packages/host/apiproxy/src/api-proxy.tslines 2461–2517 — return the enqueue asacceptedwithout a "queued, not processed" signal.packages/client/ui-conversation/src/client/input/hub.tslines 149–168 — optimistic draft-clear with only a rejection-restore, no queued-state surface.packages/client/ui-conversation/src/client/skeleton/InputBar.tsx+queue-mirror.ts— surfacing a "queued/pending" state to the writer.packages/client/connection/src/websocket-downlink.ts— serial await per frame is a shared backpressure point for the confirmation echo.Feel free to ask for the session-log excerpts or a reduced repro (one GUI session running a long turn while a follow-up is sent) if helpful.
All reactions