5.3 KiB
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. --resumeper-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):
- The tool resolves the caller task id, registers a pending question + a
TaskCompletionSource<string>in a singletonPendingQuestionRegistry, and broadcastsTaskQuestionAsked(taskId, questionId, question). - Mission Control surfaces the question with an input box.
- The user answers →
WorkerHub.AnswerTaskQuestionresolves the TCS → the tool returns the answer as its result → Claude continues. - 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. NoTaskStatusenum value, noTaskStateServicetransition, no EF migration. If the worker dies mid-wait,StaleTaskRecoveryflips the orphanedRunningrow toFailedlike any interrupted run. MCP_TOOL_TIMEOUTmust be raised. Claude Code caps HTTP MCP tool calls at 60 s by default. Theclaudedo_runMCP is HTTP, soClaudeProcessmust setMCP_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
TaskRunneronly mints the run MCP for standalone top-level tasks (forSuggestImprovement). To satisfy "any running task," move the MCP-identity setup out of that gate so everyRunAsyncgetsclaudedo_run.AllowedToolsalways includesmcp__claudedo_run__AskUser;SuggestImprovementstays 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) — addAskUser[McpServerTool]; inject the registry.Runner/TaskRunner.cs(edit) — wire MCP identity for all runs; addAskUserto allowed tools.Runner/ClaudeProcess.cs(edit) — setMCP_TOOL_TIMEOUTenv.Hub/HubBroadcaster.cs(edit) —TaskQuestionAsked,TaskQuestionResolved.Hub/WorkerHub.cs(edit) —AnswerTaskQuestion,GetPendingQuestion+ DTO.Program.cs(edit) — registerPendingQuestionRegistrysingleton.- 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)
- Real-Claude smoke test — confirm a blocking
AskUsercall survives ≥3 min withMCP_TOOL_TIMEOUT=200000and that the model actually calls the tool when uncertain. - 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).