When a customer-facing agent emits request_human_input(...) in block mode, somebody needs to answer in minutes — not in the next Slack thread. Winnow's HIL queue lives where reviewers already work, expands inline, and resumes the agent the moment the answer lands.
Block-Mode HIL: A Queue, Not a Channel
Most teams treat agent-asks-human as an alerting problem: pipe a Slack ping into an SME channel, hope the right person sees it, hope they answer in a thread, hope the agent runtime parses the answer back out. Two of those three hopes break in the first week of production.
The framing that actually works is the inverse. The agent's question is a row in a queue. The queue is the system of record for "things the agent paused on." Reviewers come to the queue, scan, click, answer. The agent resumes. Nothing about that flow lives in a chat thread.
Where it lives
Winnow's HIL queue is the Human input tab inside Reviews — the same tab strip that already holds Feedback, Transcripts, and Approvals. There is no new page, no new modal, no dedicated alert pane. The route is just /reviews?tab=human-input and a deep-link from a Slack alert opens straight to the queue.
Each pending row shows three things at a glance: the agent's identity (and trial number, when the runtime stamps it), the question in two lines, and how long it has been waiting. A red badge marks block-mode requests (production agent paused, customer waiting); a blue badge marks simulate-mode (eval/training, the simulator already answered, the human label is for calibration).
Click to expand, answer to resume
Click a row and the panel expands in place — no nav, no modal. The expand block shows:
- A breadcrumb strip with suite, trace_id, and the request_id so an engineer can pivot to the raw record.
- A "Recent turns" block reading the last conversational turns the runtime stamped into the request context (user / agent / tool calls).
- A yellow-bordered callout for the agent's actual question.
- A textarea for the SME's answer, plus three inline actions: Answer & resume, Reject (should not have asked), and Skip · fail trial.
Answering posts to /scoring/human-requests/{id}/answer; the row leaves the queue; the agent resumes mid-conversation; the customer sees nothing odd. Reject posts to the skip route with a should_not_have_asked reason — that's the ground truth signal the A4 calibration grader uses to compute precision (the agent shouldn't have asked here) and recall (the agent should have asked but didn't).
Why a queue, not a channel
Three reasons a queue beats a Slack thread for this:
- Survival of context. A Slack thread loses the agent's question, the transcript, the trace_id, and the answer the second the thread scrolls off. The queue keeps them on a typed row that the calibration grader can read later.
- Reviewer ergonomics. A queue with 7 pending rows is scannable in two seconds. A Slack channel with 7 threads requires opening 7 threads.
- Calibration signal. The Reject action is a label, not a message. Labels train the agent's "when to ask" model; messages don't.
Honesty about what isn't built yet
This first slice ships the queue surface and the three inline actions. Three follow-ups are explicitly tracked in docs/UNBUILT-FEATURES.md P2: a typed should_have_asked column so the calibration grader stops reading reasons out of the free-form skip field, a per-reviewer claim lock to prevent collision-answering on a multi-on-call rotation, and a Calibration sub-tab that aggregates precision/recall across all suites in the workspace. The block-mode pause itself is handled by the existing agent runtime primitive — the queue just gives the reviewer a place to do the work.