How Ego-Lite Isolates Task Spaces and Implements Its Ownership Model

Ego-Lite isolates browser automation scripts inside short-lived task spaces—each with dedicated tabs, snapshots, and CDP sessions—while enforcing an ownership model that prevents agents from mutating user-owned contexts without explicit hand-off or claim.

This article explains how the ego-lite SDK from Citrolabs achieves task space isolation and implements its ownership policy. We'll examine the source code in src/helpers.ts and src/index.ts to understand how agents create, switch, and claim task spaces while respecting ownership boundaries.

What Is a Task Space in Ego-Lite?

A task space is an isolated browsing context that contains:

  • Its own set of browser tabs
  • Snapshot state for restoration
  • A dedicated Chrome DevTools Protocol (CDP) session

The native bridge (the closed-source ego-lite application) enforces this isolation at the system level. The JavaScript SDK exposes a task-space façade called taskSpaces that agents use to manage these spaces programmatically.

The Three Ownership States

Ego-Lite's ownership model defines who controls a task space and what operations are permitted. The SDK recognizes three ownership values:

Ownership value Description
"agent" Created by and fully owned by the automation agent; full mutating access permitted
"agentDelegatedToUser" Created by the agent but temporarily handed over to the user (e.g., after handOff)
"user" Created by and owned by the human user; agent cannot mutate without claiming

This ownership policy is documented in the source code comments at src/helpers.ts lines 18-30.

Core Helper Functions and Ownership Enforcement

The ownership system is implemented in package/ego-browser/src/helpers.ts. Here's how key functions handle ownership:

Checking Ownership: isAgentOwned

// src/helpers.ts lines 43-45
function isAgentOwned(space: TaskSpace): boolean {
  return space.owner === 'agent';
}

This simple predicate underlies all ownership decisions in the SDK.

Switching Spaces: switchTaskSpace

The switchTaskSpace helper enforces ownership before switching contexts:

// src/helpers.ts lines 57-62
async function switchTaskSpace(name: string): Promise<void> {
  const space = await getTaskSpace(name);
  if (!isAgentOwned(space)) {
    throw new UserControlError(`Task space "${name}" is owned by user`);
  }
  await ego.useTaskSpace(name);
}

If the target space has "user" ownership, the function throws UserControlError to prevent the agent from hijacking a user session.

Using or Creating Spaces: useOrCreateTaskSpace

When agents want to reuse an existing space or create one if missing, useOrCreateTaskSpace handles user-owned spaces gracefully at lines 94-110:

  • If the space exists and is agent-owned: switch to it
  • If the space exists but is user-owned: throw UserControlError (agent must explicitly claim it)
  • If the space doesn't exist: create it with "agent" ownership

Claiming User-Owned Spaces: claimTaskSpace

To take control of a user-created space, agents use claimTaskSpace:

// src/helpers.ts lines 24-27
async function claimTaskSpace(name: string): Promise<void> {
  await ego.transferTaskSpaceOwnership(name, 'agent');
  await ego.useTaskSpace(name);
}

This transfers ownership from "user" to "agent" before selection, making it safe to mutate.

Non-Mutating Operations Bypass Ownership

Functions that only read state—like waitForAgentControl and takeOverTaskSpace—ignore ownership checks because they don't modify the task space. This allows agents to observe user activity without interfering.

Exposing the Task Space Façade

The SDK registers all helper methods under the taskSpaces global in src/index.ts. The LEGACY_GLOBAL_HELPERS array (lines 119-133) includes:

// src/index.ts lines 119-133 (excerpt)
const LEGACY_GLOBAL_HELPERS = [
  // ... other helpers ...
  {
    name: 'taskSpaces',
    factory: createTaskSpaceHelpers,
    methods: ['new', 'useOrCreate', 'switch', 'claim', 'handOff', 'complete']
  }
];

This registration pattern makes task space operations available as taskSpaces.new(), taskSpaces.switch(), etc., directly from automation scripts.

Practical Usage Examples

Here are complete patterns for working with task spaces in ego-lite:

Create and Switch to a New Agent-Owned Space

// Creates space and automatically switches to it
await taskSpaces.new('myAgentSpace');
// Now operating in isolated context with "agent" ownership

Safely Use an Existing Space

// Use if agent-owned, create if missing
try {
  await taskSpaces.useOrCreate('sharedSpace');
} catch (e) {
  if (e instanceof UserControlError) {
    // Space exists but is user-owned; claim it first
    await taskSpaces.claim('sharedSpace');
  }
}

Hand Off to User Without Closing

// Delegates ownership to user, keeps space alive
await taskSpaces.handOff();  // Skips user-owned spaces automatically

Clean Up After Completion

// Close the task space when done
await taskSpaces.complete('myAgentSpace', { keep: false });

Isolation Guarantees

Together, these mechanisms provide two critical guarantees:

  1. Resource isolation—Each task space maintains independent tab state, snapshots, and CDP sessions. The ego.useTaskSpace call ensures the correct browser context is active.

  2. Security boundary—The ownership model prevents automation scripts from accidentally interfering with user-owned browsing sessions. Mutations require either original agent ownership or an explicit claim operation.

Summary

  • Task spaces in ego-lite are isolated browsing contexts with dedicated tabs and CDP sessions, enforced by the native bridge.

  • The ownership model uses three states ("agent", "agentDelegatedToUser", "user") to control who can mutate a space.

  • Core implementation lives in src/helpers.ts, with ownership checks in switchTaskSpace (lines 57-62) and ownership transfer in claimTaskSpace (lines 24-27).

  • The taskSpaces façade exposed via src/index.ts (lines 119-133) provides the agent-facing API.

  • Non-mutating operations bypass ownership checks, while mutating operations require agent ownership or explicit claiming.

Frequently Asked Questions

What happens if an agent tries to switch to a user-owned task space?

The switchTaskSpace function throws a UserControlError with a message like Task space "name" is owned by user. The agent must first call taskSpaces.claim() to transfer ownership.

Can multiple agents share the same task space?

Only one agent can own a task space at a time. If agent A has "agent" ownership, agent B cannot claim it until agent A completes or hands off the space. The native bridge enforces this serialization.

How does task space isolation differ from browser profiles?

Task spaces are lighter-weight than full browser profiles—they share the same browser instance but isolate CDP sessions, tabs, and execution context. This makes them faster to create and destroy for short-lived automation tasks.

What is the difference between handOff and claim?

handOff changes ownership from "agent" to "agentDelegatedToUser" (or skips if already user-owned), letting the user take control without closing the space. claim does the opposite: it transfers ownership from "user" to "agent" so the automation can proceed.

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 →