Form Builder
Form Builder
Form Builder is the primary way to create interactive UI components on MelodyArc. When an AI Operator needs a human associate to provide input, make a decision, or review and confirm data, Form Builder delivers the interface that makes that interaction possible.
This is the foundation of human-in-the-loop AI Operator flows — the most common pattern on MelodyArc. An AI Operator handles the logic, but at key decision points, it hands off to an associate through a Form Builder component rendered directly in their workspace.
Dynamic Forms (XML DSL) — Recommended
The XML domain-specific language (DSL) is the current primary method for building Form Builder components. It lets you define rich, interactive forms as a compact XML string — no component configuration objects required. The DSL renders into fully functional UI components that respond to associate input in real time.
Where DSL forms render
DSL forms can be targeted to three UI surfaces:
| Surface | Description |
|---|---|
| MACIE Chat Thread | Inline within the AI Operator's conversation with the associate |
| Action Panel | The right-hand panel in the associate workspace |
| Task Action Drop Down | Contextual actions available within a task |
How it works
DSL markup is embedded in the instructions field of a Form Builder page — the platform renders it as a live interactive component within the surrounding Form Builder UI. This remains the standard pattern until Form Builder is fully deprecated in 2026 (see Legacy Form Builder below). DSL can also be used in any other Form Builder component, either within invoke point attributes or directly in code points.
The example below shows a complete invoke_component Point. The DSL is passed as the instructions value on a page, and associate-provided values are mapped back to the data token via invoke_data_token.
{
"organization_id": "org_YOUR_ORG_ID",
"name": "collect_user_feedback",
"partition": "op_skills",
"description": "Collect structured feedback from the user via a form_builder UI",
"tags": ["skill", "ui", "invoke_component", "operator:chat"],
"inputs": {
"task.operator": { "evaluate": "=chat" },
"_this.status": { "evaluate": "=ready" },
"_this.name": { "evaluate": "=collect_user_feedback" }
},
"attributes": {
"friendly_name": "Collect user feedback",
"invoke_pages": [
{
"header": "Quick Feedback",
"instructions": "Please fill this out so I can help you better.",
"fields": [
{
"id": "satisfaction",
"title": "How satisfied are you?",
"component": "radio_group",
"instructions": "Choose one option.",
"required": true,
"default_value": "good",
"options": [
{ "value": "great", "label": "Great" },
{ "value": "good", "label": "Good" },
{ "value": "ok", "label": "Okay" },
{ "value": "bad", "label": "Bad" }
]
},
{
"id": "topic",
"title": "What topic is this about?",
"component": "select",
"instructions": "Pick the closest match.",
"required": true,
"options": "@feedback_topics"
},
{
"id": "details",
"title": "Anything else you want to add?",
"component": "text_area",
"instructions": "Optional, but helpful.",
"required": false
}
]
}
],
"invoke_data_token": {
"request.feedback.satisfaction": "satisfaction",
"request.feedback.topic": "topic",
"request.feedback.details": "details"
},
"add_keys": {
"response.user_response": "Thanks — I've got your feedback.",
"request.feedback.captured_by": "_this.name"
}
},
"code": "invoke_component",
"allow_taskless": false,
"is_quicklink": false,
"allow_dynamic_code": false,
"allow_dynamic_key": false,
"version": "latest",
"search_resource_type": "invoke"
}DSL example
The screenshot above is produced by the following DSL. It demonstrates the core patterns: state initialization, dynamic variable interpolation via ${}, conditional rendering, and two-way input binding via path. Note that the DSL uses explicit core: namespace prefixes, which is the standard convention.
<core:card padding="lg">
<state:set path="form.allow_send" value="yes" />
<state:set path="form.message_rejection_reason" value="" mode="unset" />
<state:set path="form.message_header" value="${internal_only}" mode="unset" />
<core:paper
withBorder="false"
radius="0"
p="xl"
shadow="md"
style='{
"background":"#ffffff",
"border":"1px solid rgba(0,0,0,0.08)",
"boxShadow":"0 14px 40px rgba(0,0,0,0.10)"
}'
>
<core:stack gap="lg">
<core:stack gap="xs">
<core:badge variant="light" color="blue" style='{"width":"fit-content"}'>
<core:if path="form.message_header" equals="no">
Customer Facing Message
</core:if>
<core:if path="form.message_header" equals="yes">
Internal Workflow Message
</core:if>
</core:badge>
</core:stack>
<core:divider style='{"opacity":0.65}' />
<core:stack gap="sm">
<core:paper
radius="0"
p="md"
style='{
"background":"rgba(0,0,0,0.02)",
"border":"1px solid rgba(0,0,0,0.06)"
}'
>
<core:text size="sm" style='{"whiteSpace":"pre-wrap","lineHeight":"1.65"}'>
${message}
</core:text>
</core:paper>
<core:text size="sm" c="dimmed" style='{"lineHeight":"1.55"}'>
${reason}
</core:text>
</core:stack>
<core:divider style='{"opacity":0.65}' />
<core:stack gap="sm">
<core:text size="sm" fw="600">Can I proceed with this?</core:text>
<core:radioGroup path="form.allow_send" withAsterisk="true">
<core:stack gap="xs">
<core:radio value="yes" label="Yes" size="md" />
<core:radio value="no" label="No" size="md" />
</core:stack>
</core:radioGroup>
<core:if path="form.allow_send" equals="no">
<core:paper
radius="0"
p="md"
style='{
"background":"rgba(255, 0, 0, 0.03)",
"border":"1px solid rgba(255, 0, 0, 0.12)"
}'
>
<core:textarea
label="Reason (required)"
path="form.message_rejection_reason"
withAsterisk="true"
autosize="true"
minRows="5"
placeholder="Explain what needs to change (tone, facts, missing info, etc.)"
/>
</core:paper>
</core:if>
</core:stack>
</core:stack>
</core:paper>
</core:card>DSL basics
The DSL uses XML-like tags that map to Mantine UI components. Tags use the explicit core: namespace prefix. Dynamic values from the AI Operator's execution context are interpolated using ${} syntax.
State binding — attach any input to a state path using path. The associate's input is read from and written to that path automatically:
<core:textInput label="Email" path="form.email" />
<core:select label="Priority" path="form.priority" data='[{"value":"high","label":"High"},{"value":"low","label":"Low"}]' />Conditional rendering — show or hide elements based on current state:
<core:if path="form.allow_send" equals="no">
<core:textarea label="Reason" path="form.rejection_reason" withAsterisk="true" />
</core:if>Initial values — seed state from markup, with optional mode="unset" to only apply if the path has no value yet:
<state:set path="form.status" value="pending" />
<state:set path="form.notes" value="${operator_notes}" mode="unset" />Dynamic variable interpolation — inject values from the AI Operator's execution context directly into the markup:
<core:text size="sm">${message_body}</core:text>Layout — compose with cards, stacks, grids, and groups:
<core:card>
<core:stack gap="md">
<core:grid gutter="md">
<core:gridCol span='{"base":12,"md":6}'>
<core:textInput label="First name" path="form.firstName" />
</core:gridCol>
<core:gridCol span='{"base":12,"md":6}'>
<core:textInput label="Last name" path="form.lastName" />
</core:gridCol>
</core:grid>
</core:stack>
</core:card>For a full DSL component reference, see Dynamic Forms.
Legacy Form Builder
Form Builder is invoked from a code point using get_component(). The function returns a configuration object that defines what the associate sees — one or more pages, each with a header, instructions, and a set of named input fields. When the associate completes the form and submits, the platform writes their responses back to the data token using the data_token map, making those values available to the rest of the AI Operator's execution flow.
async function get_component() {
return {
component_name: "form_builder",
friendly_name: _token.friendly_name,
pages: [
{
header: "Confirm Action",
instructions: "Review and confirm the details below.",
fields: [
{
id: "disposition",
title: "Proceed?",
component: "radio_group",
required: true,
options: [
{ value: "confirm", label: "Yes, confirm" },
{ value: "cancel", label: "No, cancel" },
],
},
],
},
],
data_token: {
"temp.disposition": "disposition",
},
};
}Each field declares a named component type that determines which UI element renders. The data_token maps each field's id to a path in the data token where its value will be written. friendly_name sets the label displayed in the associate's task pane.
get_component() and data_token are not going away. What is being retired in 2026 is the named component type system and the rigid page-and-fields structure it requires. The DSL replaces that layer with far more expressive, unconstrained markup. Until then, the component types below remain fully supported.
Supported components
View all legacy Form Builder components
The following components are available within the fields array on any page.
Text Input (text_input)
Captures free-form text with optional validation. Key properties: id, title, component, instructions, default_value, required, reg_ex, placeholder
Textarea (textarea)
Multi-line text input for longer responses. Key properties: id, title, component, instructions, default_value, required, size
Markdown Editor (markdown_editor)
Rich text editor supporting markdown formatting. Key properties: id, title, component, instructions, default_value, required, size
Select (select)
Dropdown list for selecting a single option. Key properties: id, title, component, options, default_value, required, allow_custom
Multi-Select (multi_select)
Dropdown list for selecting multiple options. Key properties: id, title, component, options, default_value, required, allow_custom
Select Tags (select_tags)
Tag-style multi-select input. Key properties: id, title, component, options, default_value, required, allow_custom
Radio Group (radio_group)
Mutually exclusive option selection displayed as radio buttons. Key properties: id, title, component, options, required Options shape: [{ value: string, label: string }]
Checkbox (checkbox)
Single boolean toggle. Key properties: id, title, component, default_value, required
Path Evaluator (path_evaluator)
Evaluates entity paths against defined input criteria. Key properties: id, title, component, paths, default_value, required, allow_custom, instructions
Date Picker (date_picker)
Calendar-based date selection. Key properties: id, title, component, default_value, required
Time Picker (time_picker)
Clock-based time selection. Key properties: id, title, component, default_value, required
File Upload (file_upload)
Accepts file uploads from the associate. Key properties: id, title, component, required, accepted_types
Display Text (display_text)
Read-only text displayed to the associate. Does not capture input. Key properties: id, title, component, content
Display Image (display_image)
Renders an image within the form. Key properties: id, component, url, alt
Updated 1 day ago
