# How Task Space Ownership Policies Affect Helper Behavior in ego-browser

> Explore how task space ownership policies in ego-browser shape helper agent behavior. Understand agent, agentDelegatedToUser, and user categories and their impact on control.

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

---

**Task space ownership policies in ego-browser determine which browsing contexts an agent can control, with three categories—`agent`, `agentDelegatedToUser`, and `user`—that gate access to navigation, DOM manipulation, and lifecycle operations.**

The **ego-browser** runtime (from the [citrolabs/ego-lite](https://github.com/citrolabs/ego-lite) repository) implements a strict ownership model to prevent agents from interfering with user-controlled browsing sessions. This article explains how ownership policies shape helper behavior across the task space API.

## Task Space Ownership Categories

The ownership system classifies every task space into one of three states defined in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts):

| Ownership | Description | Agent Can Control? |
|-----------|-------------|-------------------|
| `agent` | Created by the agent; fully under agent control | ✅ Yes |
| `agentDelegatedToUser` | Created by agent but temporarily handed to user via GUI takeover | ✅ Yes (runtime ownership retained) |
| `user` | Created by the user; agent has no access until explicitly claimed | ❌ No |

The ownership check is centralized in a single utility function:

```typescript
// src/helpers.ts
function isAgentOwned(ownership) {
  return ownership === "agent" || ownership === "agentDelegatedToUser";
}

```

Every helper that requires agent privileges calls `isAgentOwned` before proceeding.

## How Ownership Policies Gate Helper Operations

### switchTaskSpace: Agent-Only Access

**`switchTaskSpace`** enforces strict ownership validation. It throws an error when targeting a `user`-owned space:

```typescript
// src/helpers.ts (lines 58-62)
if (!isAgentOwned(space.ownership)) {
  throw new Error(`switchTaskSpace requires an agent-owned task space, got ownership ${JSON.stringify(space.ownership)}`);
}

```

This prevents agents from hijacking user tabs. Attempting to switch to a `user`-owned space fails immediately with a descriptive error.

### claimTaskSpace: Converting User to Agent Ownership

**`claimTaskSpace`** is the only helper that mutates ownership state. It converts a `user` space to `agent` ownership via the native bridge, then selects it:

```typescript
// src/helpers.ts (lines 24-43)
const claimed = await taskSpaces.claim('my-personal-tab');

```

This is the **entry point** for agents that need to automate a pre-existing user session.

### handOffTaskSpace: No-Op for User-Owned Spaces

**`handOffTaskSpace`** respects existing user control. For `user`-owned spaces, the helper returns early with no action:

```typescript
// src/helpers.ts (lines 21-35)
// Skips user-owned spaces—user already has control

```

For agent-owned spaces, it invokes the native bridge (`ego.handOffTaskSpace`) to hide the agent overlay and transfer UI control.

### completeTaskSpace: Conditional Behavior Based on Keep Flag

**`completeTaskSpace`** implements dual-path logic depending on the `keep` option:

| Space Ownership | `keep: true` | `keep: false` |
|-----------------|--------------|---------------|
| `agent` / `agentDelegatedToUser` | Close space, keep page open | Close space and page |
| `user` | Skip: `{ done: false, skipped: "user-owned" }` | **Claim first**, then close |

The source ([`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) lines 66-71) shows this conditional enforcement: preserving user control when possible, but allowing forced takeover when explicitly requested.

### takeOverTaskSpace and waitForAgentControl: Bridge-Delegated Validation

**`takeOverTaskSpace`** and **`waitForAgentControl`** differ from other helpers: they **do not perform ownership checks** in the helper layer. Instead, they rely on the native `ego` bridge to surface errors when user control cannot be overridden.

This design delegates policy enforcement to the lower-level runtime, which can implement platform-specific behaviors (macOS entitlement checks, browser permission dialogs, etc.).

## Practical Code Examples

### Switching to an Agent-Owned Space

```javascript
// Only succeeds for agent-owned spaces
try {
  const space = await taskSpaces.switch('research-123');
  console.log('Switched to:', space.name);
} catch (e) {
  console.error('Cannot switch:', e.message);
  // Error: switchTaskSpace requires an agent-owned task space...
}

```

### Claiming User-Created Content

```javascript
// Convert user-owned to agent-owned
const claimed = await taskSpaces.claim('my-personal-tab');
console.log('Claimed and selected:', claimed.id);
// Ownership now: 'agent'

```

### Safe Hand-Off with Skip Detection

```javascript
const result = await taskSpaces.handOff();
if (!result.done) {
  console.log('Skipped – already user-owned');
  // No error thrown; graceful no-op
}

```

### Completing with User-Control Preservation

```javascript
// Respect user ownership when keeping page open
const outcome = await taskSpaces.complete('analysis-42', { keep: true });
if (!outcome.done && outcome.skipped === 'user-owned') {
  console.log('User retains control; agent cleanup skipped');
}

```

### Waiting for Control Restoration

```javascript
// Polls until agentDelegatedToUser or agent ownership restored
await taskSpaces.waitForAgentControl('demo-space', { timeout: 300 });
console.log('Agent control restored, resuming automation');

```

## Architectural Flow: From Discovery to Action

The ownership policy system follows a consistent pipeline across all helpers:

1. **Discovery**: `listTaskSpaces()` retrieves raw spaces from `ego.listTaskSpaces`
2. **Normalization**: `normalizeTaskSpace()` converts JSON into uniform JS objects
3. **Ownership Decision**: Helper inspects `space.ownership` against `isAgentOwned()`
4. **Action**:
   - **Allowed**: Call native method (`ego.useTaskSpace`, `ego.claimTaskSpace`, etc.)
   - **Forbidden**: Throw error or return skipped result

This keeps **policy enforcement colocated** in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) while delegating state transitions to native bindings.

## Key Source Files

| File | Purpose |
|------|---------|
| [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) | Core ownership logic, all task space helpers |
| [`src/ego-errors.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/ego-errors.ts) | Error handling (`assertNoEgoError`, `isEgoUserControlError`) |
| [`src/format.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/format.ts) | Public API documentation generation for `help()` |

## Summary

- **Three ownership states** (`agent`, `agentDelegatedToUser`, `user`) gate all task space operations in ego-browser
- **`isAgentOwned()`** centralizes policy checks; most helpers refuse `user`-owned spaces
- **`claimTaskSpace`** is the sole helper that changes ownership, converting `user` → `agent`
- **`handOffTaskSpace`** and **`completeTaskSpace`** with `keep: true` skip operations that would violate user control
- **Bridge-delegated helpers** (`takeOverTaskSpace`, `waitForAgentControl`) rely on native runtime for enforcement

## Frequently Asked Questions

### Can an agent automate a tab the user opened manually?

Not directly. The agent must first call **`claimTaskSpace`** to convert the `user`-owned space to `agent` ownership. Until then, all navigation and DOM helpers throw ownership errors.

### What happens if `handOffTaskSpace` targets a user-owned space?

Nothing. The helper returns a skipped result without error. The native bridge is never invoked because the user already retains UI control—no additional hand-off is needed.

### Why do `takeOverTaskSpace` and `waitForAgentControl` skip ownership checks?

These operations are inherently about **regaining** control from users. The helper layer defers to the native `ego` bridge, which can implement platform-specific permission flows (system dialogs, timeouts) that the JavaScript layer cannot predict.

### How does `completeTaskSpace` with `keep: false` handle user-owned spaces?

It **claims the space first**, then closes it. This ensures the agent can always tear down resources it created, even if the user briefly interacted with the tab.