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

> Discover how ego-lite ensures user and agent isolation in the browser using task spaces and ownership flags for secure command control and seamless hand-offs.

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

---

**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`](https://github.com/citrolabs/ego-lite/blob/main/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.

```typescript
// 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.

```typescript
// 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.

```typescript
// 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.

```typescript
// 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.

```typescript
// 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`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/ego-errors.ts) and surfaces it via `isEgoUserControlError`.

```typescript
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

```typescript
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`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/helpers.ts) | Implements `switchTaskSpace`, `claimTaskSpace`, `handOffTaskSpace`, `takeOverTaskSpace`, `waitForAgentControl` with ownership-policy enforcement |
| [`package/ego-browser/src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/ego-errors.ts) | `isEgoUserControlError` helper interpreting native "user-in-control" exceptions |
| `package/ego-browser/src/driver/*` (e.g., [`nav.ts`](https://github.com/citrolabs/ego-lite/blob/main/nav.ts), [`pointer.ts`](https://github.com/citrolabs/ego-lite/blob/main/pointer.ts)) | Low-level driver functions calling the native `ego` bridge, relying on upstream ownership checks |
| [`skills/ego-browser/SKILL.md`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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`](https://github.com/citrolabs/ego-lite/blob/main/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.