Problem
Maka's PermissionEngine already has most of the round trip needed for in-turn human input: a parked Promise, stream-watchdog pause, timeout, and clean abort when the turn stops. Maka still has no model-callable question tool, so an agent can only ask in prose and end the turn. It cannot wait for a bounded user decision and continue the same turn.
Open-ended follow-up is not the target. The model should continue to ask those in normal assistant text and wait for the next user turn.
Decided v1 contract
AskUserQuestion({questions: [{question: string,options: [{label: string,description?: string,}, ...],// 2–3 model-provided, mutually exclusive options}, ...],// 1–3 independent questions})- The client always adds an
Other choice with free-text input. The model does not provide is_other. - One option is selected per question. Multi-select is out of scope.
- The UI returns
{ requestId, answers: Array<string | null> }, ordered exactly like questions. null means the user explicitly left that question unanswered. - The tool returns and persists one normal JSON tool result:
{answers: [{question: string,answer: string|null},],}permissionRequired: false.- Available in
explore, ask, execute, and bypass. - Registered only for interactive root agents in desktop and TUI.
- Not registered in headless/Harbor, Pi transport, or child-agent toolsets.
There is no current excludedToolNames extension point on main. v1 does not add a new root-agent tool blacklist solely for this tool.
Runtime design
Reuse the existing await machinery; do not build a second waiting pipeline.
- Extract the turn-scoped parked-request registry from
PermissionEngine into a shared low-level primitive. - Keep permission policy and remember-for-turn behavior owned by
PermissionEngine. - Let
ToolRuntime use the same primitive for user questions, including watchdog pause, late-response rejection, and turn stop/abort cleanup. Permission keeps its existing 300-second hard timeout; user questions have no fixed timeout and remain parked until answer, explicit skip, stop, abort, or turn teardown. - Add a distinct
user_question_request session event. - Route the UI answer back as a distinct UI → runtime response command.
- Do not persist a second
user_question_response event. The normal tool result is the single source of truth for the answer. - Do not synthesize a
UserMessage, because that would create a false user turn and break tool-call/result adjacency. - Do not add a dedicated
user_answerToolResultContent kind; the existing JSON result preserves the structured answer without expanding every result consumer.
The tool description must say that it is for bounded choices required to continue the current turn. It must not replace ordinary open-ended conversation.
Interaction design
Deliver three sequential PRs. Start each one from the latest main after the previous PR is squash-merged.
PR1 — permission composer takeover
- Replace the full-screen permission modal with a compact surface that temporarily takes over the existing composer slot.
- Hide the composer while waiting, but preserve its draft and restore it unchanged after allow, deny, or stop.
- Keep the action summary, risk, allow/deny actions, and Stop visible.
- Put long raw parameters and diffs behind a disclosure with a capped internal scroll area.
- Keep the default height compact (about 160 px at the standard chat measure); expanded content grows upward with a viewport-relative cap.
- Preserve the existing per-session FIFO and remember-for-turn semantics.
PR2 — AskUserQuestion runtime + TUI
- Add the core request/response/event contract, shared turn-scoped await primitive, AI SDK tool execution, abort handling, and runtime response routing.
- Register the tool only in the interactive TUI root-agent toolset.
- Reuse the TUI's existing selection overlay, ask the 1–3 questions sequentially, open short text input for
Other, use Esc to leave only the current question unanswered, and submit all answers once. - Keep desktop unregistered in this slice.
- Prove the complete same-turn round trip through focused core, runtime, and TUI tests.
PR3 — AskUserQuestion desktop
- Register the tool in the desktop root-agent toolset and add the main/preload/renderer response route.
- Reuse PR1's composer-takeover container.
- Show one question at a time with option descriptions and client-owned
Other input. - Use explicit Previous / Next controls; selecting an option does not auto-advance.
- Show progress (
2 / 3), allow review, and submit all answers once on the last question. - Let permission and question requests share one per-session interaction FIFO so parallel tool calls cannot create competing input surfaces.
- Cover one complete fake-backend desktop journey plus focused renderer, IPC, and computed-style contracts.
Acceptance criteria
- A model can call
AskUserQuestion, receive the user's answer as the tool result, and continue in the same turn. - Desktop and TUI both support 1–3 single-choice questions plus client-owned
Other. - The answer is persisted exactly once as the tool result and survives history replay.
- Permission timeout and user-question answer/skip/stop/abort/teardown paths settle cleanly without leaving a parked request; late responses are ignored.
- Parallel permission/question requests are queued without overwrite or competing focus.
- The tool is absent from headless/Harbor, Pi transport, and child-agent toolsets.
- The permission tray and question tray are covered by one representative fake-backend desktop E2E journey plus focused runtime, UI, TUI, and computed-style contracts.
Out of scope
Pure free-text questions, secrets/masked input, autoResolutionMs, caller-provided is_other, per-question ids or headers, multi-select, more than three questions, headless/subagent availability, a dedicated transcript question card, a persisted response event, a dedicated answer result kind, and a new generic tool-exclusion system.
Problem
Maka's
PermissionEnginealready has most of the round trip needed for in-turn human input: a parked Promise, stream-watchdog pause, timeout, and clean abort when the turn stops. Maka still has no model-callable question tool, so an agent can only ask in prose and end the turn. It cannot wait for a bounded user decision and continue the same turn.Open-ended follow-up is not the target. The model should continue to ask those in normal assistant text and wait for the next user turn.
Decided v1 contract
Otherchoice with free-text input. The model does not provideis_other.{ requestId, answers: Array<string | null> }, ordered exactly likequestions.nullmeans the user explicitly left that question unanswered.permissionRequired: false.explore,ask,execute, andbypass.There is no current
excludedToolNamesextension point onmain. v1 does not add a new root-agent tool blacklist solely for this tool.Runtime design
Reuse the existing await machinery; do not build a second waiting pipeline.
PermissionEngineinto a shared low-level primitive.PermissionEngine.ToolRuntimeuse the same primitive for user questions, including watchdog pause, late-response rejection, and turn stop/abort cleanup. Permission keeps its existing 300-second hard timeout; user questions have no fixed timeout and remain parked until answer, explicit skip, stop, abort, or turn teardown.user_question_requestsession event.user_question_responseevent. The normal tool result is the single source of truth for the answer.UserMessage, because that would create a false user turn and break tool-call/result adjacency.user_answerToolResultContentkind; the existing JSON result preserves the structured answer without expanding every result consumer.The tool description must say that it is for bounded choices required to continue the current turn. It must not replace ordinary open-ended conversation.
Interaction design
Deliver three sequential PRs. Start each one from the latest
mainafter the previous PR is squash-merged.PR1 — permission composer takeover
PR2 — AskUserQuestion runtime + TUI
Other, useEscto leave only the current question unanswered, and submit all answers once.PR3 — AskUserQuestion desktop
Otherinput.2 / 3), allow review, and submit all answers once on the last question.Acceptance criteria
AskUserQuestion, receive the user's answer as the tool result, and continue in the same turn.Other.Out of scope
Pure free-text questions, secrets/masked input,
autoResolutionMs, caller-providedis_other, per-question ids or headers, multi-select, more than three questions, headless/subagent availability, a dedicated transcript question card, a persisted response event, a dedicated answer result kind, and a new generic tool-exclusion system.