docs(ask-user): spec + plan for answering Claude's mid-run questions in Mission Control
This commit is contained in:
102
docs/superpowers/specs/2026-06-25-interactive-ask-user-design.md
Normal file
102
docs/superpowers/specs/2026-06-25-interactive-ask-user-design.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Interactive "Answer Claude's Questions" — Design
|
||||
|
||||
**Date:** 2026-06-25
|
||||
**Status:** Approved (brainstormed with Mika)
|
||||
|
||||
## Goal
|
||||
|
||||
Let the user answer a question Claude raises *mid-run* from inside Mission Control,
|
||||
without leaving the autonomous-execution model. Not a chat panel, not a terminal, not
|
||||
proactive steering — only: *Claude surfaces a question → the user types an answer → the
|
||||
run continues with that answer in context.*
|
||||
|
||||
User decisions (brainstorm):
|
||||
- Scope: "I mostly want to answer his questions if he surfaces any."
|
||||
- Trigger: **any running task** may ask, with a **3-minute** answer window.
|
||||
|
||||
## Why not the alternatives
|
||||
|
||||
- **Embedded terminal / PTY** — would destroy the NDJSON contract the whole worker
|
||||
pipeline depends on (StreamAnalyzer, token accounting, auto-commit, status flow) and
|
||||
needs a terminal-emulator control Avalonia doesn't have. Rejected.
|
||||
- **Streaming-stdin (`--input-format stream-json`)** — right tool for a free-form chat,
|
||||
overkill here. Rejected for v1.
|
||||
- **`--resume` per-turn** — already exists; not live (cold process per turn).
|
||||
|
||||
## Mechanism
|
||||
|
||||
The in-task MCP already blocks the `claude -p` process while a tool call is in flight.
|
||||
That blocking *is* the pause. Add one in-task MCP tool, `AskUser(question)`:
|
||||
|
||||
1. The tool resolves the caller task id, registers a pending question + a
|
||||
`TaskCompletionSource<string>` in a singleton `PendingQuestionRegistry`, and
|
||||
broadcasts `TaskQuestionAsked(taskId, questionId, question)`.
|
||||
2. Mission Control surfaces the question with an input box.
|
||||
3. The user answers → `WorkerHub.AnswerTaskQuestion` resolves the TCS → the tool
|
||||
returns the answer as its result → Claude continues.
|
||||
4. No answer within **3 minutes** → the tool returns *"No response received within 3
|
||||
minutes — proceed using your best judgment."* and the run carries on autonomously.
|
||||
|
||||
### Key facts that make this work
|
||||
|
||||
- **No persisted status change.** The task is still genuinely `Running` (process alive,
|
||||
blocked mid-tool-call). "Waiting for input" is **ephemeral**: in-memory registry +
|
||||
live SignalR events + a UI overlay. No `TaskStatus` enum value, no `TaskStateService`
|
||||
transition, **no EF migration**. If the worker dies mid-wait, `StaleTaskRecovery`
|
||||
flips the orphaned `Running` row to `Failed` like any interrupted run.
|
||||
- **`MCP_TOOL_TIMEOUT` must be raised.** Claude Code caps HTTP MCP tool calls at **60 s**
|
||||
by default. The `claudedo_run` MCP is HTTP, so `ClaudeProcess` must set
|
||||
`MCP_TOOL_TIMEOUT=200000` (≈3 min + margin) on the spawned process or the 3-min window
|
||||
is silently truncated to 60 s.
|
||||
- **MCP wired for all runs.** Today `TaskRunner` only mints the run MCP for standalone
|
||||
top-level tasks (for `SuggestImprovement`). To satisfy "any running task," move the
|
||||
MCP-identity setup out of that gate so every `RunAsync` gets `claudedo_run`.
|
||||
`AllowedTools` always includes `mcp__claudedo_run__AskUser`; `SuggestImprovement` stays
|
||||
gated to improvement-eligible (standalone) runs.
|
||||
|
||||
## Surface changes
|
||||
|
||||
**Worker (mostly new files):**
|
||||
- `Runner/PendingQuestionRegistry.cs` (new, singleton) — `Register`, `TryAnswer`, `Get`,
|
||||
`Remove`; one pending question per task.
|
||||
- `Runner/TaskRunMcpService.cs` (edit) — add `AskUser` `[McpServerTool]`; inject the
|
||||
registry.
|
||||
- `Runner/TaskRunner.cs` (edit) — wire MCP identity for all runs; add `AskUser` to
|
||||
allowed tools.
|
||||
- `Runner/ClaudeProcess.cs` (edit) — set `MCP_TOOL_TIMEOUT` env.
|
||||
- `Hub/HubBroadcaster.cs` (edit) — `TaskQuestionAsked`, `TaskQuestionResolved`.
|
||||
- `Hub/WorkerHub.cs` (edit) — `AnswerTaskQuestion`, `GetPendingQuestion` + DTO.
|
||||
- `Program.cs` (edit) — register `PendingQuestionRegistry` singleton.
|
||||
- System prompt (edit) — one line telling Claude the tool exists and to use it only when
|
||||
a wrong guess would be costly/irreversible (otherwise proceed).
|
||||
|
||||
**UI:**
|
||||
- `Services/IWorkerClient.cs` + `WorkerClient.cs` (edit) — `AnswerTaskQuestionAsync`,
|
||||
`GetPendingQuestionAsync`, `TaskQuestionAskedEvent`, `TaskQuestionResolvedEvent`.
|
||||
- `ViewModels/Islands/TaskMonitorViewModel.cs` (edit, **hot file**) — pending-question
|
||||
state, `AnswerDraft`, `SubmitAnswerCommand`, clear on finish/resolve.
|
||||
- `ViewModels/MissionControlViewModel.cs` (edit) — hydrate pending question on attach.
|
||||
- `Views/MissionControl/MonitorPaneView.axaml` (edit, **hot file**) — additive
|
||||
question/answer banner above the terminal.
|
||||
- `Localization/locales/en.json` + `de.json` — `missionControl.question.*` keys.
|
||||
|
||||
**Tests:** `PendingQuestionRegistry` (answer/timeout/unknown/overwrite), `AskUser` tool
|
||||
(answer + timeout fallback, fake broadcaster — no real Claude), `TaskMonitorViewModel`
|
||||
(surface/submit/clear). Update IWorkerClient fakes in both test projects.
|
||||
|
||||
## Concurrency note
|
||||
|
||||
Two files (`TaskMonitorViewModel.cs`, `MonitorPaneView.axaml`) are also being touched by
|
||||
a concurrent Mission Control drag-and-drop session on the shared main tree. Keep edits
|
||||
additive, commit explicit paths only (never `git add -A`).
|
||||
|
||||
## Verification gaps (manual)
|
||||
|
||||
1. **Real-Claude smoke test** — confirm a blocking `AskUser` call survives ≥3 min with
|
||||
`MCP_TOOL_TIMEOUT=200000` and that the model actually calls the tool when uncertain.
|
||||
2. **Visual** — the question banner + input box in the pane (Mika does the visual pass).
|
||||
|
||||
## Non-goals
|
||||
|
||||
Free-form chat panel; proactive steering; tool-permission prompts (stays `auto`);
|
||||
`ContinueAsync`/resumed runs gaining `AskUser` (deferred follow-up).
|
||||
Reference in New Issue
Block a user