# Question Tool Design **Date:** 2026-04-09 **Project:** `/home/alex/dotfiles` **Target file:** `.pi/agent/extensions/question.ts` ## Goal Add a tracked pi extension that gives the agent a single `question` tool for asking either one question or multiple questions in interactive mode, while always preserving a final user escape hatch: **"Something else…"** opens inline free-text entry when none of the listed options fit. ## Context - Pi supports custom tools through TypeScript extensions placed in auto-discovered extension directories. - This dotfiles repo already tracks pi configuration under `.pi/agent/`. - The working extension directory `.pi/agent/extensions/` is currently empty. - Pi’s upstream examples already include: - a single-question `question.ts` example - a multi-question `questionnaire.ts` example - The requested tool should combine those use cases into one obvious agent-facing tool. ## User-Approved Requirements 1. The tool must be tracked in this repo at: - `/home/alex/dotfiles/.pi/agent/extensions/question.ts` 2. The tool name should be: - `question` 3. The tool must support both: - a single question - multiple questions in one interaction 4. Every question is multiple-choice, but the UI must always append a final choice: - **"Something else…"** 5. Choosing **"Something else…"** must allow direct user text entry. 6. Question options should support machine-friendly values and user-facing labels: - `{ value, label, description? }` 7. This should be a unified tool, not separate `question` and `questionnaire` tools. ## Recommended Approach Implement a single extension modeled after pi’s upstream `question.ts` and `questionnaire.ts` examples: - one registered tool: `question` - one parameter shape: `questions: Question[]` - one UI that adapts to question count: - single-question picker for `questions.length === 1` - multi-question review flow for `questions.length > 1` This keeps the agent-facing API simple while still supporting richer user clarification flows. ## Tool Contract The extension will register a tool with this conceptual input shape: ```ts { questions: Array<{ id: string; label?: string; prompt: string; options: Array<{ value: string; label: string; description?: string; }>; }>; } ``` ### Field intent - `id`: stable identifier for the answer - `label`: short summary label for tabs/review UI; defaults to `Q1`, `Q2`, etc. - `prompt`: the full question shown to the user - `options`: predefined choices the model wants the user to pick from ### Normalization rules Before rendering the UI: 1. Ensure at least one question exists. 2. Ensure each question has a usable short label. 3. Preserve the provided predefined options as-is. 4. Append a final synthetic option to every question: - label: `Something else…` - behavior: switch into inline text entry 5. Do not require the model to explicitly include the synthetic option. ## Interaction Design ### Single-question mode When exactly one question is provided: - display the prompt - display numbered predefined options - automatically display the final appended option: - `Something else…` - selecting a predefined option completes the tool immediately - selecting `Something else…` opens inline free-text entry - `Esc` in the picker cancels the tool - `Esc` in text entry exits text entry and returns to the option list ### Multi-question mode When multiple questions are provided: - show one question at a time - allow tab or left/right navigation between questions - append `Something else…` to every question - after answering one question, move to the next question - include a final review/submit step summarizing all current answers - allow navigating back to change answers before final submission - submit only from the review step This provides a guided flow without requiring separate tools. ## Answer Model The tool result should always remain structured. Conceptual result shape: ```ts { questions: Question[]; answers: Array<{ id: string; value: string; label: string; wasCustom: boolean; index?: number; }>; cancelled: boolean; } ``` ### Predefined option answers For a predefined choice: - `value` = the provided option value - `label` = the provided option label - `wasCustom` = `false` - `index` = 1-based index of the selected predefined option ### Custom answers via “Something else…” For a typed answer: - `value` = typed text - `label` = typed text - `wasCustom` = `true` - `index` is omitted This gives the agent consistent structured data while preserving user freedom. ## Rendering The extension should provide readable tool renderers: ### `renderCall` Show: - tool name (`question`) - question count - short labels or summary where useful ### `renderResult` Show: - `Cancelled` when the user aborts - one concise success line per answered question - whether an answer was predefined or custom when helpful The rendering should remain compact in normal use and not dump full raw JSON unless the default fallback is needed. ## Error Handling The tool should return structured results for expected user/runtime states instead of throwing. ### Non-interactive mode If pi is running without interactive UI support: - return a clear text result indicating UI is unavailable - mark the interaction as `cancelled: true` in details - do not crash the session ### Invalid input If `questions` is empty: - return a clear text result like `Error: No questions provided` - include a structured details payload with `cancelled: true` ### User cancel If the user cancels from the picker or review flow: - return `cancelled: true` - do not throw an exception ### Empty custom text If the user enters free-text mode and submits an empty value: - do not accept an empty answer - keep the user in text-entry mode until they provide non-empty text or press `Esc` - avoid returning meaningless blank answers to the model ## File Structure Implementation stays in one file unless complexity clearly justifies splitting later: - Create: `/home/alex/dotfiles/.pi/agent/extensions/question.ts` Internal sections inside the file should stay logically separated: 1. types and schemas 2. question normalization helpers 3. single-question UI flow 4. multi-question UI flow 5. tool registration 6. call/result rendering ## Loading and Usage Because the file will live in an auto-discovered project extension directory, the expected activation flow is: 1. start pi from the dotfiles repo or a directory where the project extension is in scope 2. use `/reload` if pi is already running 3. allow the model to call `question` when clarification is needed ## Testing Strategy No dedicated automated test harness is required for the first version. Manual verification should cover: 1. **Single question, predefined answer** - tool returns selected option value/label 2. **Single question, custom answer** - selecting `Something else…` opens text entry and returns typed text 3. **Single question, cancel** - cancellation returns structured cancelled result 4. **Multi-question, all predefined** - step-through and final review work correctly 5. **Multi-question, mixed predefined/custom** - at least one typed answer and one predefined answer are preserved correctly 6. **Multi-question, edit before submit** - user can revisit and change answers before final submission 7. **Empty custom submission** - blank text is rejected or bounced back safely 8. **Non-interactive mode** - tool returns a clear UI-unavailable result ## Non-Goals The first version will not add: - separate text-only question types - nested conditional question trees - validation rules beyond basic non-empty custom text handling - persistence beyond normal pi session/tool result storage - a separate `questionnaire` tool name ## Acceptance Criteria The work is complete when: 1. `.pi/agent/extensions/question.ts` exists in this repo 2. pi discovers the extension via project auto-discovery 3. the agent has a single `question` tool 4. the tool supports both one-question and multi-question flows 5. every question automatically ends with `Something else…` 6. selecting `Something else…` allows direct typed input 7. results are structured and distinguish custom answers from predefined ones 8. cancel/error states return cleanly without crashing the session