Skip to content

Elicitation Forms

Quickstart

Build a form from plain data, render it to the widget surface, and validate the answer that posts back:

from attune.elicitation import (
    form_from_dict,
    form_to_widget_html,
    collect_form_response,
)

form = form_from_dict({
    "title": "Release plan",
    "fields": [{
        "id": "bump",
        "text": "Which version bump?",
        "type": "decision",
        "options": ["patch", "minor", "major"],
        "recommended": "minor",
        "rationale": "Three additive features since the last tag.",
        "option_notes": {"minor": "new API, backward-compatible"},
    }],
})

html = form_to_widget_html(form)          # pass straight to show_widget
# … the user picks an option; the widget posts {"bump": "minor"} back …
response = collect_form_response(form, {"bump": "minor"})
assert response.responses["bump"] == "minor"

Tasks

Render a decision

A recommended option with a rationale and per-option tradeoffs, rendered as cards (recommended badged and ordered first):

form = form_from_dict({
    "title": "Approval",
    "fields": [{
        "id": "gate", "text": "High-severity gate failed — proceed?",
        "type": "decision",
        "options": ["Fix and retry", "Approve and continue"],
        "recommended": "Fix and retry",
        "rationale": "Two findings are unverified.",
    }],
})

Render a pushback

Disagreement framed as dissent — the user's approach beside the agent's alternative, under a "why I'd push back" rationale:

form = form_from_dict({
    "title": "Approach",
    "fields": [{
        "id": "call", "text": "Build it how?",
        "type": "pushback",
        "options": ["Hand-roll a parser", "Reuse the existing one"],
        "user_position": "Hand-roll a parser",
        "recommended": "Reuse the existing one",
        "rationale": "The existing parser already handles every case here.",
    }],
})

Render a progress report with a blocked-item picker

progress_items carries every item by status; the blocked subset must equal options (the picker). When nothing is blocked, the construct degrades to a pure status display with no answer:

form = form_from_dict({
    "title": "Where we are",
    "fields": [{
        "id": "next", "text": "Which blocker first?",
        "type": "progress",
        "options": ["Fix the failing lane"],
        "recommended": "Fix the failing lane",
        "progress_items": [
            {"label": "Spec drafted", "status": "done"},
            {"label": "Tests written", "status": "in_flight"},
            {"label": "Fix the failing lane", "status": "blocked"},
        ],
    }],
})

Render a select as a numbered list

form = form_from_dict({
    "title": "Delivery",
    "fields": [{
        "id": "fmt", "text": "Pick a format:",
        "type": "single_select",
        "options": ["Bulleted brief", "Numbered steps", "Markdown table"],
        "list_style": "ordered",
    }],
})

Reference

Public API — attune.elicitation

Symbol Purpose
form_from_dict(data) Build and validate a FormSchema from plain data; raises FormValidationError on a malformed definition.
form_to_widget_html(form, message="") Render the rich inline HTML form for show_widget.
form_to_askuserquestion(form, batch_size=4) Render batched AskUserQuestion payloads (the fallback surface).
form_to_elicitation_schema(form) Render a native MCP elicitation JSON schema.
collect_form_response(form, raw_answers, template_id="") Validate answers (R4) and return a FormResponse; raises FormValidationError.
WIDGET_RESPONSE_MARKER The sentinel key the widget posts back under.
FormValidationError Raised for a malformed definition or answer; lists every problem.

QuestionType values

text_input, textarea, single_select, multi_select, boolean, number (with minimum / maximum), date (YYYY-MM-DD), and the three construct types decision, pushback, progress — ten in all.

Construct-specific FormQuestion fields

Field Used by Meaning
recommended decision / pushback / progress option to badge and order first; must be in options
rationale decision / pushback / progress the "why" callout
option_notes decision / pushback {option: one-line tradeoff}
user_position pushback the option that is the user's stated approach
progress_items progress {label, status, detail?} items; blocked subset must equal options
list_style single_select / multi_select "ordered" or "unordered" list render

MCP tools

elicitation_render_form, elicitation_render_widget, elicitation_collect_response, and elicitation_ask — the same model, exposed for agents that drive forms through the MCP server.