Skip to main content
One of the most powerful features of the Agent Stack is the ability to request structured data from users through interactive forms. Instead of relying on free-form text input, your agent can present users with specific fields, dropdowns, and other form elements to gather precise information. The Agent Stack provides a Form extensions that allows you to collect structured data from users in two ways:
  1. Initial form rendering - Present a form as the first interaction before users start a conversation with your agent
  2. Dynamic form requests - Request forms at any point during a multi-turn conversation when your agent needs specific structured input

Initial Form Rendering

For initial form rendering, you specify the form structure when injecting the extension and then parse the response using a Pydantic model. The form is presented to users before they start a conversation with your agent.

Quickstart

1

Import the form extension

Import FormServiceExtensionServer, FormServiceExtensionSpec, FormRender, and field types from the Agent Stack SDK.
2

Define your form data model

Create a Pydantic model with fields matching your form field IDs.
3

Add form parameter to your agent

Inject the form extension into your agent function using FormServiceExtensionSpec.demand(initial_form=FormRender(...)).
4

Parse form data

Call form.parse_initial_form(model=YourModel) to extract the submitted form data.
from typing import Annotated

from a2a.types import Message
from pydantic import BaseModel
from agentstack_sdk.server import Server
from agentstack_sdk.a2a.extensions.common.form import FormRender, TextField
from agentstack_sdk.a2a.extensions.services.form import (
    FormServiceExtensionServer,
    FormServiceExtensionSpec,
)

server = Server()


class UserInfo(BaseModel):
    first_name: str | None
    last_name: str | None


@server.agent()
async def initial_form_agent(
    _message: Message,
    form: Annotated[
        FormServiceExtensionServer,
        FormServiceExtensionSpec.demand(
            initial_form=FormRender(
                title="Welcome! Please tell us about yourself",
                columns=2,
                fields=[
                    TextField(id="first_name", label="First Name", col_span=1),
                    TextField(id="last_name", label="Last Name", col_span=1),
                ],
            )
        ),
    ],
):
    """Agent that collects user information through an initial form"""
    
    # Parse the form data using a Pydantic model
    user_info = form.parse_initial_form(model=UserInfo)
    
    if user_info is None:
        yield "No form data received."
    else:
        yield f"Hello {user_info.first_name} {user_info.last_name}! Nice to meet you."


if __name__ == "__main__":
    server.run()

Dynamic Form Requests

For dynamic form requests during conversation, you can request forms at any point when your agent needs structured input. This is useful when your agent needs to collect additional information based on the conversation flow.

Quickstart

1

Import the request form extension

Import FormRequestExtensionServer, FormRequestExtensionSpec, FormRender, and field types from the Agent Stack SDK.
2

Define your form data model

Create a Pydantic model with fields matching your form field IDs.
3

Add request form parameter to your agent

Inject the request form extension into your agent function using FormRequestExtensionSpec().
4

Request form dynamically

Call await form_request.request_form(form=FormRender(...), model=YourModel) when you need to collect structured input.
from typing import Annotated

from a2a.types import Message
from a2a.utils.message import get_message_text
from pydantic import BaseModel
from agentstack_sdk.server import Server
from agentstack_sdk.a2a.extensions.common.form import FormRender, TextField
from agentstack_sdk.a2a.extensions.ui.form_request import (
    FormRequestExtensionServer,
    FormRequestExtensionSpec,
)

server = Server()


class ContactInfo(BaseModel):
    email: str | None
    phone: str | None
    company: str | None


@server.agent()
async def dynamic_form_agent(
    message: Message,
    form_request: Annotated[
        FormRequestExtensionServer,
        FormRequestExtensionSpec(),
    ],
):
    """Agent that requests forms dynamically during conversation"""
    
    user_input = get_message_text(message)
    
    # Check if user wants to provide contact information
    if "contact" in user_input.lower() or "reach" in user_input.lower():
        # Request contact form dynamically
        contact_info = await form_request.request_form(
            form=FormRender(
                title="Please provide your contact information",
                columns=2,
                fields=[
                    TextField(id="email", label="Email Address", col_span=2),
                    TextField(id="phone", label="Phone Number", col_span=1),
                    TextField(id="company", label="Company", col_span=1),
                ],
            ),
            model=ContactInfo,
        )
        
        if contact_info is None:
            yield "No contact information received."
        else:
            yield f"Thank you! I'll contact you at {contact_info.email} or {contact_info.phone} regarding {contact_info.company}."
    else:
        yield "Hello! If you'd like me to contact you, just let me know and I'll ask for your details."


if __name__ == "__main__":
    server.run()

How to work with forms

Here’s what you need to know to add form capabilities to your agent: Import the form components:
  • For form fields and FormRender, import from agentstack_sdk.a2a.extensions.common.form
  • For initial forms, import FormServiceExtensionServer and FormServiceExtensionSpec from agentstack_sdk.a2a.extensions.services.form
  • For dynamic forms, import FormRequestExtensionServer and FormRequestExtensionSpec from agentstack_sdk.a2a.extensions.ui.form_request
Inject the extension: Add a form parameter to your agent function using the Annotated type hint. For initial forms: Use FormServiceExtensionSpec.demand(initial_form=FormRender(...)) to specify the form structure and call form.parse_initial_form(model=YourModel) to extract data. For dynamic forms: Use FormRequestExtensionSpec() and call await form_request.request_form(form=FormRender(...), model=YourModel) when needed. Access form data: The recommended approach is to use a Pydantic model (or TypedDict, dataclass, or any class supported by pydantic.TypeAdapter) to load form data. Define a model with fields matching your form field IDs:
from pydantic import BaseModel

class ContactInfo(BaseModel):
    email: str | None
    phone: str | None
    company: str | None
Then, pass model=ContactInfo to parse_initial_form(...) or request_form(...) to get the form data directly as an instance of ContactInfo:
# For initial forms
contact_info: ContactInfo | None = form.parse_initial_form(model=ContactInfo)

# For dynamic forms
contact_info: ContactInfo | None = await form_request.request_form(
    form=FormRender(...),
    model=ContactInfo
)
If you don’t use a model, the methods return FormResponse which has a values dictionary. You can access values using form_data.values['field_id'].value. Different field types return different value types:
  • TextField/DateField: Returns str | None
  • FileField: Returns list[FileInfo] | None where each FileInfo has uri, name, and mime_type
  • SingleSelectField: Returns str | None (selected option ID)
  • MultiSelectField: Returns list[str] | None (list of selected option IDs)
  • CheckboxField: Returns bool | None

Form Field Types

The Agent Stack supports various field types for collecting different kinds of structured data:

TextField

Basic text input fields for collecting strings, names, descriptions, etc.
from agentstack_sdk.a2a.extensions.common.form import TextField

TextField(
    id="username", 
    label="Username", 
    col_span=1,
    required=True,
    placeholder="Enter your username",
    default_value="",
    type="text"  # Optional, defaults to "text"
)

DateField

Date input fields for collecting dates and timestamps.
from agentstack_sdk.a2a.extensions.common.form import DateField

DateField(
    id="birth_date", 
    label="Birth Date",
    col_span=1,
    required=True,
    placeholder="YYYY-MM-DD",
    default_value="1990-01-01"
)

FileField

File upload fields for collecting files from users.
from agentstack_sdk.a2a.extensions.common.form import FileField

FileField(
    id="document", 
    label="Upload Document",
    col_span=2,
    required=True,
    accept=["application/pdf", "image/jpeg", "image/png"]
)

SingleSelectField

Single-select dropdown fields for choosing single option from a list.
from agentstack_sdk.a2a.extensions.common.form import OptionItem, SingleSelectField

SingleSelectField(
    id="contact_method", 
    label="Preferred Contact Method",
    col_span=2,
    required=False,
    options=[
        OptionItem(id="email", label="Email"),
        OptionItem(id="phone", label="Phone"),
        OptionItem(id="sms", label="SMS"),
        OptionItem(id="none", label="Do not contact")
    ],
    default_value="email"
)

MultiSelectField

Multi-select dropdown fields for choosing multiple options from a list.
from agentstack_sdk.a2a.extensions.common.form import OptionItem, MultiSelectField

MultiSelectField(
    id="interests", 
    label="Your Interests",
    col_span=2,
    required=False,
    options=[
        OptionItem(id="tech", label="Technology"),
        OptionItem(id="sports", label="Sports"),
        OptionItem(id="music", label="Music"),
        OptionItem(id="travel", label="Travel")
    ],
    default_value=["tech", "music"]
)

CheckboxField

Single checkbox fields for boolean values.
from agentstack_sdk.a2a.extensions.common.form import CheckboxField

CheckboxField(
    id="newsletter", 
    label="Subscribe to Newsletter", 
    col_span=1,
    required=False,
    content="I agree to receive marketing emails",
    default_value=False
)

Form Layout Configuration

Control how your form appears using the FormRender configuration:
from agentstack_sdk.a2a.extensions.common.form import FormRender

FormRender(
    title="Form Title",
    description="Optional description text below the title",
    columns=2,  # Number of columns in the form grid
    submit_label="Custom Submit Button Text",
    fields=[
        # Your field definitions here
    ]
)
FormRender properties:
  • title: Main heading displayed above the form (optional)
  • description: Optional description text displayed below the title
  • columns: Number of columns in the form grid (1-4, optional)
  • submit_label: Custom text for the submit button (optional, default: “Submit”)
  • fields: List of form field definitions (required)
Use col_span on individual fields to control how they span across the grid. For example, with columns=2, a field with col_span=2 will take the full width, while col_span=1 will take half the width.