docs/design/daemon-ask-user-question-restore.md
ask_user_question HITL is in-memory. After a daemon restart, session/load
and session/resume currently close a trailing unanswered question as a
failed tool result (orphan repair + transcript finalizeDangling). The old
requestId is gone, so POST /session/:id/permission/:requestId cannot
complete it.
When --restore-ask-user-question is on (default off) and a client
loads or resumes that session:
ask_user_question.requestPermission with a new requestId
(timeout clock resets).GET /session/:id/status again shows isWaitingForUserQuestion and
pendingInteractions.Daemon boot does not scan or auto-resume waiting sessions.
continueLastTurnGeneric continue classifies a dangling functionCall as
interrupted_turn and synthesizes an error function response. Restoring
a question as a tool crash would lose the HITL. Restore is a separate
tracked prompt path. When the flag is on and the trailing question is
restorable, continueLastTurn declines (accepted: false, interruption: none) instead of answering the restored question with a synthetic failure.
All of the following must hold:
model turn.functionCall in that turn is ask_user_question.AskUserQuestionParams.branchSession in this call.Main session only. Forks/subagents cannot run this tool.
--restore-ask-user-question on qwen serve and on the ACP child
(qwen --acp --restore-ask-user-question). Default false. The child-side
flag is honored only in ACP mode: in the plain TUI nothing can re-hang the
question, and skipping load-time orphan repair would wedge the resumed
session. No settings.json key and no capability tag in v1.
startChat skips orphan repair only for the eligible AUQ call ids;
the per-send inline repair pass does the same, so a prompt that beats
the restore prompt does not close the preserved call with a synthetic
failure.finalize() skips those call ids so the UI stays
in-progress. Skip and re-hang stay in lockstep: when the daemon already
knows it will decline (no attached client, fork restore), the
child-bound request carries
qwen.daemon.suppressRestoreAskUserQuestion and the child neither hints
nor skips — the replay finalizes the question as failed._meta includes qwen.daemon.restoreAskUserQuestion
when the argv switch is on and the session is eligible. The default-off
path never calls the restore-only Session helper.sendPrompt with that meta (same admission as continue). External
POST /prompt cannot smuggle the meta. Admission additionally requires
the entry to be idle at fire time (promptActive, pendingPromptCount
and goalTurnActive are all clear); admission failures are logged and
swallowed — restore is a best-effort side effect of a successful load.Session.prompt() rebuilds the tool, requestPermission, then Submit
writes the real function response and continues the model. Cancel
persists a decline (live decline handling). A timeout on a restored
question persists nothing: the call stays dangling in the transcript so
a later load can re-hang it again.requestId is not persisted across restarts.
continueLastTurn synthetic-failure semanticsrequestId / permission audit ring