--- created: '2026-06-25' path: Sources/Dev project: petal-dispatch-asks-panel tags: - petal-dispatch - lovebug - dispatch - mcp - claude-code - phase-4 type: project-plan --- # Goal Give split-Lovebug (and per-thread Harness procs) a structured-question surface for clarifying input from Travis, mirroring the claude.ai built-in question semantics, rendered as a non-modal panel that doesn't hide the chat. Why: a multi-choice clarifying ask is the cleanest pattern for "I need to confirm something before I do this work." The Anthropic docs codify the shape — between one and four questions per call, with two to four options each, single- or multi-select. Cowork has it built-in. Petal-Dispatch (headless `claude --print` plus stream-json) does not — confirmed empirically: the init event's tool list does not include the built-in even when its name is passed via the allowed-tools flag, and claude itself reports the tool absent. So we build the same behavioral contract under a different name, mounted as a custom MCP tool on the executor. Spec source: https://code.claude.com/docs/en/agent-sdk/user-input — input and output shape mirrored verbatim. # Locked Decisions - **Custom MCP tool, not the built-in.** The headless CLI doesn't expose it; no flag or allowedTools entry surfaces it. The Agent SDK's canUseTool callback is the SDK-mode mechanism; the CLI subprocess path doesn't have an equivalent. - **Tool name: `mcp__harness__ask_user_question`** — distinct from the built-in so nobody confuses the two. Mounted on the existing harness MCP at `/harness-mcp/` on the executor (port 8002). Per-thread Harness procs and split-Lovebug both inherit it via the shared mcp.json. - **Input and output shape mirrors the docs verbatim.** Input is a questions array, each item having the question text, a short header (max 12 chars), an options array (2 to 4 items, each with label and description), and a boolean for multi-select. Output mirrors the SDK: pass-through of the original questions, plus an answers map from question text to chosen label (or array of labels for multi-select), plus an optional free-text response override. - **Blocking semantics in the MCP server.** The tool listens on a Postgres notification channel and pauses until the matching answer arrives. Claude's turn pauses until the tool returns. - **5-minute tool timeout.** If no answer arrives within 5 minutes, the tool returns an empty-answers shape with a timeout-explanatory response, and marks the row cancelled. Picked over a longer window so unanswered asks don't pin claude's turn open all day. - **Backing storage in a new `dispatch_harness.questions` row store.** Columns: an autoincrement id, the tool-use id, the source session uuid, the questions blob, a status (pending / answered / cancelled), the answers blob, the optional response text, and timestamps for created and answered. A notification channel called `question_answered` fires on status transition to answered or cancelled. - **UI: non-modal Asks panel alongside Workers and Activity.** Chat stays interactive while questions are pending. Explicit preference over the modal-overlay pattern. - **Air-gap pattern preserved.** Ingress writes to the questions row store, executor listens for the notification. No HTTP path from ingress to executor. Mirrors the chat-queue, stop-queue, and thread-control-queue precedent. - **Split-only build.** Backend (executor MCP tool plus ingress endpoints) lives on the split path; UI lives in the shared `src/petal_dispatch/static/index.html` that the ingress serves. Fused service unchanged through all of this — its MainPID stays pinned. # Open Items - **Fused-side mount of the tool** — DEFERRED indefinitely. The fused UI is not being upgraded, so even if the tool were available on fused-Lovebug, there'd be no panel to render its tool_use blocks. Not planned for any cutover phase either; cutover retires fused entirely so the tool only needs to live on the executor. # Build Phases ## Phase 1 — Tool plus panel (core) 1. Migration `010_dispatch_questions` — additive row store plus its notify trigger plus grants. Notification channel `question_answered`. 2. MCP tool `mcp__harness__ask_user_question` on the harness MCP — input/output shape per docs, blocks via Postgres notify, 5-minute timeout. 3. Ingress endpoints — `GET /api/asks` (pending plus recent answered), `POST /api/asks//answer` (accepts either an answers map OR a free-text response body), `DELETE /api/asks/` (cancel). 4. UI panel "Asks" in the sidebar — pending cards with answer buttons, multi-select with checkboxes; each card also has a dismiss-with-free-reply path (text input then POST with response set, answers empty). Live updates via SSE filtered on the tool's `dispatch.tool_use` and `dispatch.tool_result` events. 5. Verification: end-to-end ask plus answer roundtrip, multi-select, page-refresh-survives, dismiss-with-reply path proven, 5-min timeout fires correctly, fused isolation. ## Phase 2 — Free-text and visual extensions - **Free-text "Other" input per option** — let the user type a custom value for a question's answer (the docs note this is supported when the predefined options don't cover what the user wants; the custom text becomes the answer value, not the literal word "Other"). - **`preview` field rendering** (markdown or HTML visual mockups per option). Anthropic uses this for visual comparisons (layouts, colors); skip elsewhere. ## Phase 3 — Scoping - **Per-thread scoping in the Asks panel.** Show only questions whose source session uuid matches the currently-focused chat surface, instead of all pending across the executor's procs. UI filter on top of the same `/api/asks` data. # Notes - Spec source: https://code.claude.com/docs/en/agent-sdk/user-input - Empirical investigation that motivated the custom-MCP-tool route — claude 2.1.191 headless CLI does not expose the built-in question tool via stream-json output, even when its name is passed in the allowed-tools flag. The Agent SDK's canUseTool callback is the SDK-mode mechanism; the CLI subprocess path doesn't have an equivalent. - This plan sits alongside plan 300 (`petal-dispatch-phase-4-2-ingress-split`) but is logically independent — the Asks panel adds a capability that's useful in the split topology and persists past cutover. Sequenced after Phase 4.2's structural split work because the implementation rides the same MCP plus air-gap infrastructure. - The fused service has run continuously since 2026-06-24 09:20:01 EDT (MainPID 1435891 as of writing). Any work under this plan must respect that constraint until cutover is explicitly approved under plan 300.