Vancetope — Document Kind questions
Specifies the
questionskind: multiple questions to the user in one message, each answered separately. Inline in the chat, it’s a wizard (one page per question, progress bar, overview before sending); the answers are returned to the Engine in a structured way. Codec kind with Java/TS twin and validation. See also: inline-and-embedded-content | doc-kind-checklist | prompts-and-manuals | web-ui
1. Purpose
An Engine needs several related decisions from the user before it can continue working. Without this kind, there is only one question per round (ASK_USER) or a list in free text that a model has to parse. questions provides both in a structured way: the questions as a Fence, the answers as a typed list.
The first user is the Wonko Engine (Review-Loop); however, the kind is engine-neutral — any Chat Engine can ask a questionnaire.
Distinctions:
- ASK_USER (Arthur/Eddie) has also been generating this Fence since 2026-10-14 — one question with options; there is only one format and one renderer per interface.
- Wizards (
wizards.md): YAML forms that generate a Prompt, started by the user.questionscomes from the Engine and responds to it. checklist: tasks to be checked off, no return channel.
Design Principle — One questionnaire, two readers. The answer travels as a readable short form in the message text (every Engine understands it, the history remains readable) and as a typed field questionAnswers in the Steer-Request (Engines that work structurally only read this).
Design Principle — Partial answers are valid. The user never has to answer everything. What is left open when sending is considered “later”.
2. Format
Body is YAML (inline as ` ```questions -Fence; stored with $meta` header, also JSON):
id: q-7f3a # optional; if missing, the client forms a stable one from the content
intro: Three points regarding the requirement
questions:
- key: eu-region
text: In which region will data be stored?
kind: choice # choice | text | confirm (Default text)
options: [eu-central-1, eu-west-1]
context: "must be stored … within the European Union"
- key: retention
text: Correct retention to 10 years?
kind: confirm
proposal: 10 years instead of 5
weight: blocker # blocker | important (Default) | minor
| Field | Required | Meaning |
|---|---|---|
questions[].key |
yes | unique within the set; the answer references it |
questions[].text |
yes | the question |
questions[].kind |
no | choice requires ≥ 2 options; confirm carries proposal |
questions[].options |
for choice | Strings or {label, description} |
questions[].proposal |
for confirm | Assumed by the questioner |
questions[].context |
no | Quote/reference for orientation |
questions[].weight |
no | Weight, displayed as a badge |
intro, id |
no | Introductory sentence, set ID |
Validation (Codec, both sides): at least one question, key and text set, keys unique, kind from the list, choice with ≥ 2 options. An invalid body is rejected (kind_validate reports questions-parse, the Web UI shows the error instead of a half-wizard). Case for kind/weight does not matter, options can be plain strings.
The standard outcomes “looks good”, “later”, “don’t know” are not in the options — the wizard always offers them.
3. Answer
A normal process-steer is sent with
content: one line per question,- <Question> → <Answer>— for one question, only the answer, as if the user had typed it (an ASK_USER Engine understands this);questionAnswers:QuestionSetAnswerDto { setId, answers[] }withQuestionAnswerDto { key, outcome, value },outcome∈ANSWERED | SETTLED | LATER | UNKNOWN. Unanswered questions are included asLATER.
The path through the Brain: ProcessSteerRequest.questionAnswers → SteerMessage.UserChatInput.questionAnswers → Pending-/Engine-Message (mapper in both directions) → Engine.
4. Web UI
- Inline in chat (
QuestionsView, Renderer-Registryquestions): Wizard — progress bar “Question n of N”, question with badge/quote, confirm button forconfirm, option buttons forchoice, free text field always, “looks good”/”later”/”don’t know”, back. Afterwards, an overview with all answers (each clickable to change), “Send answers” and “Back”. One question (ASK_USER) is compact: no progress bar, no overview — clicking an option, confirming, an outcome or free text sends immediately. Question texts are Markdown. The normal chat input field remains usable — a free text answer there is also valid. - Active as long as it is open: a newer questionnaire replaces it. A questionnaire with
idremains answerable until a user message is marked as its answer (meta.questionsAnswered = <id>, set by the Engine that accepted the answer) — a follow-up question to the Engine leaves it open. Without anid, any subsequent user message closes it.ChatViewcalculates the context,MessageBubblepasses it viaprovideto the Wizard (questionsActionableinquestionsAnswers.ts); when closed, it shows as read-only. - Sending path: the wizard fires a bubbling DOM event
vance-question-answers;ChatViewforwards it asquestion-answers,ChatApp/ChatSidePanelsend viaChatComposer.setTextAndSend(text, { questionAnswers }). - Editor / embedded: read-only list of questions. Creation via “New Document” (YAML-only).
5. foot and other Plain-Text Clients
foot (de.mhus.vance.foot.question):
- Parser: third twin (
QuestionsFence) — foot must not depend on vance-shared —, tested against the same fixture corpus (QuestionsFenceParityTest), same error texts. - Display:
ChatTerminal.chatMarkdownreplaces every valid Fence with readable Markdown (QuestionsText): introduction, numbered questions,[blocker], reference as quote, Assumption, options as[1] A [2] B. Invalid Fences remain. - Line-Wizard (
QuestionsWizard,PendingQuestions): a Fence in an Assistant message of the main process opens it, the input prefix shows1/3 ›oranswer ›. A number selects, Enter confirms the assumption, any other text is the answer;:ok:later:unknown:back:pause. After the last question, an overview: Enter sends, a number reopens the question. A question is sent with the first answer./-commands continue to the Dispatcher;:pauseexits the wizard,/answerresumes. - Closing: same rules as in Web (§4) — sent, user message marked as answered (
meta.questionsAnswered) or, withoutid, any user message; a new Fence replaces the old one. - Sent as in Web: readable text +
questionAnswers(Set-Id withoutidvia the same djb2 hash asquestionsAnswers.ts).
Other Plain-Text Clients see the YAML code block (inline-and-embedded-content §6); Engines assign free text answers themselves (Wonko: Interpret step).
6. Engines
- Description for models: exactly one — the Manual
kind-questions(Router entry inembed-fences). Engines read it viamanual_read('kind-questions'); where an Engine explicitly needs it, this Manual is injected, no second description (drift). - ASK_USER (Arthur, Eddie) with
options→AskUserQuestions.render: a Fence with one question (choicefrom two options, otherwisetextwith the option), withoutid— the Engine reads the answer as text, any subsequent user message closes the questionnaire. When forwarding (Eddie RELAY), the Fence travels as text. - Wonko generates the Fence in code via
QuestionsCodec.toFence— valid by construction, no model writes it. Wonko stores the user message that answered a questionnaire withmeta.questionsAnswered; a message that is not an answer (follow-up question) answers it directly and leaves the questionnaire open.
7. Implementation
| Part | Location |
|---|---|
| DTOs | vance-api de.mhus.vance.api.question |
| Codec (Java) | vance-shared document/kind/QuestionsCodec — parse, serialize, toFence; registered in CodecKindHandlers |
| Codec (TS) | vance-face kindViews/questionsCodec.ts |
| Parity | test-fixtures/kind-codecs/questions/ incl. expectedError-fixtures |
| Wizard | kindViews/QuestionsView.vue, kindViews/questionsAnswers.ts |
| Manual | _vance/manuals/kind-questions.md |