close
Skip to content
Get started

Automation Events

Orientation for consuming the Server-Sent Event stream from /v1/automate -- ordering, conditional events, filtering, and consumption patterns.

The /v1/automate endpoint streams Server-Sent Events (SSE). Each event has a string event name and a typed data payload; the SDKs expose the stream as a discriminated union (AutomateEvent in TypeScript, matched via event.event in Python), so switch/match on the event name narrows the payload for you.

This page covers what the API reference can’t: the order events arrive in, which ones only fire under specific conditions, what you probably want to filter out in production, and a starter consumption pattern. For the complete list of events and their payload schemas, see the API reference.


A simple task (extract the title of a single page, no form interactivity, no extraction-heavy work) produces roughly this sequence:

  1. cdp:endpoint_connected — browser session attached.
  2. agent:processing / agent:status — the agent builds a task plan.
  3. browser:navigated — the initial page loads.
  4. task:started — execution begins; the payload echoes the plan and successCriteria.
  5. A per-iteration loop:
    • agent:step — iteration begins.
    • agent:processing — the agent is thinking.
    • agent:reasoned — reasoning output for this step.
    • agent:action — the action being taken (e.g. extract, click, done).
    • agent:processing — post-action processing.
  6. task:validated — the completion check passes. See the API reference for the payload fields.
  7. task:completed — the agent decides the task is done (fires inside the agent loop).
  8. complete — final result event: finalAnswer, stats (action count, iteration count, duration), success, and an optional structured error.
  9. done — stream terminator (empty payload today, reserved for future metadata).

Longer tasks repeat step 5 multiple times and may interleave browser:navigated, browser:action_started/browser:action_completed, browser:screenshot_captured, and agent:waiting. The first four events and the last four are stable landmarks; everything between them is per-iteration noise you can filter down to what you actually want to display.

complete, done, and the top-level error event are full members of the typed AutomateEvent union (added in SDK 2.6.0), so an exhaustive switch narrows their payloads the same way it does for any other event.


These only fire under specific conditions:

  • interactive:form_data:request / interactive:form_data:error — only when interactive: true is set. See the Interactive Mode guide for the request/response cycle.
  • agent:extracted — fires when the task performs structured extraction.
  • task:validation_error — emitted if the completion check fails; rare on well-formed tasks.
  • task:aborted — the task was terminated early.
  • browser:screenshot_captured_image — fires alongside browser:screenshot_captured when the screenshot payload is actually attached; large events, so the image variant is separate from the lightweight capture notice.
  • browser:reconnected — the underlying browser session was re-established (e.g. after a transient disconnect).
  • ai:generation / ai:generation:error — LLM-call instrumentation around agent reasoning steps.
  • cdp:endpoint_cycle — the CDP endpoint was rotated.
  • task:metrics / task:metrics_incremental — metric emissions for tasks when metrics are enabled.

Payload shapes for each are in the API reference.


Filtering is client-side — the server always emits these.

  • system:debug_compression, system:debug_message — internal diagnostics; nothing end-user-facing.
  • browser:screenshot_captured_image — the payload carries image bytes; drop it unless you actually render screenshots.
  • agent:processing — high frequency. If you only want state transitions (navigated, action, validated, completed), this is the first thing to filter.
  • ai:generation — chatty during LLM-heavy steps.

A typical progress UI only keeps task:started, browser:navigated, agent:action, task:validated, task:completed, and complete.


Switch on event.event; the SDK narrows event.data for each case. Show only the events you care about and ignore the rest:

import Tabstack from '@tabstack/sdk'
const client = new Tabstack()
const stream = await client.agent.automate({
task: 'Extract the page title',
url: 'https://example.com',
})
for await (const event of stream) {
switch (event.event) {
case 'task:started':
console.log('Task running:', event.data.task)
break
case 'agent:action':
console.log('Action:', event.data.action, '(value:', event.data.value, ')')
break
case 'browser:navigated':
console.log('Navigated:', event.data.url)
break
case 'complete':
if (event.data.success) {
console.log('Completed:', event.data.finalAnswer)
console.log(` ${event.data.stats.iterations} iterations, ${event.data.stats.durationMs}ms`)
} else {
console.log(`Failed (${event.data.error?.code}):`, event.data.error?.message)
}
break
case 'error':
console.log(`Runner error (${event.data.error.code}):`, event.data.error.message)
break
}
}

For a deeper walkthrough of consuming the stream end-to-end, see the Automate event flow guide.


Failures surface in three places:

  1. HTTP-level exceptions raised by the SDK before or during the request (e.g. a malformed body, missing task, auth failure, rate limit). These are the SDK’s typed error classes — BadRequestError, AuthenticationError, RateLimitError, etc. Wrap the initial automate(...) call and the for await loop with try/except (or try/catch) to handle them.
  2. Agent-level aborts surface inside the complete event with success: false and a structured error payload: { code, message }. The code is a typed enum (TASK_ABORTED, MAX_ITERATIONS, MAX_ERRORS, TASK_FAILED) you can branch on. A task:aborted event also fires earlier in the stream, but complete carries the canonical final state.
  3. Top-level error events fire only if the task runner itself crashes — distinct from the agent-level aborts above. Payload: { success: false, error: { code, message, timestamp } } where timestamp is an ISO-8601 string. This event is in the typed AutomateEvent union, so an exhaustive switch narrows it.

When in doubt, log unknown event names during development — they are either conditional events you do not handle yet, or new variants added in a later SDK version.