# How Ego-Browser Implements Task Space Isolation and Agent/User Ownership

> Learn how Ego-Browser secures task space isolation and defines agent/user ownership. Discover how explicit flags enable automation and block unauthorized agent commands.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: internals
- Published: 2026-08-15

---

**Ego‑Browser enforces strict isolation between agent and user control through task spaces with explicit ownership flags, where agent‑owned spaces permit full automation and user‑owned spaces block agent commands until explicitly claimed.**

The Ego‑Browser library in the `citrolabs/ego‑lite` repository provides a **task space isolation** model that prevents AI agents from hijacking user-controlled browsing sessions. Every tab or window exists within a **task space**—an isolated browsing context tagged with an `ownership` field that governs what operations are permitted. This design ensures seamless collaboration between automated agents and human users without accidental interference.

## Understanding the Ownership Model in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)

The ownership policy is defined in **[`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)** (lines 35–43) with three possible states:

| Value | Meaning |
|-------|---------|
| `"agent"` | Fully agent‑controlled; any automation permitted |
| `"agentDelegatedToUser"` | Agent owns but temporarily handed to user; treated as agent‑owned |
| `"user"` | User‑controlled; agent commands blocked or require explicit claim |

The helper **`isAgentOwned`** (lines 43–45) collapses the first two cases:

```ts
// Returns true for both "agent" and "agentDelegatedToUser"
function isAgentOwned(ownership: string): boolean {
  return ownership === "agent" || ownership === "agentDelegatedToUser";
}

```

This boolean check gates nearly every operation in the task space API.

## Creating and Selecting Agent-Owned Spaces

### `newTaskSpace`: Guaranteed Agent Ownership

The **`newTaskSpace`** function (lines 66–84) creates a fresh browsing context exclusively for the agent:

```ts
export async function newTaskSpace(name: string): Promise<void> {
  await ego.createTaskSpace(name);  // Native bridge call
  return switchTaskSpace(name);     // Auto-select the new space
}

```

Always returns an agent‑owned space—no ownership ambiguity.

### `useOrCreateTaskSpace`: Smart Space Acquisition

**`useOrCreateTaskSpace`** (lines 86–114) implements the core **policy engine**:

1. **Space exists + agent‑owned** → select it
2. **Space exists + user‑owned** → select **without claiming**; caller must explicitly call `claimTaskSpace` for automation rights
3. **Space does not exist** → create new agent‑owned space

```ts
// Example: Workflow that handles both fresh and resumed sessions
const task = await useOrCreateTaskSpace('checkout-flow');
// Safe to automate—either newly created or already agent-owned

```

## Switching and Claiming Spaces

### `switchTaskSpace`: Ownership-Enforced Selection

The **`switchTaskSpace`** helper (lines 52–64) **enforces the isolation boundary**:

```ts
export async function switchTaskSpace(name: string): Promise<void> {
  const space = await getTaskSpace(name);
  if (!isAgentOwned(space.ownership)) {
    throw new Error(`Cannot switch to user-owned task space "${name}"`);
  }
  // ...proceed with native switch
}

```

Attempting to switch to a user‑owned space throws immediately—no silent failures.

### `claimTaskSpace`: Transferring Ownership

When the agent needs control of a user‑created tab, **`claimTaskSpace`** (lines 119–128) performs the transfer:

```ts
export async function claimTaskSpace(name: string): Promise<void> {
  await ego.claimTaskSpace(name);   // Native ownership transfer
  return switchTaskSpace(name);     // Now safe to select
}

```

Use this after `useOrCreateTaskSpace` selects a user‑owned space without claiming it.

## Completing Spaces with Ownership-Aware Policies

**`completeTaskSpace`** (lines 74–78 and 96–115) demonstrates **conditional behavior** based on ownership and the `keep` option:

| Scenario | `keep: true` | `keep: false` |
|----------|------------|-------------|
| **Agent‑owned** | Close normally | Close normally |
| **User‑owned** | Skip with `{ done: false, skipped: "user-owned" }` | **Claim first**, then close |

```ts
// Safe completion attempt—respects user control
const result = await completeTaskSpace('profile-page', { keep: true });
if (result.skipped === "user-owned") {
  // Prompt user for permission or use keep:false to claim
}

```

This prevents accidental destruction of user work.

## Handoff and Takeover: Collaborative Workflows

### `handOffTaskSpace`: Yielding to the User

**`handOffTaskSpace`** (lines 119–130) enables **interactive breakpoints**:

```ts
export async function handOffTaskSpace(name: string): Promise<void> {
  const space = await getTaskSpace(name);
  if (space.ownership === "user") {
    return; // Already user-controlled; nothing to do
  }
  await ego.handOffTaskSpace(name); // Native handoff
}

```

Useful for CAPTCHAs, 2FA, or manual verification steps.

### `takeOverTaskSpace`: Resuming After Handoff

**`takeOverTaskSpace`** (lines 142–154) assumes the agent regains control—no ownership check, as the handoff was intentional:

```ts
export async function takeOverTaskSpace(name?: string): Promise<void> {
  if (name) {
    await switchTaskSpace(name);    // Select if specified
  }
  await ego.takeOverTaskSpace();    // Native agent overlay restore
}

```

Called after the user signals completion (e.g., clicking "I'm done").

### `waitForAgentControl`: Polling for Regained Control

**`waitForAgentControl`** (lines 77–88) implements **blocking recovery**:

```ts
export async function waitForAgentControl(
  name: string,
  opts: { interval?: number; timeout?: number } = {}
): Promise<void> {
  const deadline = Date.now() + (opts.timeout ?? 60_000);
  while (Date.now() < deadline) {
    try {
      await ego.snapshot(); // Harmless probe
      return; // Success = agent has control
    } catch (e) {
      if (e.code !== 'EGO_TASK_SPACE_USER_IN_CONTROL') throw e;
      // Expected failure—wait and retry
      await sleep(opts.interval ?? 100);
    }
  }
  throw new Error('Timeout waiting for agent control');
}

```

The `EGO_TASK_SPACE_USER_IN_CONTROL` error code becomes a **controlled wait signal** rather than a hard failure.

## Listing and Inspecting Spaces

**`listTaskSpaces`** (lines 101–115) retrieves normalized space data from the native bridge:

```ts
export async function listTaskSpaces(): Promise<TaskSpace[]> {
  const raw = await ego.listTaskSpaces();
  return raw.map(normalizeSpace); // Converts native format to JS-friendly objects
}

```

Essential for building UIs that show ownership status or let users select spaces to claim.

## Complete Workflow Example

```ts
import {
  useOrCreateTaskSpace,
  claimTaskSpace,
  handOffTaskSpace,
  takeOverTaskSpace,
  waitForAgentControl,
  completeTaskSpace
} from 'ego-browser';

// Phase 1: Initialize or resume checkout automation
const checkout = await useOrCreateTaskSpace('checkout-2024-06');

// Phase 2: Hit a CAPTCHA—hand to user
await handOffTaskSpace('checkout-2024-06');

// Phase 3: Poll until user finishes
await waitForAgentControl('checkout-2024-06', { interval: 10, timeout: 300 });

// Phase 4: Resume automation
await takeOverTaskSpace('checkout-2024-06');

// Phase 5: Complete and clean up
await completeTaskSpace('checkout-2024-06', { keep: false });

```

## Key Implementation Files

- **[`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)** – Core task‑space helpers and ownership logic: `isAgentOwned`, `switchTaskSpace`, `useOrCreateTaskSpace`, `claimTaskSpace`, `completeTaskSpace`, `handOffTaskSpace`, `takeOverTaskSpace`, `waitForAgentControl`, `listTaskSpaces`

- **[`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts)** – Global runtime state including native bridge reference

- **`src/taskspace-e2e.test.mjs`** – End‑to‑end tests verifying ownership transitions

- **[`skills/ego-browser/SKILL.md`](https://github.com/citrolabs/ego-lite/blob/main/skills/ego-browser/SKILL.md)** – User‑facing command documentation

## Summary

- **Ownership is explicit**: Every task space carries an `"agent"`, `"agentDelegatedToUser"`, or `"user"` flag in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts)

- **Agent commands are gated**: `switchTaskSpace` throws on user‑owned spaces; `useOrCreateTaskSpace` respects existing ownership

- **Ownership transfers require intent**: `claimTaskSpace` must be called explicitly—no automatic hijacking

- **Collaborative handoffs are first‑class**: `handOffTaskSpace` and `takeOverTaskSpace` enable CAPTCHA flows and manual interruptions

- **Polling recovery is built‑in**: `waitForAgentControl` turns ownership errors into wait loops with configurable timeouts

## Frequently Asked Questions

### What happens if an agent tries to automate a user‑owned task space?

The operation fails. `switchTaskSpace` throws an error, `completeTaskSpace` with `keep: true` returns `{ done: false, skipped: "user-owned" }`, and most automation commands error or hang. The agent must either call `claimTaskSpace` to request ownership transfer or prompt the user for confirmation.

### Can a user take back control after an agent has claimed a space?

Yes. The user can always seize control through the native browser UI—this changes the space's `ownership` to `"user"`. The agent detects this via `EGO_TASK_SPACE_USER_IN_CONTROL` errors. To resume gracefully, the agent calls `takeOverTaskSpace` after the user signals they're done, or `waitForAgentControl` to poll for availability.

### What's the difference between `"agent"` and `"agentDelegatedToUser"` ownership?

Both are treated as **agent‑owned** by `isAgentOwned`, meaning the agent retains full rights. The distinction tracks whether the space was **explicitly handed off** to the user temporarily. This flag enables the native UI to show appropriate status indicators without affecting permission checks.

### How does `waitForAgentControl` avoid infinite loops?

It accepts `timeout` and `interval` options (default 60 seconds, 100ms polling). The probe uses `ego.snapshot()`, which fails with `EGO_TASK_SPACE_USER_IN_CONTROL` for user‑owned spaces. Only this specific error triggers a retry; other errors propagate immediately. Once `snapshot()` succeeds, the loop exits.