Direct file commit since vault MCP create_artifact has been CF-blocked on five consecutive attempts. Postgres index will catch up on next vault re-index/backfill; markdown body is the source of truth either way. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
61 lines
6.6 KiB
Markdown
61 lines
6.6 KiB
Markdown
---
|
|
project: petal-dispatch-asks-panel
|
|
type: project-plan
|
|
status: active
|
|
path: Sources/Dev
|
|
tags: [petal-dispatch, lovebug, dispatch, mcp, claude-code, phase-4]
|
|
summary: Custom MCP tool plus non-modal UI panel giving split-Lovebug the structured multi-choice question surface that the built-in counterpart provides on claude.ai. The headless CLI does not surface it over stream-json (confirmed empirically); this plan rebuilds the same shape via a tool on the executor harness MCP, Postgres-mediated answer roundtrip, and an Asks panel alongside Workers/Activity.
|
|
created: 2026-06-25
|
|
updated: 2026-06-25
|
|
---
|
|
|
|
# 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 (1-4 questions, 2-4 options each, single- or multi-select). Cowork has it built-in. Petal-Dispatch (headless `claude --print` + stream-json) does not — confirmed empirically: `system.init` tool list does not include the built-in even with the name passed via `--allowedTools`, 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/output schema 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/output schema mirrors the docs verbatim.** Input: `{questions: [{question, header, options: [{label, description}], multiSelect}]}`. Output: `{questions, answers: {question: label-or-array}, response?}`. Constraints: 1-4 questions, 2-4 options, `header` max 12 chars.
|
|
- **Blocking semantics in the MCP server.** The tool LISTENs on Postgres NOTIFY `question_answered` and blocks until the matching answer arrives. Claude's turn pauses until the tool returns.
|
|
- **5-minute tool timeout.** If no answer within 5 minutes, the tool returns `{questions, answers: {}, response: "Timed out waiting for user input"}` and marks the row `cancelled`. Picked over a longer window so unanswered asks don't pin claude's turn open all day.
|
|
- **Backing storage: new `dispatch_harness.questions` table** (id, tool_use_id, source_session_uuid, questions jsonb, status enum, answers jsonb, response, created_at, answered_at). NOTIFY channel `question_answered` fires on status transition to 'answered' or 'cancelled'.
|
|
- **UI: non-modal "Asks" panel alongside Workers/Activity.** Chat stays interactive while questions are pending. Explicit preference over the modal-overlay pattern.
|
|
- **Air-gap pattern preserved.** Ingress writes to questions table, executor LISTENs. No HTTP path from ingress to executor. Mirrors chat_queue / stop_queue / thread_control_queue precedent.
|
|
- **Split-only build.** Backend (executor MCP tool + ingress endpoints) lives on the split path; UI lives in the shared `src/petal_dispatch/static/index.html` (ingress serves it). 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. DDL `db/migrations/010_dispatch_questions.sql` — additive table + trigger + grants. NOTIFY channel `question_answered`.
|
|
2. MCP tool `mcp__harness__ask_user_question` on the harness MCP — input/output schema per docs, blocks via Postgres LISTEN, 5-minute timeout.
|
|
3. Ingress endpoints — `GET /api/asks` (pending + recent answered), `POST /api/asks/<tool_use_id>/answer` (accepts either `answers` OR `response` body), `DELETE /api/asks/<tool_use_id>` (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 → POST with `response` set, `answers` empty). Live updates via SSE filtered on the tool's `dispatch.tool_use` / `dispatch.tool_result` events.
|
|
5. Verification: end-to-end ask + 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 `--allowedTools`. 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 + 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.
|