Interactive Form Defer Flow
This document describes the standard render and resume flow for the interactive_form_question MCP tool. The flow lets an Agent show a structured form while running, then continue from a later user message after the user submits the form.
Design Goalsβ
Interactive forms must follow these constraints:
- MCP remains the only tool entry point. Argument validation, duplicate prevention, and form normalization happen in the MCP server.
- The frontend must not render forms from raw tool input. It can only render the
render_payloadwritten by the backend into the message block. - After a form is displayed, the current Agent run must stop. Waiting for user input must not be represented as a fake user message.
- After the user submits the form, the task resumes through the next user message carrying an answer payload.
- A single subtask can successfully display at most one
interactive_form_questionform.
Standard Flowβ
MCP Output Contractβ
When interactive_form_question successfully displays a form, the MCP server must write render_payload to the tool block:
{
"type": "interactive_form_question",
"ask_id": "ask_123",
"task_id": 1,
"subtask_id": 2,
"questions": []
}
The tool result is only used to tell the runtime to stop the current run:
{
"__deferred_user_input__": true,
"success": true,
"status": "waiting_for_user_response",
"ask_id": "ask_123"
}
The frontend may render only when all of these are true:
- The block is an
interactive_form_questiontool block. - The block has a valid
render_payload. render_payload.typeisinteractive_form_question.render_payload.questionsis a non-empty array.
The frontend must not read raw tool input to render a form. Raw input can contain invalid fields produced by the model; only the MCP-normalized payload is a trusted UI schema.
Chat Shell Behaviorβ
After Chat Shell calls the tool, if the tool result satisfies the defer contract:
- Emit the tool done event so the message block is complete.
- Raise an internal deferred exit to stop the current ReAct run.
- Wait for the user's next message.
Do not write the deferred result as user content, and do not inject βpending user inputβ into the model context.
Claude Code Behaviorβ
Claude Code uses deferred_mcp_proxy only for MCP tools whose name contains interactive_form_question. All other MCP tools continue to use the Claude Code SDK's native path.
The proxy flow is:
- The SDK hook catches the
interactive_form_questiontool call. - The proxy forwards the original arguments to Backend MCP.
- Backend MCP validates the payload, writes
render_payload, and returns a deferred result. - The response processor emits tool done and ends the turn with
stop_reason=tool_deferred.
User Answer Validationβ
Before accepting a later user message, Backend checks whether the current task has a pending interactive form:
- If no form is pending, form answers are rejected.
- If a form is pending, ordinary text messages are rejected until the user submits or cancels the form.
- The answer payload must include
type=interactive_form_questionand the matchingtool_use_id.
This prevents ordinary chat messages from being treated as form results and keeps invalid form answers out of model context.
Skill Publishing Authenticationβ
Skill creation scripts use WEGENT_SKILL_IDENTITY_TOKEN to publish generated Skills at task runtime. This token is a Skill runtime identity, not a general login token.
Skill publishing APIs must support this token:
GET /api/v1/kinds/skills: used by publish scripts to check for an existing Skill, especially withexact_match=true.POST /api/v1/kinds/skills/upload: create a Skill.PUT /api/v1/kinds/skills/{skill_id}: overwrite an existing Skill.
General business APIs should not accept WEGENT_SKILL_IDENTITY_TOKEN by default, otherwise the Skill runtime identity would become a general user credential.