How UI-TARS Handles User‑Initiated Stops During Agent Execution

The UI‑TARS agent handles user‑initiated stops by setting a stopped flag in the shared AgentContext, which the Executor checks at the start of every iteration to break the main loop and emit a TASK_CANCEL event.

When building autonomous browser agents in the bytedance/UI-TARS-desktop repository, allowing users to interrupt a running task is critical for safety and control. The system implements a cooperative cancellation pattern where a shared context stores the stop state, and the execution pipeline polls this flag to halt gracefully without orphaning browser sessions.

Where the Stop Flag Is Stored

The AgentContext Class

The source of truth for a user‑initiated stop lives in the AgentContext class defined in packages/agent-infra/browser-use/src/agent/types.ts. This shared context object carries a stopped boolean property that defaults to false and is set to true when the user requests cancellation.

// packages/agent-infra/browser-use/src/agent/types.ts
export class AgentContext {
  // ...
  stopped: boolean;
  // ...

  async stop() {
    this.stopped = true;                 // ← set by the stop request
  }
}

Any component with access to this context can invoke stop() to signal that the agent should cease operations. This design centralizes state management, ensuring that all executor and agent subcomponents read from the same flag.

How the Executor Checks for User‑Initiated Stops

The shouldStop() Guard

Before every step, the Executor calls shouldStop() to determine whether the run should continue. This private method returns true immediately when context.stopped is detected, preventing any further logic from executing.

// packages/agent-infra/browser-use/src/agent/executor.ts
private async shouldStop(): Promise<boolean> {
  if (this.context.stopped) {
    logger.info('Agent stopped');
    return true;                       // ← halts the loop
  }
  // ...
}

Breaking the Main Execution Loop

The main entry point Executor.execute() runs a for loop that iterates up to allowedMaxSteps. At the top of each iteration, it awaits shouldStop(), breaking out of the loop instantly when the flag is set.

// packages/agent-infra/browser-use/src/agent/executor.ts
for (let step = 0; step < allowedMaxSteps; step++) {
  // ...
  if (await this.shouldStop()) {
    break;                             // ← stop the task
  }
  // ...
}

This polling strategy guarantees that the agent responds to a user‑initiated stop within a single iteration, typically after the current browser action completes but before the next LLM call is issued.

Agent‑Level Stop Guards

Individual agents also respect the stop flag to avoid performing redundant work. The system embeds checks at multiple layers of the call stack.

Before attempting to navigate to a new URL, the executor verifies that the task has not been paused or stopped:

// packages/agent-infra/browser-use/src/agent/executor.ts
if (context.paused || context.stopped) {
  return false;                        // ← abort navigation
}

The Navigator agent—responsible for determining the next browser action—performs an early exit when it detects a stop request:

// packages/agent-infra/browser-use/src/agent/agents/navigator.ts
if (this.context.paused || this.context.stopped) {
  return { result: null, error: null }; // ← early exit
}

Other specialized agents, such as the Planner and Validator, implement identical guards. This ensures that once stop() is invoked, no further LLM calls or browser interactions are scheduled.

API Surface for Canceling Execution

Three primary entry points allow users or internal components to trigger a stop:

  • Executor.cancel() – Located in packages/agent-infra/browser-use/src/agent/executor.ts, this method simply forwards the call to context.stop(), setting the boolean flag that the execution loop will pick up on the next iteration.
  • GUIAgent.stop() – Exposed in packages/ui-tars/sdk/src/GUIAgent.ts, this public SDK method delegates to the underlying executor’s cancel(). It is the standard mechanism for UI buttons or keyboard shortcuts that let users abort a task.
  • Direct context.stop() – Any internal component holding a reference to the shared AgentContext can call this method directly, making the cancellation system extensible for programmatic timeouts or error‑handling scenarios.

What Happens When a Stop Is Requested

Once the stopped flag is raised, the following sequence occurs:

  1. Immediate Effect – The current iteration finishes if it is already mid‑step, but the loop exits before the next step begins.
  2. Event Emission – The executor emits a TASK_CANCEL event via the context’s event emitter to notify listeners (e.g., the UI layer) that the agent has halted:
this.context.emitEvent(
  Actors.SYSTEM,
  ExecutionState.TASK_CANCEL,
  'Task cancelled',
  browserState,
);
  1. Cleanup – After exiting the loop, Executor may still run cleanup() to close browser pages and release resources, but no further actions are scheduled or executed.

Summary

  • Centralized State – The AgentContext.stopped boolean in packages/agent-infra/browser-use/src/agent/types.ts acts as the single source of truth for cancellation.
  • Polling PatternExecutor.shouldStop() checks the flag at the start of every loop iteration in packages/agent-infra/browser-use/src/agent/executor.ts.
  • Layered Guards – Individual agents like Navigator verify context.stopped before performing actions, preventing stray LLM or browser calls.
  • Public APIGUIAgent.stop() provides the user‑facing hook, routing to Executor.cancel() to set the stop flag.
  • Graceful Exit – Stopping emits a TASK_CANCEL event and allows cleanup code to run, ensuring browser resources are released properly.

Frequently Asked Questions

How quickly does the UI-TARS agent respond to a user-initiated stop?

The agent responds at the boundary of the current step. The Executor.shouldStop() method is checked at the top of each iteration in packages/agent-infra/browser-use/src/agent/executor.ts, so the loop breaks immediately after the current browser action and LLM call complete, but before the next step begins.

What is the difference between Executor.cancel() and AgentContext.stop()?

AgentContext.stop() is the low‑level method that sets the stopped boolean flag in the shared state. Executor.cancel() is a convenience wrapper that calls this.context.stop() and is exposed on the executor instance. GUIAgent.stop() in turn calls Executor.cancel(), creating a clean chain from the UI to the core state.

Does stopping the agent close the browser immediately?

Stopping does not forcibly terminate the browser mid‑action. Instead, the executor exits its main loop and then runs optional cleanup() logic, which gracefully closes pages and releases resources. This prevents orphaning browser processes while ensuring no new actions are issued after the stop request.

Can the agent stop itself, or can only the user initiate a stop?

While designed for user‑initiated cancellation, any component with access to the AgentContext can call context.stop(). This allows the system to support programmatic stops, such as timeouts or error‑recovery routines, without requiring user interaction.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →