Dynamic forms and the agent-to-user communication grammar

Failure modes

Rendering on a surface that has none

If a form is rendered to the widget surface but the client cannot post back (sendPrompt unavailable), the submit button reports it and the caller should fall back to form_to_askuserquestion. A rich widget needs a widget-capable client; a plain terminal degrades to the menu fallback by design.

"Registered ≠ working until the server reboots"

A newly added construct or field reaches the live MCP server only after the server restarts on the new version — the tool schema is loaded at startup. Verify the live elicitation_render_widget schema actually carries a new enum value before asserting the construct works end-to-end.

A template cast fails on a slot, not on the form

form_from_template validates the declaration and the values in both directions before it substitutes anything: a missing value, an extra name, a declared-but-unused slot, or an undeclared {placeholder} in a field each surface as their own problem line. Read the slot problems first — the form-level validation only runs on a successfully cast definition. An unknown template name lists every available template, so a typo is a one-look fix.

A progress form whose blocked items disagree with its options

The bridge enforces set(blocked labels) == set(options); a mismatch raises FormValidationError. The picker can never offer a non-existent blocker or omit a real one.