How ego-lite Handles Isolation Between Users and Agents in the Browser

ego-lite enforces isolation between users and agents through task spaces with explicit ownership flags, blocking agent commands whenever a user owns control while allowing seamless hand-offs back and forth.

The citrolabs/ego-lite project implements a robust browser isolation model that prevents AI agents from interfering with human users during shared browser sessions. At the core of this system are task spaces—discrete execution contexts that carry an ownership property determining who can execute browser commands at any moment.

How Task Space Ownership Works

Task spaces in ego-lite support three ownership states:

  • "agent" — The agent has full control and may execute any helper (click, type, navigate, etc.)
  • "agentDelegatedToUser" — The agent created the space but temporarily yielded control; the agent can select the space but real actions are blocked until control returns
  • "user" — The human user owns the space; the agent cannot issue commands requiring agent-owned control

This ownership flag is stored on the task-space object returned from the native ego runtime and is validated by every public helper before any browser-affecting operation executes.

Core Isolation Mechanisms in helpers.ts

The isolation logic lives primarily in package/ego-browser/src/helpers.ts. Each helper enforces specific ownership rules:

switchTaskSpace

Throws an error if the target space is not agent-owned, preventing the agent from accidentally switching into a user-controlled context.

// source: package/ego-browser/src/helpers.ts#L52-L63
// throws if target space ownership !== "agent"
await taskSpaces.switch(targetSpaceId);

claimTaskSpace

Converts a user-owned space into an agent-owned one before selection, enabling the agent to resume work on previously user-controlled sessions.

// source: package/ego-browser/src/helpers.ts#L24-L27
// claims ownership from "user" → "agent"
await taskSpaces.claim('user-space-id');

handOffTaskSpace

Yields control to the user and resolves with { done: false, skipped: "user-owned" } when the space is already user-owned, making hand-offs idempotent.

// source: package/ego-browser/src/helpers.ts#L26-L34
const result = await taskSpaces.handOff();
// result: { done: true } or { done: false, skipped: "user-owned" }

completeTaskSpace

Honors the keep flag: with keep: true on a user-owned space it does nothing; with keep: false it first claims the space then closes it.

// source: package/ego-browser/src/helpers.ts#L66-L77
await taskSpaces.complete({ keep: false }); // claims then closes

waitForAgentControl

Polls via harmless ego.snapshot calls until succeeding, which only happens when agent control is restored—useful for async re-acquisition flows.

// source: package/ego-browser/src/helpers.ts#L84-L95
await taskSpaces.waitForAgentControl();

Native Bridge Enforcement

When helpers execute real browser commands (click, fill, navigate), the underlying driver calls the native ego bridge, which performs a second ownership check. If the user still has control, the bridge throws EGO_TASK_SPACE_USER_IN_CONTROL. The JavaScript layer catches this in package/ego-browser/src/ego-errors.ts and surfaces it via isEgoUserControlError.

import { isEgoUserControlError } from 'ego-browser';

try {
  await page.click('#submit');
} catch (err) {
  if (isEgoUserControlError(err)) {
    // User currently owns this space—request takeover first
  }
}

Complete User-Agent Hand-Off Flow

The isolation model follows a predictable lifecycle:

  1. Create task space (newTaskSpace) → ownership = "agent"
  2. Agent executes → all helpers run normally
  3. Hand off to user (handOffTaskSpace) → UI overlay removed, ownership becomes "user" or "agentDelegatedToUser"
  4. User interacts freely → any agent helper calls are blocked at the native bridge
  5. Agent resumes (takeOverTaskSpace or claimTaskSpace) → overlay restored, ownership returns to "agent"

This guarantees no agent command executes during active user interaction, preserving both privacy and operational safety.

Practical Implementation Example

import { taskSpaces, page } from 'ego-browser';

// 1. Create isolated agent workspace
const ts = await taskSpaces.new('research-session');

// 2. Agent performs automated work
await page.goto('https://example.com');
await page.locator('button#search').fill('query');
await page.locator('button#go').click();

// 3. Yield to user for verification/input
await taskSpaces.handOff(); // { done: true }

// ... user interacts with page freely ...

// 4. Agent must reclaim before resuming
await taskSpaces.takeOver(); // selects space, restores overlay
await page.locator('button#next').click();

// 5. Or claim a user-created space
await taskSpaces.claim('user-started-space');
await page.locator('button#admin').click();

Key Source Files Supporting Isolation

File Role in Isolation
package/ego-browser/src/helpers.ts Implements switchTaskSpace, claimTaskSpace, handOffTaskSpace, takeOverTaskSpace, waitForAgentControl with ownership-policy enforcement
package/ego-browser/src/state.ts Global state singleton tracking agent workspace and default timeouts for task-space operations
package/ego-browser/src/ego-errors.ts isEgoUserControlError helper interpreting native "user-in-control" exceptions
package/ego-browser/src/driver/* (e.g., nav.ts, pointer.ts) Low-level driver functions calling the native ego bridge, relying on upstream ownership checks
skills/ego-browser/SKILL.md End-user documentation maintaining ownership policy table synchronized with implementation

Summary

  • Task spaces are the fundamental isolation boundary in ego-lite, carrying explicit ownership metadata
  • Three ownership states ("agent", "agentDelegatedToUser", "user") define who may execute browser commands
  • Dual enforcement occurs in JavaScript helpers (helpers.ts) and the native ego bridge for defense in depth
  • Hand-off primitives (handOffTaskSpace, takeOverTaskSpace, claimTaskSpace) enable safe, reversible control transfers
  • Error handling via isEgoUserControlError allows graceful degradation when isolation boundaries are hit

Frequently Asked Questions

What happens if an agent tries to click while the user owns the task space?

The native ego bridge throws EGO_TASK_SPACE_USER_IN_CONTROL, which propagates to JavaScript and can be detected via isEgoUserControlError(). The click never executes—the browser state remains unchanged.

Can a user accidentally execute agent commands?

No. The ownership model is unidirectional: user-owned spaces block agent commands, but there is no corresponding restriction on user actions in agent-owned spaces. The handOffTaskSpace helper explicitly transfers control rather than sharing it.

How does claimTaskSpace differ from takeOverTaskSpace?

takeOverTaskSpace simply selects a space without ownership verification, useful when the agent already owns it. claimTaskSpace actively converts "user" ownership to "agent", required when resuming work on a user-controlled space. Both are implemented in package/ego-browser/src/helpers.ts.

Is the ownership check performed once or on every operation?

Every operation. Helpers validate ownership before executing, and the native bridge validates again when issuing the actual browser command. This double-check prevents race conditions where ownership changes between JavaScript validation and native execution.

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 →