AskUserQuestion
Let the agent ask interactive questions during a run
AskUserQuestion is a dynamic tool that lets the agent ask the user questions during execution. Your UI renders the questions and submits answers to an answer API. The runner waits up to 60 seconds by default, then continues with either the submitted answers or a model-visible timeout result.
Architecture
Runner (inside sandbox)
- Creates the pending approval file
- Polls approval file every 500ms
- Continues when status == "completed"
- Continues with a timeout result after 60s by default
↕
Answer API (server)
- Receives { toolCallId, questions, answers }
- Writes approval file via submitAnswer()
↕
UI
- Renders questions
- Submits answers on selectionKey points:
- The runner creates the pending file and then reads/polls it
- The answer API overwrites the file on every update
- If a timeout occurs, the runner continues with any partial answers plus an explicit timeout marker
- Set
askUserQuestionTimeoutMsin runner options to override the 60-second default
1. Answer API
Use the same workdir as your chat sandbox.
import path from "node:path";
import { LocalSandbox, submitAnswer, type Question } from "@sandagent/sdk";
export async function POST(request: Request) {
const { toolCallId, questions, answers } = await request.json();
const sandbox = new LocalSandbox({
workdir: path.join(process.cwd(), "workspace"),
});
await submitAnswer(sandbox, { toolCallId, questions, answers });
return Response.json({ success: true });
}2. Render in UI
Detect the tool part and render a component using useAskUserQuestion.
import { useAskUserQuestion } from "@sandagent/sdk/react";
import type { DynamicToolUIPart } from "ai";
function AskUserQuestionUI({ part }: { part: DynamicToolUIPart }) {
const {
questions,
answers,
isCompleted,
isWaitingForInput,
selectAnswer,
isSelected,
} = useAskUserQuestion({
part,
answerEndpoint: "/api/answer",
});
// Render questions/options; call selectAnswer on click
return null;
}3. Flow Summary
- Agent emits
AskUserQuestiontool call - UI renders the tool and submits answers
- Answer API writes approval file via
submitAnswer - Runner reads the file and continues when the answer is completed
- If no completed answer arrives before the timeout, the runner returns a timeout result to the model so it can keep chatting
Data Structures
Input
interface AskUserQuestionInput {
questions: Array<{
question: string;
header?: string;
options?: Array<{ label: string; description?: string }>;
multiSelect?: boolean;
}>;
}Output
interface AskUserQuestionOutput {
questions: Array<{...}>;
answers: Record<string, string>; // multi-select = comma-separated
}Approval File
Location: {workdir}/.sandagent/approvals/{toolCallId}.json
{
"questions": [
{
"question": "What is your preferred language?",
"options": [
{ "label": "TypeScript" },
{ "label": "Python" }
]
}
],
"answers": {
"What is your preferred language?": "TypeScript"
},
"status": "completed",
"timestamp": "2025-01-30T12:00:00.000Z"
}Troubleshooting
- No progress → verify both APIs use the same
workdir - UI not rendering → ensure you handle
dynamic-toolparts withtoolName === "AskUserQuestion" - Answers not applied → confirm
answerEndpointmatches your API route