🏖️ SandAgent

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 for an approval file in the sandbox, then continues.

Architecture

Runner (inside sandbox)
  - Polls approval file every 500ms
  - Continues when status == "completed"

Answer API (server)
  - Receives { toolCallId, questions, answers }
  - Writes approval file via submitAnswer()

UI
  - Renders questions
  - Submits answers on selection

Key points:

  • The runner only reads/polls the file — it does not create it
  • The answer API overwrites the file on every update
  • If timeout occurs, the runner may continue with partial answers

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

  1. Agent emits AskUserQuestion tool call
  2. UI renders the tool and submits answers
  3. Answer API writes approval file via submitAnswer
  4. Runner reads the file, continues, and the tool becomes completed

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-tool parts with toolName === "AskUserQuestion"
  • Answers not applied → confirm answerEndpoint matches your API route

On this page