How to Use Interactive Mode
Interactive Mode allows the automation agent to pause and request information from the caller when it encounters forms requiring user data like email addresses, credentials, or preferences. Instead of failing or hallucinating, the agent asks for what it needs.
The Problem
Section titled “The Problem”When an automation agent encounters a form that requires personal data, it has a gap: it doesn’t know your email address, your password, or your preferences. Without that context, the agent either fails the task or fills in garbage data.
Consider this task: “Sign up for the Mozilla newsletter.”
The agent can navigate to the signup page, locate the form, and identify the required fields. But it can’t submit the form without your email address. Interactive Mode solves this by letting the agent pause and ask.
How Interactive Mode Works
Section titled “How Interactive Mode Works”When you enable interactive mode, the automation agent gains the ability to request information mid-task. Here’s the flow:
- You send an automate request with
interactive: true - The agent executes the task normally, navigating pages and interacting with elements
- When the agent encounters a form requiring user data, it pauses and emits an
interactive:form_data:requestevent containing the form fields it needs filled - Your application receives this event, collects the data (from a user prompt, a database, another agent, etc.), and submits it back via the input endpoint
- The agent fills in the form fields and resumes the task
- If form validation fails, the agent emits an
interactive:form_data:errorevent with the specific errors, and you can re-submit corrected values
The agent fills form fields directly without exposing user-provided values to the LLM, keeping sensitive data like passwords and personal information private.
Prerequisites
Section titled “Prerequisites”Before using interactive mode, you’ll need:
- A valid Tabstack API key: Sign up at https://tabstack.ai to get your key.
- An SSE-capable client: Your HTTP client must handle
text/event-streamresponses. - A way to handle input requests: Your application needs logic to respond when the agent requests form data.
export TABSTACK_API_KEY="your-api-key-here"Enabling Interactive Mode
Section titled “Enabling Interactive Mode”Add interactive: true to your automate request:
curl -X POST https://api.tabstack.ai/v1/automate \ -H "Authorization: Bearer $TABSTACK_API_KEY" \ -H "Content-Type: application/json" \ -N \ -d '{ "task": "Sign up for the Mozilla newsletter", "url": "https://www.mozilla.org", "interactive": true }'from tabstack import Tabstack
client = Tabstack()
stream = client.agent.automate( task="Sign up for the Mozilla newsletter", url="https://www.mozilla.org", interactive=True,)import Tabstack from "@tabstack/sdk";
const client = new Tabstack();
const stream = await client.agent.automate({ task: "Sign up for the Mozilla newsletter", url: "https://www.mozilla.org", interactive: true,});When interactive is not set or set to false, the agent will automatically decline any form data requests and attempt to continue without user input.
Handling Form Data Requests
Section titled “Handling Form Data Requests”When the agent needs user input, it emits an interactive:form_data:request event in the SSE stream. The event data contains:
| Field | Type | Description |
|---|---|---|
requestId | string | Unique identifier for this request. Use this when submitting your response. |
pageUrl | string | URL of the page containing the form. |
pageTitle | string | Title of the page. |
formDescription | string | Human-readable description of the form’s purpose. |
fields | array | The form fields the agent needs filled. |
Field Structure
Section titled “Field Structure”Each field in the fields array contains:
| Field | Type | Description |
|---|---|---|
ref | string | Element reference ID (e.g., "E42"). Include this in your response to map values to fields. |
label | string | The field’s visible label (e.g., "Email Address"). |
fieldType | string | One of: text, email, phone, date, number, select, checkbox, radio, textarea, password, other. |
required | boolean | Whether the field must be filled. |
options | string[] | Available options for select and radio fields. |
currentValue | string | Current value if the field is already partially filled. |
description | string | Additional context or hints about the field. |
Example Event
Section titled “Example Event”event: interactive:form_data:requestdata: { "requestId": "a1b2c3d4", "pageUrl": "https://www.mozilla.org/en-US/newsletter/", "pageTitle": "Mozilla Newsletter", "formDescription": "Newsletter signup form requiring email and format preference", "fields": [ { "ref": "E12", "label": "Your email address", "fieldType": "email", "required": true }, { "ref": "E15", "label": "Format", "fieldType": "select", "required": false, "options": ["HTML", "Text"] } ]}Submitting User Input
Section titled “Submitting User Input”Once you have the values for the requested fields, submit them to the input endpoint:
POST /v1/automate/{requestId}/inputcurl -X POST "https://api.tabstack.ai/v1/automate/a1b2c3d4/input" \ -H "Authorization: Bearer $TABSTACK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "fields": [ { "ref": "E12", "value": "[email protected]" }, { "ref": "E15", "value": "HTML" } ] }'response = client.agent.automate_input( "a1b2c3d4", fields=[ {"ref": "E15", "value": "HTML"}, ],)const response = await client.agent.automateInput("a1b2c3d4", { fields: [ { ref: "E15", value: "HTML" }, ],});The endpoint returns 202 Accepted on success. If the request has expired or already been answered, it returns 410 Gone.
Handling Validation Errors
Section titled “Handling Validation Errors”If the form submission fails validation (e.g., an invalid email format), the agent emits an interactive:form_data:error event. This event includes the same field structure as the original request, plus a fieldErrors map showing which fields failed and why.
event: interactive:form_data:errordata: { "requestId": "e5f6g7h8", "pageUrl": "https://www.mozilla.org/en-US/newsletter/", "pageTitle": "Mozilla Newsletter", "formDescription": "Newsletter signup form requiring email and format preference", "fields": [ { "ref": "E12", "label": "Your email address", "fieldType": "email", "required": true, "description": "Invalid email address" } ], "fieldErrors": { "E12": "Invalid email address" }}Handle this the same way as the initial request: collect corrected values from the user and submit them to the input endpoint using the new requestId.
Cancelling a Request
Section titled “Cancelling a Request”If you want to skip the form or abort the interactive request, submit with cancelled: true:
curl -X POST "https://api.tabstack.ai/v1/automate/a1b2c3d4/input" \ -H "Authorization: Bearer $TABSTACK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "cancelled": true }'response = client.agent.automate_input( "a1b2c3d4", cancelled=True,)const response = await client.agent.automateInput("a1b2c3d4", { cancelled: true,});When cancelled, the agent will attempt to continue the task without the form data, which may result in task failure depending on the form’s requirements.
Complete Example
Section titled “Complete Example”This example shows the full flow: starting an interactive automation, listening for form data requests, prompting the user, and submitting the response.
from tabstack import Tabstack
client = Tabstack()
# Start a task with interactive mode enabledstream = client.agent.automate( task="Sign up for the Mozilla newsletter", interactive=True,)
# Listen for events in the streamfor event in stream: print(f"[{event.event}]")
# When the agent requests form data if event.event == "interactive:form_data:request": request_id = event.data.get("requestId") fields = event.data.get("fields", [])
print(f"\nThe agent needs the following information:")
field_values = [] for field in fields: ref = field.get("ref") label = field.get("label", "Unknown field") required = field.get("required", False) field_type = field.get("fieldType", "text") options = field.get("options")
# Show available options for select/radio fields if options: print(f" {label} (options: {', '.join(options)})")
prompt = f" {label}{'*' if required else ''}: " value = input(prompt) field_values.append({"ref": ref, "value": value})
# Submit the values back to the agent client.agent.automate_input(request_id, fields=field_values) print("Input submitted, agent resuming...\n")
# Handle validation errors elif event.event == "interactive:form_data:error": request_id = event.data.get("requestId") field_errors = event.data.get("fieldErrors", {}) fields = event.data.get("fields", [])
print(f"\nForm validation failed:") for ref, error in field_errors.items(): print(f" {ref}: {error}")
field_values = [] for field in fields: ref = field.get("ref") label = field.get("label", "Unknown field") error = field_errors.get(ref, "")
prompt = f" {label} ({error}): " if error else f" {label}: " value = input(prompt) field_values.append({"ref": ref, "value": value})
client.agent.automate_input(request_id, fields=field_values) print("Corrected input submitted...\n")
# Print the final result elif event.event == "complete": result = event.data print(f"\nTask complete: {result}")import Tabstack from "@tabstack/sdk";import * as readline from "readline";
const client = new Tabstack();
// Helper to prompt user for inputfunction prompt(question: string): Promise<string> { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); return new Promise((resolve) => { rl.question(question, (answer) => { rl.close(); resolve(answer); }); });}
async function run() { // Start a task with interactive mode enabled const stream = await client.agent.automate({ task: "Sign up for the Mozilla newsletter", interactive: true, });
for await (const event of stream) { console.log(`[${event.event}]`);
// When the agent requests form data if (event.event === "interactive:form_data:request") { const { requestId, fields } = event.data;
console.log("\nThe agent needs the following information:");
const fieldValues = []; for (const field of fields) { const label = field.label || "Unknown field"; const required = field.required ? "*" : ""; const options = field.options;
if (options) { console.log(` ${label} (options: ${options.join(", ")})`); }
const value = await prompt(` ${label}${required}: `); fieldValues.push({ ref: field.ref, value }); }
// Submit the values back to the agent await client.agent.automateInput(requestId, { fields: fieldValues }); console.log("Input submitted, agent resuming...\n"); }
// Handle validation errors else if (event.event === "interactive:form_data:error") { const { requestId, fields, fieldErrors } = event.data;
console.log("\nForm validation failed:"); for (const [ref, error] of Object.entries(fieldErrors)) { console.log(` ${ref}: ${error}`); }
const fieldValues = []; for (const field of fields) { const error = fieldErrors[field.ref]; const label = field.label || "Unknown field";
const suffix = error ? ` (${error})` : ""; const value = await prompt(` ${label}${suffix}: `); fieldValues.push({ ref: field.ref, value }); }
await client.agent.automateInput(requestId, { fields: fieldValues }); console.log("Corrected input submitted...\n"); }
// Print the final result else if (event.event === "complete") { console.log("\nTask complete:", event.data); } }}
run();Use Cases
Section titled “Use Cases”Interactive mode is useful whenever the agent needs context that only the caller can provide:
- Account signups: The agent navigates the signup flow while you provide email, username, and password.
- Form submissions: Contact forms, registration forms, surveys where personal data is required.
- Checkout flows: Providing shipping addresses, payment preferences, or coupon codes.
- Multi-agent pipelines: A parent orchestrator agent automatically supplies data from its own context when the automation agent requests it, with no human involvement needed.
Limitations
Section titled “Limitations”- Form-filling only: This beta release is optimized for form completion. The agent requests data specifically when it encounters form fields requiring user input.
- 2-minute timeout: Input requests expire after 2 minutes. A
410 Goneresponse means the task is unrecoverable — restart it. - Single request per form: The agent requests all fields for a form in a single event. It does not ask for fields one at a time.
- Not available via MCP: Interactive Mode requires a persistent SSE stream to exchange events. It is not supported through the Tabstack MCP server. Use the SDK directly.