Questions
Feature Definition
Section titled “Feature Definition”A questions tool gives the model a structured way to pause execution and request human decisions.
Without a structured question tool, an agent has two bad options:
- guess and continue (risking wrong implementation)
- emit plain text questions in assistant output (which can break deterministic tool loops)
A dedicated tool solves this by making user clarification part of the tool-call protocol:
- model emits a typed question payload
- runtime blocks the turn
- UI renders controlled choices/freeform fields
- answer is returned as structured data
- model resumes with that answer in context
This becomes critical in planning, architectural tradeoff selection, and ambiguous requirement interpretation.
See also Skills for how question-style prompts are reused during skill dependency and environment setup flows. For host-side confirmation UX (approve/deny tool execution) rather than model-initiated questions, see Approval Flow.
Why It Is Hard
Section titled “Why It Is Hard”The hard part is not asking a question.
The hard part is preserving determinism and safety across:
- different clients (TUI, app server, ACP, headless)
- different turn modes (interactive vs non-interactive)
- different answer shapes (single choice, multi choice, freeform, secret)
- cancellation and interruption semantics
If this is under-specified, the agent can deadlock waiting for input that no client can provide.
Aider Implementation
Section titled “Aider Implementation”Aider at commit b9050e1d5faf8096eae7a46a9ecc05a86231384b has no model-callable question tool.
No Model-Callable Question Tool
Section titled “No Model-Callable Question Tool”Aider’s model loop does not expose a JSON-schema tool for “ask user”.
When a model needs clarification, it asks in normal assistant text.
There is no dedicated runtime object like:
QuestionRequest- pending question queue
- typed answer payload returned to tool call
Ask Mode (/ask)
Section titled “Ask Mode (/ask)”Aider has a chat mode switch for Q&A behavior:
aider/commands.py:cmd_ask()delegates to_generic_chat_command(args, "ask")cmd_chat_mode()exposesaskas “Ask questions about your code without making any changes”
aider/coders/ask_coder.py:AskCodersetsedit_format = "ask"
aider/coders/ask_prompts.py:- system prompt is analysis-oriented and non-edit focused
This is a useful mode, not a runtime question tool.
Runtime Confirmation Prompts (confirm_ask and prompt_ask)
Section titled “Runtime Confirmation Prompts (confirm_ask and prompt_ask)”Aider does ask the human for runtime confirmations, but these are host-side procedural prompts, not model tools:
aider/io.py:confirm_ask()- yes/no(+all/skip/don’t ask again) interactions
- supports grouped confirmations
- supports
explicit_yes_required
aider/io.py:prompt_ask()- freeform input prompt
These are called from command and coder logic directly (lint fixes, shell operations, file creation, etc.).
The model does not emit a typed question call to trigger them.
Practical Result
Section titled “Practical Result”Aider supports human-in-the-loop clarification, but through:
- conversational text
- host-side confirmation functions
- mode switching (
/ask)
not through a first-class question tool protocol.
Codex Implementation
Section titled “Codex Implementation”Codex at commit 4ab44e2c5cc54ed47e47a6729dfd8aa5a3dc2476 exposes a dedicated request_user_input function tool, but with strict mode and schema behavior.
Tool Surface and Availability
Section titled “Tool Surface and Availability”Tool definition lives in:
references/codex/codex-rs/core/src/tools/spec.rs(create_request_user_input_tool())
Registration is gated by collaboration-mode feature wiring:
build_tool_registry(...)inspec.rs- only registered when
config.collaboration_modes_toolsis enabled
Mode gating is explicit:
references/codex/codex-rs/protocol/src/config_types.rsModeKind::allows_request_user_input()istrueonly forPlan
references/codex/codex-rs/core/src/tools/handlers/request_user_input.rs- returns model-visible error if called outside allowed mode
So the tool exists primarily for plan-time clarification loops.
Schema Contract in create_request_user_input_tool()
Section titled “Schema Contract in create_request_user_input_tool()”The model-facing schema requires:
questions: []- each question includes:
idheaderquestionoptions(2-3 mutually exclusive choices encouraged)
Important nuance:
- protocol structs (
protocol/src/request_user_input.rs) support richer fields likeisSecretand optional options - but the model-facing tool schema intentionally requires
options
Handler Flow (RequestUserInputHandler)
Section titled “Handler Flow (RequestUserInputHandler)”Implementation:
references/codex/codex-rs/core/src/tools/handlers/request_user_input.rs
Flow:
- parse function args into
RequestUserInputArgs - reject if current collaboration mode disallows tool
- reject if any question has missing/empty options
- force
question.is_other = truefor every question - call
session.request_user_input(...) - serialize
RequestUserInputResponseJSON back to tool output text
The is_other = true mutation means UI always gets an extra freeform “Other” path for tool-generated questions.
Session/Event Flow (Session::request_user_input)
Section titled “Session/Event Flow (Session::request_user_input)”Core event plumbing:
references/codex/codex-rs/core/src/codex.rsSession::request_user_input(...)Session::notify_user_input_response(...)
references/codex/codex-rs/core/src/state/turn.rs- pending user input map stored per active turn
Runtime behavior:
- create oneshot channel
- store pending responder in turn state
- emit
EventMsg::RequestUserInput - wait asynchronously for
Op::UserInputAnswer - resume tool handler with structured answers
Protocol vocabulary is documented in:
references/codex/codex-rs/docs/protocol_v1.mdEventMsg::RequestUserInputOp::UserInputAnswer
TUI Overlay and Answer Encoding
Section titled “TUI Overlay and Answer Encoding”TUI implementation:
references/codex/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs- history rendering:
references/codex/codex-rs/tui/src/history_cell.rs
Behavior highlights:
- question-by-question navigation UI
- options list + notes field
- optional “None of the above” injected when
is_otheris true - unanswered confirmation before submit
- FIFO queue for multiple incoming requests
Answer encoding detail:
- selected options are returned as labels
- notes are appended as synthetic entries with prefix:
user_note: <freeform text>history_cell.rs splits answers by detecting this prefix.
App-Server and Delegate Bridging
Section titled “App-Server and Delegate Bridging”Codex app-server bridges question requests over v2 protocol:
references/codex/codex-rs/app-server/src/bespoke_event_handling.rs- maps core
RequestUserInputEvent-> app-serverToolRequestUserInputParams - waits for client response
- submits
Op::UserInputAnswerback to core
- maps core
Sub-agent delegate forwarding also supports question events:
references/codex/codex-rs/core/src/codex_delegate.rs- intercepts child
EventMsg::RequestUserInput - relays to parent session
- returns answer to child as
Op::UserInputAnswer
- intercepts child
Internal Codex Uses Beyond Model Tool Calls
Section titled “Internal Codex Uses Beyond Model Tool Calls”Codex also uses the same request-user-input event pathway internally for non-tool workflows:
- skill environment variable collection:
core/src/skills/env_var_dependencies.rs
- skill MCP dependency install confirmation:
core/src/mcp/skill_dependencies.rs
- MCP app/tool approval prompts:
core/src/mcp_tool_call.rs
Those code paths can use richer fields (is_secret, optional options), even when model-facing tool calls are constrained.
OpenCode Implementation
Section titled “OpenCode Implementation”OpenCode at commit 7ed449974864361bad2c1f1405769fd2c2fcdf42 exposes a first-class question tool plus API/UI infrastructure around a pending-request broker.
Tool Contract (QuestionTool)
Section titled “Tool Contract (QuestionTool)”Tool definition:
references/opencode/packages/opencode/src/tool/question.ts
Schema:
questions: Question.Info[](withoutcustomoverride in tool input)
Execution:
QuestionTool.execute(...)callsQuestion.ask(...)- blocks until user replies/rejects
- returns formatted summary in tool output and raw answers in metadata
In-Memory Pending Request Broker (Question.ask)
Section titled “In-Memory Pending Request Broker (Question.ask)”Core broker:
references/opencode/packages/opencode/src/question/index.ts
Data model:
Question.Info:question,header,options, optionalmultiple, optionalcustomQuestion.Request: question packet withsessionIDand optional tool call linkageQuestion.Answer:string[]
Flow:
- generate question id (
Identifier.ascending("question")) - store pending resolver in in-memory map
- publish
question.askedbus event - wait on Promise
- resolve on
Question.reply(...) - reject on
Question.reject(...)withRejectedError
This is a clean async rendezvous model.
Client/Server API and TUI
Section titled “Client/Server API and TUI”HTTP routes:
references/opencode/packages/opencode/src/server/routes/question.tsGET /questionlist pendingPOST /question/:requestID/replyPOST /question/:requestID/reject
TUI sync/event ingestion:
references/opencode/packages/opencode/src/cli/cmd/tui/context/sync.tsx- tracks
question.asked/question.replied/question.rejected
- tracks
Question modal UI:
references/opencode/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx
Notable UI behavior:
- single-question auto-submit for simple single-select
- multi-question tabbed prompt with final confirm tab
- multi-select support (
multiple) - custom typed answer support (
custom) - keyboard-first navigation and escape-to-reject
Main session view prioritizes overlays:
session/index.tsx- permission prompt first
- then question prompt
- main composer disabled while either is active
Agent Permissions and Non-Interactive Mode
Section titled “Agent Permissions and Non-Interactive Mode”Question execution is permission-aware.
Defaults live in:
references/opencode/packages/opencode/src/agent/agent.ts
Important defaults:
- global default permission includes
question: deny - built-in
buildandplanagents explicitly allow question
CLI non-interactive run mode hard-denies question prompts:
references/opencode/packages/opencode/src/cli/cmd/run.ts- session rules include
question: deny
- session rules include
This prevents deadlocks in headless execution.
Tool Registry and ACP Opt-In
Section titled “Tool Registry and ACP Opt-In”Tool registration:
references/opencode/packages/opencode/src/tool/registry.ts
Question tool inclusion is conditional:
- enabled for app/cli/desktop clients
- or explicit
OPENCODE_ENABLE_QUESTION_TOOL
ACP docs explicitly call out opt-in:
references/opencode/packages/opencode/src/acp/README.md- QuestionTool excluded by default in ACP
- enable only when client can handle interactive prompts
Pitfalls & Hard Lessons
Section titled “Pitfalls & Hard Lessons”Tool Schema vs UI Capability Drift
Section titled “Tool Schema vs UI Capability Drift”Codex and OpenCode support different question primitives.
Codex model tool is effectively single-select (+other notes). OpenCode supports multi-select and custom freeform answers per question.
If OpenOxide abstracts this poorly, provider behavior will diverge by client/runtime.
”Other” Must Be Deterministic
Section titled “”Other” Must Be Deterministic”Codex forces is_other = true in handler.
OpenCode treats custom answers as configurable.
If OpenOxide toggles this dynamically without a stable contract, prompts and UI will disagree about what answers are possible.
Non-Interactive Deadlocks
Section titled “Non-Interactive Deadlocks”Headless clients cannot satisfy blocking question requests.
OpenCode solves this by hard-denying question permission in run mode. Codex constrains availability to Plan mode and emits explicit errors outside it.
OpenOxide should fail fast in non-interactive contexts, not wait forever.
Cancellation/Interruption Semantics Are Easy to Break
Section titled “Cancellation/Interruption Semantics Are Easy to Break”Codex TUI has TODOs around interrupted question result persistence. OpenCode rejects pending question promises explicitly.
If interruptions are not modeled clearly, pending question state leaks and subsequent turns can become inconsistent.
Secret Answers Need a First-Class Type
Section titled “Secret Answers Need a First-Class Type”Codex protocol supports isSecret; OpenCode question model does not expose an equivalent dedicated secret field in this path.
Packing secrets into generic freeform text risks accidental logging or history rendering.
OpenOxide should treat secret answer handling as first-class, not a UI hint.
OpenOxide Blueprint
Section titled “OpenOxide Blueprint”Crate and Core Types
Section titled “Crate and Core Types”Create openoxide-question with these core types:
QuestionRequest { request_id, turn_id, source, questions, mode }Question { id, header, prompt, kind, options, allow_custom, secret }QuestionKind:SingleSelectMultiSelectFreeform
QuestionResponse { answers: HashMap<QuestionId, AnswerPayload> }AnswerPayload { selected: Vec<String>, note: Option<String> }
Do not encode notes as sentinel strings ("user_note: ...").
Keep note as typed field.
Two Shapes: Model Tool vs Internal Prompt
Section titled “Two Shapes: Model Tool vs Internal Prompt”Define two request constructors:
QuestionRequest::from_model_tool(...)- enforces model-safe schema
- applies defaults (max question count, options shape)
QuestionRequest::from_internal_system(...)- allows secret/freeform questions for runtime subsystems
This mirrors Codex’s separation between model-tool constraints and internal event reuse.
Runtime Flow
Section titled “Runtime Flow”Standard flow:
- tool handler or subsystem builds request
- runtime registers pending request in turn state
- event bus emits
QuestionRequested - client responds with
QuestionAnsweredorQuestionRejected - runtime resolves or cancels pending promise
Use timeout/cancellation hooks to auto-resolve with explicit Rejected state when session interrupts.
UI and Transport
Section titled “UI and Transport”Transport contract should include:
- API endpoint(s) to list pending questions
- reply/reject endpoints
- event stream notifications for ask/reply/reject
UI contract should include:
- overlay precedence (permissions first, then questions)
- input locking while question unresolved
- keyboard-first and mouse support
- explicit unanswered confirmation for multi-question batches
Safety Rules
Section titled “Safety Rules”- disable question tool in non-interactive sessions by default
- return deterministic model-facing error when unavailable
- cap question count per call (recommend <=3)
- require stable question ids for mapping
- redact secret answers from normal history renderers and logs
Recommended Crates
Section titled “Recommended Crates”serde/schemarsfor schema and wire typestokio::sync::oneshot+tokio::select!for pending response orchestrationindexmapfor stable answer ordering where UI requires deterministic displaytracingwith structured redaction for secret input paths
Test Matrix
Section titled “Test Matrix”Minimum tests:
- tool unavailable mode returns model-facing error
- question request + answer round trip
- reject path and interruption path
- queued question ordering
- secret answer redaction in history/log serialization
- non-interactive policy auto-denial
- multi-question unanswered confirmation behavior