If your agent renders UI widgets — buttons, dropdowns, multi-select pickers, text inputs — Snowglobe can simulate users interacting with them. You return widget definitions from yourDocumentation Index
Fetch the complete documentation index at: https://snowglobe.so/docs/llms.txt
Use this file to discover all available pages before exploring further.
completion() function, and Snowglobe’s simulated user picks options, fills inputs, and clicks buttons just like a real user would. On the next turn your agent receives the user’s actions as structured data.
Use this when:
- Your agent emits forms or quick actions, and you want to test them under realistic user flows
- A button click or option selection is part of the conversation (escalation buttons, “yes/no” confirmations, order-pickers, etc.)
- You want simulations that exercise both your free-text and widget paths
Widgets are part of agent simulation. If you don’t have access yet, see the agent simulation overview for how to enable it.
How it works at a high level
Each turn is a round trip:- Your
completion()returns a text response and (optionally) a list of widgets to show - Snowglobe persists the widgets and shows them to the simulated user along with the text
- The simulated user picks an option, fills an input, or clicks a button
- On the next turn, your
completion()receivesrequest.widget_actionsdescribing what the user did - Your agent reads the actions and replies — possibly with more widgets, possibly with plain text
Prerequisites
- Agent simulation set up end-to-end. If you haven’t completed the agent simulation tutorial, do that first
- Snowglobe Connect SDK with widget support (the types live under
snowglobe.client) - Your agent already returns
CompletionFunctionOutputsfrom itscompletion()function
Step 1. Import the widget types
The widget types are re-exported fromsnowglobe.client:
| Type | What it is |
|---|---|
Interaction | A group of widgets emitted on a single turn. Has an id, a blocking flag, and a list of widgets. |
Button | A clickable button. The user “clicks” it. |
Select | A single-choice dropdown. The user picks one option. |
MultiSelect | A multi-choice picker. The user picks zero or more options. |
Input | A text input. The user types into it. |
Step 2. Emit widgets from your agent
To show widgets, setwidgets on CompletionFunctionOutputs. Each entry is an Interaction containing one or more widget elements.
The simplest case: a single button.
snowglobe_agent.py
blocking=False means the simulated user can ignore the button and keep the conversation going with free text. blocking=True (form-style) is covered below.
Step 3. Read what the simulated user did
When the simulated user acts on a widget, the next call tocompletion() carries request.widget_actions — a list of WidgetAction objects describing what happened.
snowglobe_agent.py
request.widget_actions is always a list — empty when the user’s input was free text, non-empty when the user interacted with a widget. Existing code that only reads request.messages keeps working without changes.
WidgetAction shape
Each action carries:
| Field | Type | Set when |
|---|---|---|
interaction_id | str | Always. Matches the id on the Interaction you emitted. |
widget_id | str | Always. Matches the id on the widget the user touched. |
action | "click" | "select_one" | "select_many" | "set_text" | Always. Determined by the widget kind. |
option_id | str | select_one only. The id of the chosen WidgetChoice. |
option_ids | list[str] | select_many only. Ids of the chosen choices. May be empty. |
text_value | str | set_text only. The text the user typed. May be "" to clear. |
Step 4. Pick the right widget kind
Button — click
A simple action with no payload.
Select — select_one
A single-choice picker. Provide a list of WidgetChoice objects with stable ids.
action="select_one" with option_id set to one of the choice ids.
MultiSelect — select_many
A multi-choice picker. Same shape as Select but the user can choose multiple options (or none).
action="select_many" with option_ids set to the chosen ids (possibly an empty list).
Input — set_text
A free-text field.
action="set_text" with text_value set to whatever the user typed (possibly "").
Step 5. Use the blocking flag for form-style flows
Set Interaction.blocking=True when the widgets together form a single submittable unit — the persona shouldn’t be able to send a free-text message until they either submit the form or explicitly cancel out of it. Snowglobe enforces this: while a blocking interaction is visible, the simulated user is restricted to widget actions or ending the conversation.
This is how you get realistic form-fill behavior. The simulated user can pick options and fill inputs in any order. Snowglobe batches those staged actions and delivers them to your completion() only when a button click commits the form.
snowglobe_agent.py
completion() receives a single call with multiple widget_actions — for example a select_one on card_type, a set_text on travel_note, and a click on submit. Read them as a batch.
Use
blocking=True for forms. Use blocking=False for one-off buttons that don’t gate the conversation (suggestions, “Get help”, quick replies). The simulated user may ignore a non-blocking widget and just keep talking.Step 6 (optional). Rewrite the user-side text with user_content
By default, when the simulated user submits a widget, the conversation transcript shows the user’s input as the widget action chips (e.g. “selected: virtual; clicked: submit”). Sometimes a click is semantically equivalent to typing a phrase — an “Escalate to human” button is really the user saying “I want to talk to a human.”
Set CompletionFunctionOutputs.user_content on the response that handles the widget action to rewrite the user-side text on that turn. Downstream evaluators, reports, and the conversation viewer all see the canonical phrase instead of the raw widget action.
user_content unset (the default) when the widget action doesn’t map cleanly to a single phrase — that’s almost always the right default for forms.
Full round-trip example
A small agent that offers a renewal form, processes the submission, and then offers a non-blocking “Anything else?” follow-up:snowglobe_agent.py
- Send a free-text opener; your agent returns the form
- Pick a card type, optionally fill the input, and click Submit; your agent processes the submission and offers the follow-up button
- Either ignore the follow-up and ask another question, or click “No, that’s all” to wrap up
What gets stored
After each turn:- The widgets your agent emitted are persisted on the assistant turn and rendered in the Snowglobe conversation viewer
- The simulated user’s actions are persisted on the user side of the next turn
- If you set
user_content, the user-side text shows your canonical phrase; otherwise it shows action chips
Tips and gotchas
- Widget ids are yours.
Interaction.id, widget ids, andWidgetChoice.idare all opaque strings owned by your agent. Keep them stable across turns so the simulated user’s actions reference the right targets. - Don’t reuse ids across siblings. Two widgets in the same
Interactionmust have distinct ids, and twoWidgetChoiceentries in the sameSelect/MultiSelectmust have distinct ids. - Empty values are valid. A
set_textaction withtext_value=""and aselect_manywithoption_ids=[]are both legal — they mean “the user cleared the field.” - Disabled choices. Set
WidgetChoice(disabled=True)to render an option that’s visible but can’t be selected. The simulated user respects this. - Pre-selection.
Select.selected_option_idandMultiSelect.selected_option_idsshow the widget pre-filled. The user can still change the selection. - Re-rendering. Each turn replaces the visible widgets. Re-emit the same
Interactionif you want it to stay on screen across multiple turns.
What’s next
Tools tutorial
The end-to-end agent simulation setup that widgets build on top of.
Concepts
Simulation modes and how Snowglobe’s mocking layer works.